Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
cbd0b3b
feat(core): define domain join configuration and policy
mchave3 Oct 4, 2026
c4486ea
feat(profiles): protect domain join credentials and media projection
mchave3 Oct 4, 2026
9e96d75
test(profiles): restore Unicode asset fixture
mchave3 Oct 4, 2026
0b8b538
feat(osd): add domain join pages and destination OU configuration
mchave3 Oct 4, 2026
648194b
fix(osd): preserve local domain join drafts on reload
mchave3 Oct 4, 2026
fda49b2
feat(deploy): prepare domain join input before disk changes
mchave3 Oct 4, 2026
2934dec
feat(postinstall): join domains and place computer accounts safely
mchave3 Oct 4, 2026
74b5770
test(postinstall): verify escaped account move requests
mchave3 Oct 4, 2026
7248914
feat(deploy): stage domain joining with matched runtime capability
mchave3 Oct 4, 2026
18e8283
feat(postinstall): recover domain joining without blocking deployment
mchave3 Oct 4, 2026
4c579fd
fix(postinstall): clear settled domain recovery ownership safely
mchave3 Oct 4, 2026
4215298
fix(deploy): complete domain join destination input
mchave3 Oct 4, 2026
7a0a28c
fix(diagnostics): redact domain join credential formats
mchave3 Oct 4, 2026
ef24624
fix(postinstall): reject incomplete domain join reports
mchave3 Oct 4, 2026
e5b66fb
fix(domain-join): validate input and clarify provisioning choices
mchave3 Oct 4, 2026
7ac2899
fix(deploy): preserve domain OU keyboard selection
mchave3 Oct 4, 2026
c2dea62
fix(postinstall): retain runtime contract version one
mchave3 Oct 4, 2026
5ddb0d6
fix(localization): translate domain join resources
mchave3 Oct 4, 2026
ad474de
style(localization): normalize Turkish resource formatting
mchave3 Oct 4, 2026
292f21d
fix(domain-join): align configuration pages with Autopilot
mchave3 Oct 4, 2026
9eb3bc8
fix(localization): standardize domain join zero-touch spelling
mchave3 Oct 4, 2026
1417a7a
Merge branch 'main' into feat/domain-join-218
mchave3 Oct 5, 2026
894d9b6
fix(domain-join): align pages with OSD layout and shared mode confirm…
mchave3 Oct 5, 2026
d17f3db
fix(domain-join): group destinations and shorten page descriptions
mchave3 Oct 5, 2026
7422d28
fix(postinstall): wait for directory readiness and report domain outc…
mchave3 Oct 5, 2026
01d232a
fix(core): keep a disabled domain join draft from blocking media and …
mchave3 Oct 5, 2026
4e31bc9
fix(localization): correct the General page title in sixteen cultures
mchave3 Oct 5, 2026
4c5646d
fix(domain-join): add card icons and type-ramp text styles
mchave3 Oct 5, 2026
fa8e06a
fix(domain-join): skip presentation refresh while disposing the view …
mchave3 Oct 5, 2026
b84ca02
fix(domain-join): keep inputs in place when a validation message appears
mchave3 Oct 5, 2026
6dc61b3
fix(localization): reword domain join strings around OUs and plain ac…
mchave3 Oct 5, 2026
a937a9e
fix(postinstall): keep readiness causes and log domain outcomes
mchave3 Oct 5, 2026
a597378
refactor(core): remove the unused Autopilot toggle evaluator and weak…
mchave3 Oct 5, 2026
fa7c4e4
fix(domain-join): harden the authoring pages against stale and stuck …
mchave3 Oct 5, 2026
ba01309
fix(deploy): validate domain join input early and report failures cor…
mchave3 Oct 5, 2026
88fa071
feat(deploy): collect domain join input in a wizard step
mchave3 Oct 5, 2026
5d875ec
fix(deploy): center the domain join step content vertically
mchave3 Oct 5, 2026
5cc9166
feat(deploy): add a reveal button to the domain join password
mchave3 Oct 5, 2026
e4640fb
fix(postinstall): tolerate recovery edge cases and log from the worker
mchave3 Oct 5, 2026
c5bab94
fix(domain-join): accept only OUs and join in the default location wh…
mchave3 Oct 5, 2026
bc6fbe8
refactor(domain-join): remove unused members and share the state seeds
mchave3 Oct 5, 2026
5bc8a91
refactor(domain-join): manage OUs like Autopilot profiles on the auth…
mchave3 Oct 5, 2026
b21fa29
refactor(domain-join): list discovered OUs in a height-bounded table
mchave3 Oct 5, 2026
2eefb5b
fix(domain-join): align the OU picker content with the dialog edges
mchave3 Oct 5, 2026
53a01c0
fix(domain-join): keep the default OU visible after it is selected
mchave3 Oct 5, 2026
6fa3ddc
refactor(domain-join): remove the Clear button beside the default OU
mchave3 Oct 5, 2026
f82d897
feat(domain-join): turn the default OU on or off with a switch
mchave3 Oct 5, 2026
77a62d6
feat(telemetry): report Domain Join usage on the media and deployment…
mchave3 Oct 5, 2026
c0fcd0b
feat(domain-join): hide the OOBE account screens for a joined computer
mchave3 Oct 5, 2026
187cd17
Merge branch 'main' into feat/domain-join-218
mchave3 Oct 8, 2026
a638a4a
refactor(domain-join): show the OU list right under the OU actions
mchave3 Oct 8, 2026
fe6e77f
fix(domain-join): stop the crash when removing several OUs and refine…
mchave3 Oct 8, 2026
30be31c
fix(domain-join): keep OU table rows aligned when several OUs are added
mchave3 Oct 8, 2026
1d1000e
refactor(ui): make every table full width, proportional and sortable
mchave3 Oct 8, 2026
5792f21
refactor(domain-join): model several domains that own their OUs
mchave3 Oct 8, 2026
2607393
refactor(domain-join): manage domains and their OUs through catalogs
mchave3 Oct 8, 2026
9e3fe94
refactor(domain-join): key authoring passwords by account
mchave3 Oct 8, 2026
03ac61e
refactor(domain-join): generate one credential payload per domain
mchave3 Oct 8, 2026
843da20
refactor(profiles): carry one domain join secret per account
mchave3 Oct 8, 2026
b45897c
refactor(domain-join): adapt Core consumers and media telemetry to se…
mchave3 Oct 8, 2026
e4059a5
refactor(domain-join): adapt authoring services to several domains
mchave3 Oct 8, 2026
8e0b83b
feat(domain-join): author several domains from one view model
mchave3 Oct 8, 2026
958835d
feat(domain-join): manage domains and OUs from command bars
mchave3 Oct 8, 2026
21864be
feat(domain-join): choose the domain on the Deploy step
mchave3 Oct 8, 2026
6ed852f
feat(domain-join): localize the multi-domain authoring pages
mchave3 Oct 8, 2026
4b622cb
fix(domain-join): load profiles saved with the single-domain shape
mchave3 Oct 8, 2026
39da7cd
fix(domain-join): keep a domain's account when edited in Interactive
mchave3 Oct 8, 2026
311549c
test(domain-join): pin shared account passwords and per-domain creden…
mchave3 Oct 8, 2026
020147a
Merge branch 'main' into feat/domain-join-218
mchave3 Oct 8, 2026
fec6cd7
fix(domain-join): let the domains table span its full width
mchave3 Oct 8, 2026
9c98805
feat(domain-join): keep join passwords while their account is stored
mchave3 Oct 8, 2026
d664446
feat(domain-join): rename the display name of a listed OU
mchave3 Oct 8, 2026
79219dd
feat(domain-join): let the listed domains and OUs decide the technici…
mchave3 Oct 8, 2026
c855b1b
refactor(domain-join): remove code left unused by the multi-domain ch…
mchave3 Oct 8, 2026
ede97ee
refactor(domain-join): rename the string keys inherited from the OU c…
mchave3 Oct 8, 2026
6ce9a68
fix(deploy): keep the Domain Join line out of the disk erase confirma…
mchave3 Oct 8, 2026
5b51d2f
feat(domain-join): warn when no local account will skip the Windows a…
mchave3 Oct 8, 2026
88c7be6
fix(domain-join): only a created local account silences the account p…
mchave3 Oct 8, 2026
4bb3c02
feat(domain-join): end setup on the sign-in screen after a verified join
mchave3 Oct 8, 2026
e48c72f
fix(ui): let single-selection tables span their full width
mchave3 Oct 8, 2026
c22d2f0
fix(domain-join): load null domain and OU lists as empty lists
mchave3 Oct 8, 2026
30d0216
fix(deploy): discard the typed domain when the Domain join step is re…
mchave3 Oct 8, 2026
b1b85e4
Merge remote-tracking branch 'origin/main' into feat/domain-join-218
mchave3 Oct 8, 2026
8c5752b
fix(localization): align Domain Join texts with the interface and fix…
mchave3 Oct 8, 2026
4ebfece
refactor(domain-join): remove unreachable code and strings before rel…
mchave3 Oct 8, 2026
c2f3b60
fix(config): raise the Deploy schema to 16 for the Domain Join contract
mchave3 Oct 8, 2026
054906b
test(bootstrap): remove the timing-dependent launch identity test
mchave3 Oct 8, 2026
bdf1231
test(core): remove the timing-dependent cross-process lease test
mchave3 Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 0 additions & 65 deletions src/Foundry.Bootstrap.Tests/Processes/ApplicationLauncherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,69 +326,4 @@ public async Task StartupFailureRecoveryWaitsForExitWithinBoundedGrace(bool exit
Directory.Delete(directory, recursive: true);
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task NegotiatedChildAcknowledgementUsesActualLaunchIdentity(bool connect)
{
string directory = Directory.CreateTempSubdirectory("FoundryStartup-").FullName;
Process? fixture = null;
try
{
string application = connect ? "Foundry.Connect" : "Foundry.Deploy";
string executable = Path.Combine(directory, application + ".exe");
File.WriteAllText(executable, "prepared payload");
File.WriteAllText(Path.Combine(directory, "foundry.startup.json"), "{\"protocolVersions\":[1]}");
using var logger = new LoggerConfiguration().CreateLogger();
var launcher = new ApplicationLauncher(logger, Path.Combine(directory, "session"), startProcess: start =>
{
string script = $$"""
$status = @{
protocolVersion = [int]$env:FOUNDRY_STARTUP_PROTOCOL
sessionId = $env:FOUNDRY_DIAGNOSTIC_SESSION_ID
launchId = $env:FOUNDRY_STARTUP_LAUNCH_ID
application = '{{application}}'
processId = $PID
stage = 'ui_ready'
timestampUtc = [DateTime]::UtcNow.ToString('o')
} | ConvertTo-Json -Compress
[IO.File]::WriteAllText($env:FOUNDRY_STARTUP_STATUS_PATH, $status)
{{(connect ? "exit 0" : "Start-Sleep -Seconds 30")}}
""";
start.FileName = "powershell.exe";
start.WorkingDirectory = Path.GetTempPath();
start.CreateNoWindow = true;
start.ArgumentList.Clear();
start.ArgumentList.Add("-NoProfile");
start.ArgumentList.Add("-NonInteractive");
start.ArgumentList.Add("-EncodedCommand");
start.ArgumentList.Add(Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(script)));
Process? child = Process.Start(start);
if (child is not null) { fixture = Process.GetProcessById(child.Id); }
return child;
}, releasePayload: File.Delete);
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var environment = new Dictionary<string, string?> { ["FOUNDRY_DIAGNOSTIC_SESSION_ID"] = "TEST" };
ApplicationLaunchResult result = connect
? await launcher.RunConnectAsync(executable, "missing.json", environment, deadline.Token)
: await launcher.StartDeployAsync(executable, environment, deadline.Token);
Assert.True(result.Succeeded);
Assert.True(result.ReadinessConfirmed);
Assert.Equal("ui_ready", result.LastStage);
Assert.Equal(!connect, File.Exists(executable));
}
finally
{
if (fixture is not null)
{
using (fixture)
{
if (!fixture.HasExited) { fixture.Kill(); }
await fixture.WaitForExitAsync(CancellationToken.None);
}
}
Directory.Delete(directory, recursive: true);
}
}
}
60 changes: 60 additions & 0 deletions src/Foundry.Core.Tests/BootMediaTelemetryPropertyBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,66 @@ public void Build_WhenAutopilotIsDisabled_ReportsDisabledProvisioningMode()
Assert.Equal(TelemetryBootMediaUsbOperations.None, result["boot_media_usb_operation"]);
}

