Files
adusermanager/Scripts/Common.ps1
2025-09-15 13:46:19 +09:00

654 lines
28 KiB
PowerShell

# ================================================================================
# 파일: Scripts/Common.ps1
# 역할: 공용 함수 라이브러리
#
# 작성자: 양범진
# 버전: 1.15
# 생성일자: 2025-06-05
# 최종 수정일자: 2025-06-17
#
# 설명:
# - 프로그램 전반에서 사용되는 공통 함수들을 정의
# - 스레드 관련 복잡성을 제거하고 안정적인 동기 실행 방식을 사용
# - OS 종류에 따라 모듈 설치를 다르게 진행(Windows 10/11 선택적 기능 추가)
# ================================================================================
#region 공용 유틸리티 함수
# --- 필수 모듈 검사 및 설치 함수 ---
# 스크립트 실행에 필요한 PowerShell 모듈 및 기능이 있는지 확인하고, 없을 경우 사용자에게 설치를 안내
function Test-RequiredModules {
# --- OS 종류 확인 ---
# ProductType 1이 일반, 그 외(2,3) 서버. 편의성을 위해 1만으로 구분하여 판단.
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
$isClientOS = ($osInfo.ProductType -eq 1)
# 설치 명령어 정의
$installCommands = @{
"ActiveDirectory" = if ($isClientOS) {
"Add-WindowsCapability -Online -Name 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0'"
} else {
"Install-WindowsFeature -Name 'RSAT-AD-PowerShell' -IncludeAllSubFeature"
}
"Microsoft.Graph" = "Install-Module Microsoft.Graph -Repository PSGallery -Force -AllowClobber -Scope AllUsers -Confirm:`$false"
}
$missingModules = @()
# --- 1. ActiveDirectory 기능(RSAT) 설치 여부 확인 ---
$adModuleInstalled = $false
try {
if ($isClientOS) {
Write-Host "일반 OS 감지. 'RSAT: Active Directory...' 기능 설치 여부 확인 중..."
$capability = Get-WindowsCapability -Online -Name 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0' -ErrorAction SilentlyContinue
if ($capability -and $capability.State -eq 'Installed') {
$adModuleInstalled = $true
}
} else {
Write-Host "서버 OS 감지. 'RSAT-AD-PowerShell' 기능 설치 여부 확인 중..."
$feature = Get-WindowsFeature -Name 'RSAT-AD-PowerShell' -ErrorAction SilentlyContinue
if ($feature -and $feature.Installed) {
$adModuleInstalled = $true
}
}
} catch {
Write-Warning "Active Directory 기능 확인 중 오류 발생: $($_.Exception.Message)"
}
if (-not $adModuleInstalled) {
$missingModules += "ActiveDirectory"
Write-Host "결과: Active Directory 관리 도구가 설치되지 않았습니다." -ForegroundColor Yellow
} else {
Write-Host "결과: Active Directory 관리 도구가 이미 설치되어 있습니다." -ForegroundColor Green
}
# --- 2. Microsoft.Graph 모듈 설치 여부 확인 ---
Write-Host "'Microsoft.Graph' 모듈 설치 여부 확인 중..."
if (-not (Get-Module -ListAvailable -Name "Microsoft.Graph")) {
$missingModules += "Microsoft.Graph"
Write-Host "결과: Microsoft.Graph 모듈이 설치되지 않았습니다." -ForegroundColor Yellow
} else {
Write-Host "결과: Microsoft.Graph 모듈이 이미 설치되어 있습니다." -ForegroundColor Green
}
# --- 3. 누락된 모듈/기능이 있으면 설치 진행 ---
if ($missingModules.Count -gt 0) {
$message = "필수 구성 요소가 설치되지 않았습니다:`n`n- $($missingModules -join "`n- ")`n`n지금 설치하시겠습니까? (시간이 소요될 수 있습니다)"
$response = [System.Windows.Forms.MessageBox]::Show($message, "필수 구성 요소 설치", "YesNo", "Question")
if ($response -eq "Yes") {
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
[System.Windows.Forms.MessageBox]::Show("설치에는 관리자 권한이 필요합니다. PowerShell을 '관리자 권한으로 실행'한 후 다시 시도해주세요.", "권한 필요", "OK", "Warning") | Out-Null
return $false
}
foreach ($moduleToInstall in $missingModules) {
Write-Host "'$moduleToInstall' 설치 시작..."
if ($moduleToInstall -eq "ActiveDirectory" -and $isClientOS) {
Write-Host "Windows 기능 설치 중입니다. PC 환경에 따라 수 분 이상 소요될 수 있습니다. 잠시만 기다려주세요..." -ForegroundColor Yellow
}
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-Expression -Command $installCommands[$moduleToInstall] -ErrorAction Stop
Write-Host "'$moduleToInstall' 설치 성공!" -ForegroundColor Green
} catch {
[System.Windows.Forms.MessageBox]::Show("'$moduleToInstall' 자동 설치 실패: $($_.Exception.Message)`n`nPowerShell을 관리자 권한으로 실행했는지 확인해주세요.", "설치 실패", "OK", "Error") | Out-Null
return $false
}
}
[System.Windows.Forms.MessageBox]::Show("설치가 완료되었습니다. 스크립트를 다시 시작해주세요.", "설치 완료", "OK", "Information") | Out-Null
Exit
} else {
Write-Warning "설치를 취소하여 종료합니다."
return $false
}
}
# --- 4. 설치된 모듈 현재 세션으로 가져오기 ---
try {
Import-Module ActiveDirectory -ErrorAction Stop
Import-Module Microsoft.Graph.Authentication -ErrorAction Stop
Import-Module Microsoft.Graph.Users -ErrorAction Stop
Import-Module Microsoft.Graph.Identity.DirectoryManagement -ErrorAction SilentlyContinue
return $true
} catch {
[System.Windows.Forms.MessageBox]::Show("모듈 로드 오류: $($_.Exception.Message)", "오류", "OK", "Error") | Out-Null
return $false
}
}
# 상세 오류 정보 대화상자 표시 함수
# 예외 발생 시, 사용자에게 기본 오류 메시지와 함께 상세 정보를 볼 수 있는 대화상자를 표시
function Show-DetailedErrorDialog {
Param(
[Parameter(Mandatory = $true)]
[System.Management.Automation.ErrorRecord]$ErrorRecord,
[string]$Message = "작업 중 오류 발생"
)
$errorForm = New-Object System.Windows.Forms.Form
$errorForm.Text = "오류"
$errorForm.Size = New-Object System.Drawing.Size(500, 200)
$errorForm.FormBorderStyle = "FixedDialog"
$errorForm.StartPosition = "CenterParent"
$iconLabel = New-Object System.Windows.Forms.Label
$iconLabel.Image = [System.Drawing.SystemIcons]::Error.ToBitmap()
$iconLabel.Location = New-Object System.Drawing.Point(20, 20)
$iconLabel.Size = New-Object System.Drawing.Size(32, 32)
$msgLabel = New-Object System.Windows.Forms.Label
$msgLabel.Text = "$Message`n$($ErrorRecord.Exception.Message)"
$msgLabel.Location = New-Object System.Drawing.Point(60, 20)
$msgLabel.Size = New-Object System.Drawing.Size(420, 60)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Text = "확인"
$okButton.Location = New-Object System.Drawing.Point(210, 120)
$okButton.Size = New-Object System.Drawing.Size(80, 25)
$okButton.DialogResult = "OK"
$detailsButton = New-Object System.Windows.Forms.Button
$detailsButton.Text = "자세히(&D) >>"
$detailsButton.Location = New-Object System.Drawing.Point(380, 120)
$detailsButton.Size = New-Object System.Drawing.Size(100, 25)
$detailsTextBox = New-Object System.Windows.Forms.TextBox
$detailsTextBox.Multiline = $true
$detailsTextBox.ScrollBars = "Both"
$detailsTextBox.ReadOnly = $true
$detailsTextBox.Text = $ErrorRecord.ToString()
$detailsTextBox.Location = New-Object System.Drawing.Point(20, 160)
$detailsTextBox.Size = New-Object System.Drawing.Size(460, 150)
$detailsTextBox.Visible = $false
$detailsButton.Add_Click({
if ($detailsTextBox.Visible) {
$errorForm.Height = 200
$detailsTextBox.Visible = $false
$detailsButton.Text = "자세히(&D) >>"
}
else {
$errorForm.Height = 360
$detailsTextBox.Visible = $true
$detailsButton.Text = "<< 간단히(&L)"
}
})
$errorForm.Controls.AddRange(@($iconLabel, $msgLabel, $okButton, $detailsButton, $detailsTextBox)) | Out-Null
$errorForm.AcceptButton = $okButton
$errorForm.ShowDialog($script:mainForm) | Out-Null
$errorForm.Dispose()
}
# --- 로그 기록 함수 ---
# 메시지를 화면(콘솔), UI(RichTextBox), 파일 세 곳에 동시에 기록
function Write-Log {
Param(
[string]$Message,
[ValidateSet("INFO", "WARNING", "ERROR", "SUCCESS")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp [$Level]: $Message"
# 1. 콘솔에 출력
Write-Host $logEntry
# 2. UI의 RichTextBox에 출력
if ($script:richTextBoxLog -and $script:richTextBoxLog.IsHandleCreated -and -not $script:richTextBoxLog.IsDisposed) {
$script:richTextBoxLog.AppendText("$logEntry`r`n")
$script:richTextBoxLog.ScrollToCaret()
[System.Windows.Forms.Application]::DoEvents()
}
# 3. 로그 파일에 기록
try {
Add-Content -Path $script:logFilePath -Value $logEntry -Encoding UTF8
}
catch {
Write-Warning "로그 파일($script:logFilePath) 쓰기 실패: $($_.Exception.Message)"
}
}
# --- 동기식 작업 실행 함수 ---
# 시간이 걸리는 작업을 실행하는 동안 UI가 멈추지 않도록 처리하고, 작업 상태를 표시
function Invoke-Synchronous {
Param(
[Parameter(Mandatory = $true)]
[ScriptBlock]$ScriptBlock,
[System.Object]$TriggerControl,
[string]$StatusMessage = "작업 중...",
[switch]$RequiresAzureAD
)
# Azure AD 연결이 필요한 작업인지 확인
if ($RequiresAzureAD -and -not (Get-MgContext)) {
Write-Log "Azure AD 연결이 필요합니다. 연결을 시도합니다." -Level "WARNING"
if (-not (Connect-AzureAD-WithInteraction)) {
Write-Log "Azure AD 연결이 취소되었거나 실패하여 작업을 중단합니다." -Level "ERROR"
return $null
}
}
# 작업 시작 전 UI 상태 설정
if ($TriggerControl) {
$TriggerControl.Enabled = $false
}
$originalCursor = $script:mainForm.Cursor
$script:mainForm.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
$script:statusLabelJob.Text = $StatusMessage
Write-Log $StatusMessage
[System.Windows.Forms.Application]::DoEvents()
try {
# 전달받은 스크립트 블록 실행
return (& $ScriptBlock)
}
catch {
# 오류 발생 시 로그 기록 및 상세 오류 대화상자 표시
Write-Log "작업 실패: $($_.Exception.Message)" -Level "ERROR"
Show-DetailedErrorDialog -ErrorRecord $_
return $null
}
finally {
# 작업 완료 후 UI 상태 복원
if ($TriggerControl -and $TriggerControl.IsHandleCreated) {
$TriggerControl.Enabled = $true
}
if ($script:mainForm.IsHandleCreated) {
$script:mainForm.Cursor = $originalCursor
}
$script:statusLabelJob.Text = ""
}
}
# --- SamAccountName 유효성 검사 함수 ---
# 계정명의 유효성(형식, 길이)과 중복 여부(On-Prem AD, Azure AD)를 검사
function Test-SamAccountName {
param(
[string]$AccountName,
[switch]$CheckAzureAD
)
if ([string]::IsNullOrWhiteSpace($AccountName)) {
return @{ IsValid = $false; Reason = "계정명은 비워둘 수 없습니다." }
}
if ($AccountName -notmatch '^[a-z0-9]+$') {
return @{ IsValid = $false; Reason = "계정명은 소문자 영문과 숫자만 사용할 수 있습니다." }
}
if ($AccountName.Length -lt 3) {
return @{ IsValid = $false; Reason = "계정명은 3자 이상이어야 합니다." }
}
if ($AccountName.Length -gt 20) {
return @{ IsValid = $false; Reason = "계정명은 20자를 초과할 수 없습니다." }
}
# 1. On-Prem AD 중복 확인
try {
if (Get-ADUser -Filter "SamAccountName -eq '$AccountName'" -Server $script:Configuration.OnPremDomainController) {
return @{ IsValid = $false; Reason = "이미 사용 중인 계정명입니다 (On-Prem AD)." }
}
}
catch {
# DC 연결 실패 등 예외 발생 시, 검사를 통과시키되 경고성 메시지를 반환
return @{ IsValid = $true; Reason = "On-Prem AD 유효성 검사 중 오류 발생 (DC 확인 불가)" }
}
# 2. Azure AD 중복 확인 (스위치가 제공된 경우)
if ($CheckAzureAD) {
if (-not (Get-MgContext)) {
return @{ IsValid = $true; Reason = "Azure AD 연결이 안 되어 UPN 중복 확인을 건너뜁니다." }
}
$upn = "$($AccountName)@$($script:Configuration.UPNSuffix)"
try {
$azureUser = Get-MgUser -Filter "userPrincipalName eq '$upn'" -ErrorAction Stop
if ($azureUser) {
return @{ IsValid = $false; Reason = "Azure AD에 동일한 UPN의 계정이 이미 존재합니다. 하드 매칭을 사용하세요." }
}
}
catch {
return @{ IsValid = $true; Reason = "Azure AD UPN 확인 중 오류 발생." }
}
}
return @{ IsValid = $true; Reason = "사용 가능한 계정명입니다." }
}
# --- 상태 표시줄 아이콘 생성 함수 ---
# 상태 표시줄에 사용할 동그란 색상 아이콘을 생성
function New-StatusIcon {
Param([System.Drawing.Color]$Color)
$bmp = New-Object System.Drawing.Bitmap(16, 16)
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$brush = New-Object System.Drawing.SolidBrush($Color)
$graphics.FillEllipse($brush, 2, 2, 12, 12)
$graphics.Dispose()
$brush.Dispose()
return $bmp
}
# --- 설정 대화상자 표시 함수 ---
# 프로그램 시작 시 사용자에게 주요 설정 값을 확인하고 수정할 수 있는 대화상자를 표시
function Show-ConfigurationDialog {
$form = New-Object System.Windows.Forms.Form
$form.Text = "스크립트 설정 확인"
$form.Size = New-Object System.Drawing.Size(550, 250)
$form.StartPosition = "CenterParent"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$controls = @{}
$yPos = 20
$labels = @{
OnPremDomainController = "온프레미스 DC:"
AADConnectServerName = "AAD Connect 서버:"
AzureTenantId = "Azure AD 테넌트 ID:"
UPNSuffix = "UPN 접미사:"
SynchronizedOUs = "동기화 OU (세미콜론; 구분):"
}
foreach ($key in $labels.Keys) {
$lbl = New-Object System.Windows.Forms.Label
$lbl.Text = $labels[$key]
$lbl.Location = New-Object System.Drawing.Point(20, $yPos)
$lbl.Size = New-Object System.Drawing.Size(200, 20)
$form.Controls.Add($lbl) | Out-Null
$txt = New-Object System.Windows.Forms.TextBox
$txt.Location = New-Object System.Drawing.Point(230, $yPos)
$txt.Size = New-Object System.Drawing.Size(280, 20)
if ($script:Configuration -and $script:Configuration.PSObject.Properties.Name -contains $key) {
$txt.Text = $script:Configuration.$key
}
$form.Controls.Add($txt) | Out-Null
$controls[$key] = $txt
$yPos += 30
}
$ok = New-Object System.Windows.Forms.Button
$ok.Text = "시작"
$ok.DialogResult = "OK"
$ok.Location = New-Object System.Drawing.Point(170, ($yPos + 10))
$ok.Size = New-Object System.Drawing.Size(90, 30)
$form.AcceptButton = $ok
$form.Controls.Add($ok) | Out-Null
$cancel = New-Object System.Windows.Forms.Button
$cancel.Text = "취소"
$cancel.DialogResult = "Cancel"
$cancel.Location = New-Object System.Drawing.Point(270, ($yPos + 10))
$cancel.Size = New-Object System.Drawing.Size(90, 30)
$form.CancelButton = $cancel
$form.Controls.Add($cancel) | Out-Null
$res = $form.ShowDialog($script:mainForm)
if ($res -eq [System.Windows.Forms.DialogResult]::OK) {
foreach ($key in $labels.Keys) {
# AAD Connect 서버 이름은 필수가 아님
if ([string]::IsNullOrWhiteSpace($controls[$key].Text) -and $key -ne 'AADConnectServerName') {
[System.Windows.Forms.MessageBox]::Show("'$($labels[$key])' 값은 비워둘 수 없습니다.", "입력 오류", "OK", "Warning") | Out-Null
return $false
}
$script:Configuration.$key = $controls[$key].Text.Trim()
}
try {
$script:Configuration | ConvertTo-Json -Depth 5 | Set-Content -Path $script:configFilePath -Encoding UTF8
Write-Log "설정 정보 저장 완료."
}
catch {
Show-DetailedErrorDialog -ErrorRecord $_ -Message "설정 파일 저장 오류"
}
return $true
}
return $false
}
# --- 스크립트 환경 초기화 함수 ---
# On-Premise AD에 연결하여 도메인 정보를 가져오고, 설정된 동기화 OU가 유효한지 확인
function Initialize-ScriptEnvironment {
Write-Log "스크립트 환경 초기화 시작."
try {
$script:CurrentADDomainInfo = Get-ADDomain -Server $script:Configuration.OnPremDomainController -ErrorAction Stop
if ($script:CurrentADDomainInfo) {
$script:CurrentADDomainDN = $script:CurrentADDomainInfo.DistinguishedName
Write-Log "온프레미스 AD 도메인 정보 로드 성공: $($script:CurrentADDomainInfo.Name)"
$script:statusLabelOnPrem.Image = $script:iconGreen
$script:statusLabelOnPrem.Text = "On-Prem DC: $($script:Configuration.OnPremDomainController)"
# 설정에 있는 동기화 OU 목록을 배열로 변환
$ouNames = $script:Configuration.SynchronizedOUs -split ';' | ForEach-Object { $_.Trim() }
$script:SynchronizedOURoots = @()
# 각 OU 이름에 대해 AD에서 실제 DistinguishedName을 찾아 저장
foreach ($ouName in $ouNames) {
try {
$ouObject = Get-ADOrganizationalUnit -Filter "Name -eq '$ouName'" -SearchBase $script:CurrentADDomainDN -SearchScope Subtree -Server $script:Configuration.OnPremDomainController -ErrorAction Stop
if ($ouObject) {
$ouObject | ForEach-Object {
$script:SynchronizedOURoots += $_.DistinguishedName
Write-Log "동기화 OU 루트 추가: $($_.DistinguishedName)"
}
}
else {
Write-Log "설정에 명시된 OU '$ouName'을(를) AD에서 찾을 수 없습니다." -Level WARNING
}
}
catch {
Write-Log "설정에 명시된 OU '$ouName'을(를) 찾는 중 오류 발생: $($_.Exception.Message)" -Level WARNING
}
}
return $true
}
else {
throw "Get-ADDomain cmdlet이 도메인 정보를 반환하지 않았습니다."
}
}
catch {
$msg = "치명적 오류: 온프레미스 AD 도메인 정보를 가져올 수 없습니다."
Write-Log $msg -Level "ERROR"
$script:statusLabelOnPrem.Image = $script:iconRed
$script:statusLabelOnPrem.Text = "On-Prem DC: 연결 오류"
return $false
}
}
# --- Azure AD 자동 연결 함수 ---
# 캐시된 자격 증명을 사용하여 사용자 개입 없이 Azure AD(Microsoft Graph)에 연결 시도
function Connect-AzureAD-Silently {
Write-Log "Azure AD 자동 연결을 시도합니다..."
$context = try {
Connect-MgGraph -TenantId $script:Configuration.AzureTenantId
Get-MgContext
}
catch {
$null
}
if ($context) {
Write-Log "Azure AD 자동 연결 성공."
$script:statusLabelAzure.Image = $script:iconGreen
$script:statusLabelAzure.Text = "Azure AD: 연결됨 ($($context.Account))"
Update-LicenseList
}
else {
Write-Log "캐시된 토큰이 없거나 만료되었습니다. 수동 연결이 필요합니다." -Level "WARNING"
$script:statusLabelAzure.Image = $script:iconRed
$script:statusLabelAzure.Text = "Azure AD: 수동 연결 필요"
}
}
# --- Azure AD 수동 연결 함수 ---
# 사용자에게 로그인 창을 표시하여 Azure AD(Microsoft Graph)에 대화형으로 연결(2단계 인증 고려)
function Connect-AzureAD-WithInteraction {
$context = Invoke-Synchronous -TriggerControl $script:connectAzureADMenu -StatusMessage "Azure AD에 연결하는 중..." -ScriptBlock {
$requiredScopes = @("User.Read.All", "User.ReadWrite.All", "Directory.Read.All", "Organization.Read.All")
Connect-MgGraph -TenantId $script:Configuration.AzureTenantId -Scopes $requiredScopes
return Get-MgContext
}
if ($context) {
Write-Log "Azure AD 연결 성공."
$script:statusLabelAzure.Image = $script:iconGreen
$script:statusLabelAzure.Text = "Azure AD: 연결됨 ($($context.Account))"
Update-LicenseList
return $true
}
else {
Write-Log "Azure AD 연결 실패 또는 취소됨." -Level "ERROR"
$script:statusLabelAzure.Image = $script:iconRed
$script:statusLabelAzure.Text = "Azure AD: 연결 오류"
return $false
}
}
# --- M365 라이선스 목록 갱신 함수 ---
# Azure AD에서 구독 중인 SKU 정보를 가져와 '계정 생성' 탭의 라이선스 목록 업데이트
function Update-LicenseList {
$licenses = Invoke-Synchronous -TriggerControl $null -StatusMessage "라이선스 목록 조회 중..." -ScriptBlock {
Get-MgSubscribedSku -All | Where-Object { $_.CapabilityStatus -eq "Enabled" }
} -RequiresAzureAD
if ($licenses) {
$script:add_listBoxLicenses.Items.Clear()
# 라이선스를 할당하지 않는 옵션을 기본으로 추가
$noLicense = [PSCustomObject]@{
DisplayName = "(라이선스 할당 안함)"
SkuId = $null
SkuObject = $null
}
$script:add_listBoxLicenses.Items.Add($noLicense) | Out-Null
# 사용 가능한 라이선스를 목록에 추가 (남은 수량 표시)
$licenses | Where-Object { -not [string]::IsNullOrWhiteSpace($_.SkuPartNumber) } | Sort-Object SkuPartNumber | ForEach-Object {
$skuItem = $_
$availableUnits = ($skuItem.PrepaidUnits.Enabled - $skuItem.ConsumedUnits)
$licenseDisplayName = "$($skuItem.SkuPartNumber) (남음: $availableUnits)"
$licenseObject = [PSCustomObject]@{
DisplayName = $licenseDisplayName
SkuId = $skuItem.SkuId
SkuObject = $skuItem
}
$script:add_listBoxLicenses.Items.Add($licenseObject) | Out-Null
}
$script:add_listBoxLicenses.DisplayMember = "DisplayName"
Write-Log "[계정 생성] 라이선스 목록 갱신 완료."
}
}
# --- AAD Connect 동기화 실행 함수 ---
# 로컬 또는 원격 AAD Connect 서버에서 동기화 주기를 시작하는 명령 실행
function Invoke-AadConnectSync {
Param ([string]$PolicyType = "Delta")
$syncSuccess = Invoke-Synchronous -TriggerControl $null -StatusMessage "AAD Connect 동기화 실행 중..." -ScriptBlock {
$server = $script:Configuration.AADConnectServerName
# 서버 이름이 없으면 로컬에서 실행, 있으면 원격으로 실행
if ([string]::IsNullOrWhiteSpace($server)) {
Import-Module ADSync -ErrorAction SilentlyContinue
Start-ADSyncSyncCycle -PolicyType $PolicyType
}
else {
Invoke-Command -ComputerName $server -ScriptBlock {
param($p)
Import-Module ADSync -ErrorAction SilentlyContinue
Start-ADSyncSyncCycle -PolicyType $p
} -ArgumentList $PolicyType -ErrorAction Stop
}
return $true # 성공 시 true 반환
}
if ($syncSuccess) {
Write-Log "AAD Connect 동기화 명령 전송 완료. 클라우드 적용까지 시간이 소요될 수 있습니다." -Level SUCCESS
}
else {
Write-Log "AAD Connect 동기화 명령 실행에 실패했습니다." -Level ERROR
}
}
# --- OU TreeView 빌드 함수 (재귀) ---
# AD의 OU 구조를 바탕으로 UI의 TreeView 컨트롤을 재귀적 구성
function Build-OU-TreeView {
param (
[string]$ParentDN,
[System.Windows.Forms.TreeNode]$ParentUiNode,
[hashtable]$OUHierarchy
)
if (-not $OUHierarchy.ContainsKey($ParentDN)) {
return
}
foreach ($ouObject in $OUHierarchy[$ParentDN] | Sort-Object Name) {
$uiNode = New-Object System.Windows.Forms.TreeNode($ouObject.Name)
$uiNode.Tag = $ouObject.DistinguishedName
$ParentUiNode.Nodes.Add($uiNode) | Out-Null
# 자식 OU에 대해 재귀 호출
Build-OU-TreeView -ParentDN $ouObject.DistinguishedName -ParentUiNode $uiNode -OUHierarchy $OUHierarchy
}
}
# --- '계정 생성' 탭 초기화 함수 ---
# 계정 생성 탭의 모든 입력 필드를 초기 상태로 되돌리기
function Reset-AddUserTab {
$script:add_textBoxLastNameKr.Text = ""
$script:add_textBoxFirstNameKr.Text = ""
$script:add_textBoxAccountNameEn.Text = ""
$script:add_textBoxSelectedOU.Text = ""
$script:add_checkedListBoxServicePlans.Items.Clear()
$script:add_labelOUSyncStatus.Text = "OU를 선택하면 동기화 상태를 안내합니다."
$script:add_labelOUSyncStatus.ForeColor = [System.Drawing.Color]::Black
$script:add_pictureBoxAccountValidation.Visible = $false
if ($script:add_treeViewOUs.Nodes.Count -gt 0 -and $script:add_treeViewOUs.SelectedNode) {
$script:add_treeViewOUs.SelectedNode = $null
}
Update-LicenseList
}
# --- '계정 삭제' 탭 초기화 함수 ---
# 계정 삭제 탭의 모든 입력 필드와 목록을 초기 상태로 되돌리기
function Reset-DeleteUserTab {
$script:del_textBoxSearchUser.Text = ""
$script:del_listBoxFoundUsers.Items.Clear()
$script:del_textBoxFoundUserDisplay.Text = "표시 이름:"
$script:del_textBoxFoundUserUPN.Text = "UPN:"
$script:del_deleteQueue.Clear()
$script:del_listBoxDeleteQueue.Items.Clear()
$script:del_buttonExecuteDelete.Enabled = $false
$script:del_buttonAddToDeleteList.Enabled = $false
}
# --- '하드 매칭' 탭 초기화 함수 ---
# 하드 매칭 탭의 모든 입력 필드와 상태 표시를 초기 상태로 되돌리기
function Reset-HardMatchTab {
$script:hm_textBoxSearchAzure.Text = ""
$script:hm_listBoxFoundAzure.Items.Clear()
$script:hm_textBoxAzureUser.Text = ""
$script:hm_textBoxImmutableId.Text = ""
$script:hm_labelGuidMatchStatus.Visible = $false
$script:hm_textBoxOnPremUser.Text = ""
$script:hm_textBoxObjectGuid.Text = ""
$script:hm_textBoxConvertedImmutableId.Text = ""
if ($script:hm_textBoxOnPremUser.Parent) {
$script:hm_textBoxOnPremUser.Parent.Visible = $false
}
$script:hm_textBoxFirstNameKr.Text = ""
$script:hm_textBoxLastNameKr.Text = ""
$script:hm_textBoxSelectedOU.Text = ""
if ($script:hm_textBoxSelectedOU) {
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::White
}
if ($script:hm_treeViewOUs) {
$script:hm_treeViewOUs.Nodes.Clear()
}
if ($script:hm_groupCreate) {
$script:hm_groupCreate.Visible = $false
}
$script:hm_buttonExecuteAction.Enabled = $false
$script:hm_buttonExecuteAction.Text = "작업 실행"
}
#endregion
Write-Host "Common.ps1 로드 완료." -ForegroundColor Cyan