feat: implement 6-DC active binding detection, targeted sync, and Task.Run async batch engine with comprehensive guide

This commit is contained in:
2026-10-20 21:34:57 +09:00
parent b2e1b8c1d0
commit 5978de5924
7 changed files with 835 additions and 23 deletions
+207
View File
@@ -376,3 +376,210 @@ function Get-ADSampleUsers {
})
}
}
function Get-ADDomainControllers {
param(
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
try {
if (Get-Command Get-ADDomainController -ErrorAction SilentlyContinue) {
$dcs = Get-ADDomainController -Filter *
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($d in $dcs) {
$list.Add([PSCustomObject]@{
HostName = $d.HostName
IPv4Address = $d.IPv4Address
Site = $d.Site
OperatingSystem = $d.OperatingSystem
IsPdc = ($d.OperationMasterRoles -contains "PDCEmulator")
IsLive = $true
})
}
return $list.ToArray()
}
} catch {
Write-Warning "실제 AD DC 목록 조회 실패: $($_.Exception.Message)"
}
}
# 6개 다중 DC 환경 모사 데이터
return @(
[PSCustomObject]@{ HostName = "dc01.corp.local"; IPv4Address = "10.10.1.11"; Site = "Seoul-HQ"; OperatingSystem = "Windows Server 2022 Datacenter"; IsPdc = $true; IsLive = $false },
[PSCustomObject]@{ HostName = "dc02.corp.local"; IPv4Address = "10.10.1.12"; Site = "Seoul-HQ"; OperatingSystem = "Windows Server 2022 Datacenter"; IsPdc = $false; IsLive = $false },
[PSCustomObject]@{ HostName = "dc03.corp.local"; IPv4Address = "10.10.1.13"; Site = "Seoul-HQ"; OperatingSystem = "Windows Server 2019 Datacenter"; IsPdc = $false; IsLive = $false },
[PSCustomObject]@{ HostName = "dc04.corp.local"; IPv4Address = "10.20.1.14"; Site = "Pangyo-DC"; OperatingSystem = "Windows Server 2022 Datacenter"; IsPdc = $false; IsLive = $false },
[PSCustomObject]@{ HostName = "dc05.corp.local"; IPv4Address = "10.30.1.15"; Site = "Daejeon-DR"; OperatingSystem = "Windows Server 2019 Datacenter"; IsPdc = $false; IsLive = $false },
[PSCustomObject]@{ HostName = "dc06.corp.local"; IPv4Address = "10.40.1.16"; Site = "Azure-KOR"; OperatingSystem = "Windows Server 2022 Datacenter"; IsPdc = $false; IsLive = $false }
)
}
function New-ADSecurityGroupOnTargetDc {
param(
[string]$GroupName,
[string]$GroupScope = "Global",
[string]$Description = "ModernAdminStudio 자동 생성 보안그룹",
[string]$TargetDc = "",
[string]$TargetOU = "",
[string[]]$Members = @(),
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if (-not $TargetDc) {
$TargetDc = if ($Config -and $Config.ActiveDirectory) { $Config.ActiveDirectory.DomainController } else { "dc04.corp.local" }
}
if (-not $TargetOU) {
$TargetOU = if ($Config -and $Config.ActiveDirectory) { $Config.ActiveDirectory.BaseOU } else { "OU=SecurityGroups,DC=corp,DC=local" }
}
if ($useLive) {
try {
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
$useLdaps = [bool]$Config.ActiveDirectory.UseLdaps
$proto = if ($useLdaps) { "LDAP://$TargetDc:636" } else { "LDAP://$TargetDc" }
$ouPath = "$proto/$TargetOU"
$ouEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($ouPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($ouPath, $user, $pass)
}
# 그룹 생성
$groupEntry = $ouEntry.Children.Add("CN=$GroupName", "group")
$groupEntry.Properties["sAMAccountName"].Value = $GroupName
$groupEntry.Properties["description"].Value = $Description
# Global Security Group Type: -2147483646 (0x80000002)
$groupType = switch ($GroupScope) {
"DomainLocal" { -2147483644 } # 0x80000004
"Universal" { -2147483640 } # 0x80000008
default { -2147483646 } # Global
}
$groupEntry.Properties["groupType"].Value = $groupType
$groupEntry.CommitChanges()
# 멤버 추가
$addedMembers = [System.Collections.Generic.List[string]]::new()
if ($Members -and $Members.Count -gt 0) {
foreach ($m in $Members) {
if ([string]::IsNullOrWhiteSpace($m)) { continue }
try {
# TargetDc 상에서 사용자 검색
$searcher = New-Object System.DirectoryServices.DirectorySearcher($ouEntry)
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$m))"
$found = $searcher.FindOne()
if ($found) {
$userDn = $found.Properties["distinguishedname"][0]
$groupEntry.Properties["member"].Add($userDn)
$addedMembers.Add($m)
}
} catch {
Write-Warning "사용자 [$m] 그룹 멤버 추가 중 오류: $($_.Exception.Message)"
}
}
$groupEntry.CommitChanges()
}
return [PSCustomObject]@{
Success = $true
Message = "타겟 DC [$TargetDc]에 보안그룹 [$GroupName] 직접 생성 완료 (멤버 $($addedMembers.Count)명 추가)"
TargetDc = $TargetDc
GroupDN = "CN=$GroupName,$TargetOU"
AddedMembers = $addedMembers.ToArray()
IsLive = $true
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "타겟 DC [$TargetDc]에 그룹 생성 실패: $($_.Exception.Message)"
TargetDc = $TargetDc
IsLive = $true
}
}
}
# Mock / Fallback
return [PSCustomObject]@{
Success = $true
Message = "[Direct DC Binding] 타겟 DC [$TargetDc]에 보안그룹 [$GroupName] 직접 생성 성공 (NetApp 복제 대기 0초 달성)"
TargetDc = $TargetDc
GroupDN = "CN=$GroupName,$TargetOU"
AddedMembers = $Members
IsLive = $false
}
}
function Sync-ADObjectToTargetDc {
param(
[string]$ObjectDN,
[string]$SourceDc = "dc01.corp.local",
[string]$DestinationDc = "dc04.corp.local",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
try {
# 1. PowerShell ActiveDirectory Module의 Sync-ADObject 시도
if (Get-Command Sync-ADObject -ErrorAction SilentlyContinue) {
Sync-ADObject -Object $ObjectDN -Destination $DestinationDc -Source $SourceDc -ErrorAction Stop
return [PSCustomObject]@{
Success = $true
Message = "Sync-ADObject 완료: [$ObjectDN] ($SourceDc -> $DestinationDc 즉시 단방향 복제)"
SourceDc = $SourceDc
DestinationDc = $DestinationDc
Method = "Sync-ADObject"
IsLive = $true
}
}
# 2. repadmin 명령행 도구 폴백 시도
$repOutput = & repadmin.exe /replicate $DestinationDc $SourceDc $ObjectDN 2>&1
if ($LASTEXITCODE -eq 0) {
return [PSCustomObject]@{
Success = $true
Message = "repadmin 복제 성공 ($SourceDc -> $DestinationDc)"
SourceDc = $SourceDc
DestinationDc = $DestinationDc
Method = "repadmin /replicate"
IsLive = $true
}
} else {
return [PSCustomObject]@{
Success = $false
Message = "repadmin 실패: $repOutput"
SourceDc = $SourceDc
DestinationDc = $DestinationDc
Method = "repadmin /replicate"
IsLive = $true
}
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "AD 개체 대상 복제 실패: $($_.Exception.Message)"
SourceDc = $SourceDc
DestinationDc = $DestinationDc
IsLive = $true
}
}
}
# Mock / Fallback
return [PSCustomObject]@{
Success = $true
Message = "[Targeted Sync] 개체 [$ObjectDN]가 소스 DC [$SourceDc]에서 목적지 DC [$DestinationDc]로 즉시 단방향 복제되었습니다."
SourceDc = $SourceDc
DestinationDc = $DestinationDc
Method = "Targeted Replication (Simulated)"
IsLive = $false
}
}