[Theory]
[InlineData(false, DomainJoinMode.Automatic, "disabled", 0, 0, false, false)]
[InlineData(true, DomainJoinMode.Automatic, "zero_touch", 2, 3, true, true)]
[InlineData(true, DomainJoinMode.Interactive, "interactive", 2, 3, true, false)]
public void Build_ReportsDomainJoinUsageWithoutDirectoryNames(bool enabled, DomainJoinMode mode, string expectedMode,
int expectedDomains, int expectedOus, bool expectedDefaultOu, bool expectedSharedAccount)
{
var document = new FoundryConfigurationDocument
{
DomainJoin = new DomainJoinSettings
{
IsEnabled = enabled,
Mode = mode,
SharedAccountName = @"PRIVATE\joiner",
DefaultDomainId = "one",
Domains =
[
new()
{
Id = "one",
DomainName = "private.example.test",
DefaultOuId = "ou-1",
OrganizationalUnits =
[
new() { Id = "ou-1", DisplayName = "PrivateOne", DistinguishedName = "OU=PrivateOne,DC=private,DC=example,DC=test" },
new() { Id = "ou-2", DisplayName = "PrivateTwo", DistinguishedName = "OU=PrivateTwo,DC=private,DC=example,DC=test" }
]
},
new()
{
Id = "two",
DomainName = "private2.example.test",
AccountName = @"PRIVATE2\joiner",
OrganizationalUnits = [new() { Id = "ou-3", DisplayName = "PrivateThree", DistinguishedName = "OU=PrivateThree,DC=private2,DC=example,DC=test" }]
}
]
}
};

IReadOnlyDictionary<string, object?> result = BootMediaTelemetryPropertyBuilder.Build(
TelemetryBootMediaTargets.Iso,
TelemetryBootMediaUsbOperations.None,
new MediaPreflightOptions(),
document,
success: true,
failedStepName: null,
duration: TimeSpan.Zero,
connectRuntimePayloadSource: TelemetryRuntimePayloadSources.None,
deployRuntimePayloadSource: TelemetryRuntimePayloadSources.None);

Assert.Equal(enabled, result["domain_join_enabled"]);
Assert.Equal(expectedMode, result["domain_join_mode"]);
Assert.Equal(expectedDomains, result["domain_join_domain_count"]);
Assert.Equal(expectedOus, result["domain_join_ou_count"]);
Assert.Equal(expectedDefaultOu, result["domain_join_default_ou_set"]);

Assert.Equal(expectedSharedAccount, result["domain_join_shared_account_used"]);
Assert.DoesNotContain(result.Values, value => value is string text && text.Contains("private", StringComparison.OrdinalIgnoreCase));
}

[Fact]
public void Build_WhenInteractiveHardwareHashUploadIsEnabled_ReportsInteractiveMode()
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@ namespace Foundry.Core.Tests.Configuration;

public sealed class ConfigurationOverviewEvaluatorTests
{
[Theory]
[InlineData(DomainJoinMode.Interactive, true, ConfigurationOverviewState.Configured)]
[InlineData(DomainJoinMode.Interactive, false, ConfigurationOverviewState.NeedsAttention)]
[InlineData(DomainJoinMode.Automatic, true, ConfigurationOverviewState.Configured)]
[InlineData(DomainJoinMode.Automatic, false, ConfigurationOverviewState.NeedsAttention)]
public void Evaluate_DomainJoinOnlyActiveModeUsesReadiness(DomainJoinMode mode, bool ready, ConfigurationOverviewState expected)
{
var configuration = new FoundryConfigurationDocument
{
DomainJoin = new DomainJoinSettings { IsEnabled = true, Mode = mode }
};
ConfigurationOverviewEvaluation evaluation = ConfigurationOverviewEvaluator.Evaluate(
CreateContext(configuration) with { IsDomainJoinConfigurationReady = ready });
ConfigurationOverviewItem active = mode == DomainJoinMode.Interactive ? ConfigurationOverviewItem.DomainJoinInteractive : ConfigurationOverviewItem.DomainJoinAutomatic;
ConfigurationOverviewItem inactive = mode == DomainJoinMode.Interactive ? ConfigurationOverviewItem.DomainJoinAutomatic : ConfigurationOverviewItem.DomainJoinInteractive;
Assert.Equal(expected, evaluation[active]);
Assert.Equal(ConfigurationOverviewState.NotSelected, evaluation[inactive]);
Assert.Equal(expected, ConfigurationOverviewNavigationEvaluator.EvaluateTarget(evaluation,
mode == DomainJoinMode.Interactive ? ConfigurationNavigationTarget.DomainJoinInteractive : ConfigurationNavigationTarget.DomainJoinAutomatic));
}

[Fact]
public void Evaluate_AutomaticDomainWithoutProtectionNeedsAttentionOnGeneralPage()
{
var configuration = new FoundryConfigurationDocument
{
DomainJoin = new DomainJoinSettings { IsEnabled = true, Mode = DomainJoinMode.Automatic }
};
ConfigurationOverviewEvaluation evaluation = ConfigurationOverviewEvaluator.Evaluate(CreateContext(configuration));
Assert.Equal(ConfigurationOverviewState.NeedsAttention, evaluation[ConfigurationOverviewItem.DeploymentProtection]);
}

[Theory]
[InlineData(false, 0, true, ConfigurationOverviewState.Disabled)]
[InlineData(true, 0, true, ConfigurationOverviewState.NeedsAttention)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public sealed class ConfigurationSchemaVersionsTests
[Fact]
public void CurrentSchemaVersions_MatchNextPublishedContractVersions()
{
Assert.Equal(17, ConfigurationSchemaVersions.FoundryCurrent);
Assert.Equal(15, ConfigurationSchemaVersions.DeployCurrent);
Assert.Equal(18, ConfigurationSchemaVersions.FoundryCurrent);
Assert.Equal(16, ConfigurationSchemaVersions.DeployCurrent);
Assert.Equal(5, ConfigurationSchemaVersions.ConnectCurrent);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,124 @@ namespace Foundry.Core.Tests.Configuration;

public sealed class DeployConfigurationGeneratorTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void AnInvalidDomainListBlocksMediaOnlyWhileDomainJoinIsEnabled(bool enabled)
{
var document = new FoundryConfigurationDocument
{
DomainJoin = new()
{
IsEnabled = enabled,
DefaultDomainId = "a",
Domains = [new() { Id = "a", DomainName = "fabrikam.test", OrganizationalUnits = [new() { Id = "devices", DisplayName = "Devices", DistinguishedName = "OU=Devices,DC=contoso,DC=test" }] }]
}
};
var generator = new DeployConfigurationGenerator();

if (enabled)
{
Assert.Throws<InvalidOperationException>(() => generator.Generate(document, null, null, null, null));
return;
}

var media = generator.Generate(document, null, null, null, null);
Assert.False(media.DomainJoin.IsEnabled);
Assert.Empty(media.DomainJoin.Domains);
}

[Fact]
public void InteractiveProjectionOmitsAccountsAndPayloads()
{
using var secrets = new DomainJoinSecretState();
secrets.SetPassword("CORP\\join", "password");
var document = new FoundryConfigurationDocument { DomainJoin = TwoDomains() with { Mode = DomainJoinMode.Interactive } };
var media = new DeployConfigurationGenerator().Generate(document, null, null, null, secrets);
Assert.True(media.DomainJoin.IsEnabled);
Assert.Equal(2, media.DomainJoin.Domains.Count);
Assert.Equal("corp", media.DomainJoin.DefaultDomainId);

Assert.All(media.DomainJoin.Domains, domain =>
{
Assert.Null(domain.AccountName);
Assert.Null(domain.EncryptedCredentials);
});
Assert.Equal("ws", media.DomainJoin.Domains[0].DefaultOuId);
Assert.Single(media.DomainJoin.Domains[0].OrganizationalUnits);
}

[Theory]
[InlineData(false, true, true)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public void ZeroTouchRequiresProtectionAndEveryPassword(bool protectedMedia, bool hasKey, bool hasEveryPassword)
{
using var secrets = new DomainJoinSecretState();
secrets.SetPassword("CORP\\join", "p1");
if (hasEveryPassword) secrets.SetPassword("EMEA\\join", "p2");
var document = new FoundryConfigurationDocument { DomainJoin = TwoDomains() };
Assert.Throws<InvalidOperationException>(() => new DeployConfigurationGenerator().Generate(document, hasKey ? new byte[32] : null,
new() { IsEnabled = protectedMedia }, null, secrets));
}

[Fact]
public void ZeroTouchWritesOnePayloadPerDomainBoundToItsOwnDomain()
{
using var secrets = new DomainJoinSecretState();
secrets.SetPassword("CORP\\join", " exact password ");
secrets.SetPassword("EMEA\\join", "p2");
byte[] key = new byte[32];
var generator = new DeployConfigurationGenerator();
var media = generator.Generate(new FoundryConfigurationDocument { DomainJoin = TwoDomains() }, key, new() { IsEnabled = true }, null, secrets);
Assert.Equal("CORP\\join", media.DomainJoin.Domains[0].AccountName);
Assert.Equal("EMEA\\join", media.DomainJoin.Domains[1].AccountName);
byte[][] plaintext = media.DomainJoin.Domains
.Select(domain => MediaSecretEnvelopeProtector.DecryptBytes(domain.EncryptedCredentials!, key, MediaSecretEnvelopeProtector.DeploymentKeyId)).ToArray();
try
{
using var corp = DomainJoinCredentialPayloadCodec.Decode(plaintext[0], new("corp.test", "CORP\\join"));
using var emea = DomainJoinCredentialPayloadCodec.Decode(plaintext[1], new("emea.test", "EMEA\\join"));
Assert.Equal(" exact password ", new string(corp.Password.Span));
Assert.Equal("p2", new string(emea.Password.Span));
Assert.DoesNotContain("exact password", generator.Serialize(media));
}
finally { foreach (byte[] bytes in plaintext) System.Security.Cryptography.CryptographicOperations.ZeroMemory(bytes); }
}

[Fact]
public void ASharedAccountPayloadCannotBeDecodedForAnotherDomain()
{
using var secrets = new DomainJoinSecretState();
secrets.SetPassword("CORP\\join", "shared");
byte[] key = new byte[32];
DomainJoinSettings settings = TwoDomains();
settings = settings with { Domains = [settings.Domains[0], settings.Domains[1] with { AccountName = null }] };
var media = new DeployConfigurationGenerator().Generate(new FoundryConfigurationDocument { DomainJoin = settings }, key, new() { IsEnabled = true }, null, secrets);
Assert.All(media.DomainJoin.Domains, domain => Assert.Equal("CORP\\join", domain.AccountName));
byte[] corp = MediaSecretEnvelopeProtector.DecryptBytes(media.DomainJoin.Domains[0].EncryptedCredentials!, key, MediaSecretEnvelopeProtector.DeploymentKeyId);
try
{
Assert.Throws<System.Security.Cryptography.CryptographicException>(() => DomainJoinCredentialPayloadCodec.Decode(corp, new("emea.test", "CORP\\join")));
using var decoded = DomainJoinCredentialPayloadCodec.Decode(corp, new("corp.test", "CORP\\join"));
Assert.Equal("shared", new string(decoded.Password.Span));
}
finally { System.Security.Cryptography.CryptographicOperations.ZeroMemory(corp); }
}

private static DomainJoinSettings TwoDomains() => new()
{
IsEnabled = true,
Mode = DomainJoinMode.Automatic,
SharedAccountName = "CORP\\join",
DefaultDomainId = "corp",
Domains =
[
new() { Id = "corp", DomainName = "corp.test", DefaultOuId = "ws", OrganizationalUnits = [new() { Id = "ws", DisplayName = "Workstations", DistinguishedName = "OU=Workstations,DC=corp,DC=test" }] },
new() { Id = "emea", DomainName = "emea.test", AccountName = "EMEA\\join" }
]
};

[Theory]
[InlineData("26.10.3.1")]
[InlineData(null)]
Expand Down
Loading
Loading