# ================================================================================ # 파일: Scripts/UI-Tab-AddUser.ps1 # 역할: '계정 생성' 탭 UI 및 기능 구현 # # 작성자: 양범진 # 버전: 1.13 # 생성일자: 2025-06-05 # 최종 수정일자: 2025-06-12 # # 설명: # - '계정 생성' 탭의 UI 컨트롤들을 생성하고 배치 # - 사용자 정보 입력, OU 선택, 라이선스 할당 등 계정 생성과 관련된 모든 로직 처리 # - 코드 가독성 및 유지보수 편의성을 위해 전체적으로 포매팅 및 주석 재정리 # ================================================================================ #region '계정 생성' 탭 UI 초기화 함수 # --- '계정 생성' 탭의 모든 UI 컨트롤을 생성하고 배치하는 메인 함수 --- function Initialize-AddUserTab { Param([System.Windows.Forms.TabPage]$parentTab) # 메인 레이아웃: 전체 탭을 2열 3행으로 구성 $addTabMainLayout = New-Object System.Windows.Forms.TableLayoutPanel $addTabMainLayout.Dock = "Fill" $addTabMainLayout.Padding = [System.Windows.Forms.Padding](10) $addTabMainLayout.ColumnCount = 2 $addTabMainLayout.RowCount = 3 $addTabMainLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null $addTabMainLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null $addTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 80))) | Out-Null $addTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $addTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 60))) | Out-Null $parentTab.Controls.Add($addTabMainLayout) | Out-Null # 상단: 작업 모드 선택 (AD/Azure AD 동기화 vs On-Premise AD만 생성) $groupMode = New-Object System.Windows.Forms.GroupBox $groupMode.Text = "작업 모드 선택" $groupMode.Dock = "Fill" $addTabMainLayout.Controls.Add($groupMode, 0, 0) | Out-Null $addTabMainLayout.SetColumnSpan($groupMode, 2) $script:add_radioSync = New-Object System.Windows.Forms.RadioButton $script:add_radioSync.Text = "AD/Azure AD 동기화 및 라이선스 할당" $script:add_radioSync.Location = New-Object System.Drawing.Point(25, 30) $script:add_radioSync.AutoSize = $true $script:add_radioSync.Checked = $true $groupMode.Controls.Add($script:add_radioSync) | Out-Null $script:add_radioOnPremOnly = New-Object System.Windows.Forms.RadioButton $script:add_radioOnPremOnly.Text = "온프레미스 AD에만 생성" $script:add_radioOnPremOnly.Location = New-Object System.Drawing.Point(450, 30) $script:add_radioOnPremOnly.AutoSize = $true $groupMode.Controls.Add($script:add_radioOnPremOnly) | Out-Null # 왼쪽 패널: 사용자 정보 입력 및 OU 선택 영역 $leftPanelLayout = New-Object System.Windows.Forms.TableLayoutPanel $leftPanelLayout.Dock = "Fill" $leftPanelLayout.RowCount = 2 $leftPanelLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 180))) | Out-Null $leftPanelLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $addTabMainLayout.Controls.Add($leftPanelLayout, 0, 1) | Out-Null # 왼쪽 패널 (상단): 사용자 정보 입력 그룹박스 $groupUserInfo = New-Object System.Windows.Forms.GroupBox $groupUserInfo.Text = "1. 사용자 정보" $groupUserInfo.Dock = "Fill" $leftPanelLayout.Controls.Add($groupUserInfo, 0, 0) | Out-Null $userInfoLayout = New-Object System.Windows.Forms.TableLayoutPanel $userInfoLayout.Dock = "Fill" $userInfoLayout.Padding = [System.Windows.Forms.Padding](10) $userInfoLayout.ColumnCount = 2 $userInfoLayout.RowCount = 4 $userInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 110))) | Out-Null $userInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $rowHeight = 35 $userInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null $userInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null $userInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null $userInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null $groupUserInfo.Controls.Add($userInfoLayout) | Out-Null # 사용자 정보 입력 컨트롤들 (한글 성/이름, 영문 계정명, 비밀번호) $labelLastNameKr = New-Object System.Windows.Forms.Label $labelLastNameKr.Text = "한글 성:" $labelLastNameKr.Dock = "Fill" $labelLastNameKr.TextAlign = "MiddleLeft" $script:add_textBoxLastNameKr = New-Object System.Windows.Forms.TextBox $script:add_textBoxLastNameKr.Dock = "Fill" $labelFirstNameKr = New-Object System.Windows.Forms.Label $labelFirstNameKr.Text = "한글 이름:" $labelFirstNameKr.Dock = "Fill" $labelFirstNameKr.TextAlign = "MiddleLeft" $script:add_textBoxFirstNameKr = New-Object System.Windows.Forms.TextBox $script:add_textBoxFirstNameKr.Dock = "Fill" $labelAccountNameEn = New-Object System.Windows.Forms.Label $labelAccountNameEn.Text = "영문 계정명:" $labelAccountNameEn.Dock = "Fill" $labelAccountNameEn.TextAlign = "MiddleLeft" # 영문 계정명 입력란 + 유효성 검사 아이콘 패널 $accountNamePanel = New-Object System.Windows.Forms.TableLayoutPanel $accountNamePanel.Dock = "Fill" $accountNamePanel.ColumnCount = 2 $accountNamePanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $accountNamePanel.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 22))) | Out-Null $accountNamePanel.Margin = [System.Windows.Forms.Padding](0) $script:add_textBoxAccountNameEn = New-Object System.Windows.Forms.TextBox $script:add_textBoxAccountNameEn.Dock = "Fill" $script:add_pictureBoxAccountValidation = New-Object System.Windows.Forms.PictureBox $script:add_pictureBoxAccountValidation.Dock = "Fill" $script:add_pictureBoxAccountValidation.SizeMode = "CenterImage" $script:add_pictureBoxAccountValidation.Visible = $false $script:add_toolTip = New-Object System.Windows.Forms.ToolTip $accountNamePanel.Controls.AddRange(@($script:add_textBoxAccountNameEn, $script:add_pictureBoxAccountValidation)) | Out-Null $labelPassword = New-Object System.Windows.Forms.Label $labelPassword.Text = "초기 비밀번호:" $labelPassword.Dock = "Fill" $labelPassword.TextAlign = "MiddleLeft" $script:add_textBoxPassword = New-Object System.Windows.Forms.TextBox $script:add_textBoxPassword.Dock = "Fill" $script:add_textBoxPassword.Text = $script:Configuration.DefaultPassword $userInfoLayout.Controls.Add($labelLastNameKr, 0, 0) | Out-Null $userInfoLayout.Controls.Add($script:add_textBoxLastNameKr, 1, 0) | Out-Null $userInfoLayout.Controls.Add($labelFirstNameKr, 0, 1) | Out-Null $userInfoLayout.Controls.Add($script:add_textBoxFirstNameKr, 1, 1) | Out-Null $userInfoLayout.Controls.Add($labelAccountNameEn, 0, 2) | Out-Null $userInfoLayout.Controls.Add($accountNamePanel, 1, 2) | Out-Null $userInfoLayout.Controls.Add($labelPassword, 0, 3) | Out-Null $userInfoLayout.Controls.Add($script:add_textBoxPassword, 1, 3) | Out-Null # 왼쪽 패널 (하단): OU 선택 그룹박스 $groupOU = New-Object System.Windows.Forms.GroupBox $groupOU.Text = "2. 대상 OU 선택" $groupOU.Dock = "Fill" $leftPanelLayout.Controls.Add($groupOU, 0, 1) | Out-Null $ouLayout = New-Object System.Windows.Forms.TableLayoutPanel $ouLayout.Dock = "Fill" $ouLayout.Padding = [System.Windows.Forms.Padding](10) $ouLayout.ColumnCount = 2 $ouLayout.RowCount = 3 $ouLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $ouLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 40))) | Out-Null $ouLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 35))) | Out-Null $ouLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null $ouLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 25))) | Out-Null $groupOU.Controls.Add($ouLayout) | Out-Null # OU 선택 컨트롤들 (선택된 OU 표시, 새로고침 버튼, TreeView, 동기화 상태 라벨) $script:add_textBoxSelectedOU = New-Object System.Windows.Forms.TextBox $script:add_textBoxSelectedOU.Dock = "Fill" $script:add_textBoxSelectedOU.ReadOnly = $true $script:add_textBoxSelectedOU.Text = "아래에서 OU를 선택하세요." $buttonRefreshOUs = New-Object System.Windows.Forms.Button $buttonRefreshOUs.Text = "🔄" $buttonRefreshOUs.Dock = "Fill" $buttonRefreshOUs.Font = New-Object System.Drawing.Font("Segoe UI Emoji", 10) $script:add_treeViewOUs = New-Object System.Windows.Forms.TreeView $script:add_treeViewOUs.Dock = "Fill" $script:add_treeViewOUs.HideSelection = $false $script:add_labelOUSyncStatus = New-Object System.Windows.Forms.Label $script:add_labelOUSyncStatus.Dock = "Fill" $script:add_labelOUSyncStatus.TextAlign = "MiddleLeft" $script:add_labelOUSyncStatus.Text = "OU를 선택하면 동기화 상태를 안내합니다." $ouLayout.Controls.Add($script:add_textBoxSelectedOU, 0, 0) | Out-Null $ouLayout.Controls.Add($buttonRefreshOUs, 1, 0) | Out-Null $ouLayout.Controls.Add($script:add_treeViewOUs, 0, 1) | Out-Null $ouLayout.SetColumnSpan($script:add_treeViewOUs, 2) $ouLayout.Controls.Add($script:add_labelOUSyncStatus, 0, 2) | Out-Null $ouLayout.SetColumnSpan($script:add_labelOUSyncStatus, 2) # 오른쪽 패널: M365 라이선스 및 서비스 플랜 할당 영역 $script:add_groupLicense = New-Object System.Windows.Forms.GroupBox $script:add_groupLicense.Text = "3. M365 라이선스 및 서비스 플랜 할당" $script:add_groupLicense.Dock = "Fill" $addTabMainLayout.Controls.Add($script:add_groupLicense, 1, 1) | Out-Null $licenseLayout = New-Object System.Windows.Forms.TableLayoutPanel $licenseLayout.Dock = "Fill" $licenseLayout.Padding = [System.Windows.Forms.Padding](10) $licenseLayout.RowCount = 2 $licenseLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null $licenseLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null $script:add_groupLicense.Controls.Add($licenseLayout) | Out-Null # 라이선스 목록 및 서비스 플랜 목록 컨트롤 $script:add_listBoxLicenses = New-Object System.Windows.Forms.ListBox $script:add_listBoxLicenses.Dock = "Fill" $script:add_checkedListBoxServicePlans = New-Object System.Windows.Forms.CheckedListBox $script:add_checkedListBoxServicePlans.Dock = "Fill" $script:add_checkedListBoxServicePlans.CheckOnClick = $true $licenseLayout.Controls.Add($script:add_listBoxLicenses, 0, 0) | Out-Null $licenseLayout.Controls.Add($script:add_checkedListBoxServicePlans, 0, 1) | Out-Null # 하단: 계정 생성 실행 버튼 $script:add_buttonCreate = New-Object System.Windows.Forms.Button $script:add_buttonCreate.Text = "계정 생성 실행(&R)" $script:add_buttonCreate.Dock = "Fill" $script:add_buttonCreate.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold) $addTabMainLayout.Controls.Add($script:add_buttonCreate, 0, 2) | Out-Null $addTabMainLayout.SetColumnSpan($script:add_buttonCreate, 2) # 이벤트 핸들러 연결 $script:add_radioSync.add_CheckedChanged($add_ModeChangedHandler) $script:add_radioOnPremOnly.add_CheckedChanged($add_ModeChangedHandler) $script:add_textBoxAccountNameEn.Add_TextChanged($add_AccountNameEn_TextChanged) $script:add_textBoxAccountNameEn.Add_Leave($add_AccountNameEn_Leave) $buttonRefreshOUs.Add_Click({ Update-OU-TreeView -TriggerControl $buttonRefreshOUs }) $script:add_treeViewOUs.Add_AfterSelect($add_treeViewOUs_AfterSelect) $script:add_listBoxLicenses.Add_SelectedIndexChanged($add_listBoxLicenses_SelectedIndexChanged) $script:add_buttonCreate.Add_Click($add_buttonCreate_Click) } #endregion #region '계정 생성' 탭 이벤트 핸들러 및 함수 # --- OU TreeView를 로드 및 갱신하는 함수 --- function Update-OU-TreeView { Param($TriggerControl) $allOUs = Invoke-Synchronous -TriggerControl $TriggerControl -StatusMessage "[계정 생성] OU 목록을 조회합니다..." -ScriptBlock { Get-ADOrganizationalUnit -Filter * -SearchBase $script:CurrentADDomainDN -SearchScope Subtree -Server $script:Configuration.OnPremDomainController -ErrorAction SilentlyContinue } if ($allOUs) { # OU 목록을 부모-자식 관계의 해시테이블로 재구성 $ouHierarchy = @{} foreach ($ou in $allOUs) { $parentDN = $ou.DistinguishedName -replace '^OU=[^,]+,', '' if (-not $ouHierarchy.ContainsKey($parentDN)) { $ouHierarchy[$parentDN] = [System.Collections.ArrayList]::new() } $ouHierarchy[$parentDN].Add($ou) | Out-Null } # TreeView 업데이트 시작 (UI 깜빡임 방지) $script:add_treeViewOUs.BeginUpdate() $script:add_treeViewOUs.Nodes.Clear() # 최상위 루트 노드 추가 $rootNode = New-Object System.Windows.Forms.TreeNode($script:CurrentADDomainInfo.DnsRoot) $rootNode.Tag = $script:CurrentADDomainDN $script:add_treeViewOUs.Nodes.Add($rootNode) | Out-Null # 재귀 함수를 통해 OU 계층 구조 빌드 Build-OU-TreeView -ParentDN $script:CurrentADDomainDN -ParentUiNode $rootNode -OUHierarchy $ouHierarchy $rootNode.Expand() # 로드 완료 후 선택된 노드 해제 if ($script:add_treeViewOUs.SelectedNode) { $script:add_treeViewOUs.SelectedNode = $null } $script:add_treeViewOUs.EndUpdate() Write-Log "[계정 생성] OU 목록 로드/갱신 완료." } } # --- 작업 모드 변경 이벤트 핸들러 --- # 라디오 버튼 선택에 따라 라이선스 할당 영역의 활성화 여부와 실행 버튼 텍스트 변경 $add_ModeChangedHandler = { param($src, $e) if (-not $src.Checked) { return } $isSyncMode = $script:add_radioSync.Checked $script:add_groupLicense.Enabled = $isSyncMode if ($isSyncMode) { $script:add_buttonCreate.Text = "계정 생성 및 할당 실행(&R)" } else { $script:add_buttonCreate.Text = "온프레미스 계정 생성 실행(&R)" } } # --- 영문 계정명 텍스트 변경 이벤트 핸들러 --- # 사용자가 계정명을 입력할 때 유효성 검사 아이콘을 '확인 중' 상태로 표시 $add_AccountNameEn_TextChanged = { $accountName = $script:add_textBoxAccountNameEn.Text if ([string]::IsNullOrWhiteSpace($accountName)) { $script:add_pictureBoxAccountValidation.Visible = $false return } $script:add_pictureBoxAccountValidation.Image = $script:iconGreen $script:add_toolTip.SetToolTip($script:add_pictureBoxAccountValidation, "확인 중...") $script:add_pictureBoxAccountValidation.Visible = $true } # --- 영문 계정명 입력란 포커스 잃음 이벤트 핸들러 --- # 입력이 완료되면 계정명의 유효성과 중복 여부를 검사하고 결과를 아이콘과 툴팁으로 표시 $add_AccountNameEn_Leave = { $accountName = $script:add_textBoxAccountNameEn.Text if ([string]::IsNullOrWhiteSpace($accountName)) { $script:add_pictureBoxAccountValidation.Visible = $false return } # Azure AD 동기화 모드일 때만 Azure AD 중복 확인을 수행 $isSyncMode = $script:add_radioSync.Checked $validationResult = Test-SamAccountName -AccountName $accountName -CheckAzureAD:$isSyncMode if ($validationResult.IsValid) { $script:add_pictureBoxAccountValidation.Image = $script:iconGreen } else { $script:add_pictureBoxAccountValidation.Image = $script:iconRed } $script:add_toolTip.SetToolTip($script:add_pictureBoxAccountValidation, $validationResult.Reason) $script:add_pictureBoxAccountValidation.Visible = $true } # --- OU TreeView 노드 선택 이벤트 핸들러 --- # OU를 선택하면 해당 OU의 DN을 텍스트박스에 표시하고, 동기화 대상 여부를 확인하여 상태 라벨에 표시 $add_treeViewOUs_AfterSelect = { param($source, $e) if ($e.Node -ne $null -and $e.Node.Tag) { $selectedOUDN = $e.Node.Tag.ToString() # 루트 도메인은 선택 불가 처리 if ($selectedOUDN -eq $script:CurrentADDomainDN) { $script:add_textBoxSelectedOU.Text = "OU를 선택해야 합니다." $script:add_labelOUSyncStatus.Text = "루트 도메인은 선택할 수 없습니다." $script:add_labelOUSyncStatus.ForeColor = [System.Drawing.Color]::Red return } $script:add_textBoxSelectedOU.Text = $selectedOUDN $isSynchronized = $false # 설정된 동기화 OU 목록과 비교 foreach ($syncRootOU in $script:SynchronizedOURoots) { if ($selectedOUDN -eq $syncRootOU -or $selectedOUDN.EndsWith(",$syncRootOU", "OrdinalIgnoreCase")) { $isSynchronized = $true break } } if ($isSynchronized) { $script:add_labelOUSyncStatus.Text = "선택된 OU는 동기화 대상입니다." $script:add_labelOUSyncStatus.ForeColor = [System.Drawing.Color]::Green } else { $script:add_labelOUSyncStatus.Text = "주의: 선택된 OU는 동기화 대상이 아닐 수 있습니다!" $script:add_labelOUSyncStatus.ForeColor = [System.Drawing.Color]::Red } } } # --- 라이선스 목록 선택 이벤트 핸들러 --- # 라이선스를 선택하면 해당 라이선스에 포함된 서비스 플랜 목록을 가져와 아래쪽 체크리스트에 표시 $add_listBoxLicenses_SelectedIndexChanged = { $script:add_checkedListBoxServicePlans.Items.Clear() $selectedItem = $script:add_listBoxLicenses.SelectedItem # "(라이선스 할당 안함)"을 선택했거나 선택된 항목이 없으면 함수 종료 if ($null -eq $selectedItem -or $null -eq $selectedItem.SkuId) { return } Write-Log "'$($selectedItem.DisplayName)' 라이선스의 서비스 플랜을 조회합니다." $allPlans = $selectedItem.SkuObject.ServicePlans | Sort-Object ServicePlanName foreach ($plan in $allPlans) { # 모든 플랜을 기본적으로 체크된 상태로 추가 $script:add_checkedListBoxServicePlans.Items.Add($plan, $true) | Out-Null } $script:add_checkedListBoxServicePlans.DisplayMember = "ServicePlanName" } # --- '계정 생성 실행' 버튼 클릭 이벤트 핸들러 --- # 입력된 정보를 바탕으로 실제 계정 생성 작업을 수행하는 메인 로직 $add_buttonCreate_Click = { # 1. 필수 값 확인 $lastNameKr = $script:add_textBoxLastNameKr.Text.Trim() $firstNameKr = $script:add_textBoxFirstNameKr.Text.Trim() $accountNameEn = $script:add_textBoxAccountNameEn.Text.Trim() $targetOUDN = $script:add_textBoxSelectedOU.Text.Trim() $passwordInput = $script:add_textBoxPassword.Text $isSyncMode = $script:add_radioSync.Checked if (-not ($lastNameKr -and $firstNameKr -and $accountNameEn -and $passwordInput)) { [System.Windows.Forms.MessageBox]::Show("사용자 정보(한글 성/이름, 영문 계정명)와 비밀번호는 필수 입력 항목입니다.", "입력 오류", "OK", "Warning") | Out-Null return } if ($targetOUDN -eq "아래에서 OU를 선택하세요." -or $targetOUDN -eq "OU를 선택해야 합니다." -or [string]::IsNullOrWhiteSpace($targetOUDN)) { [System.Windows.Forms.MessageBox]::Show("대상 OU를 선택해야 합니다.", "입력 오류", "OK", "Warning") | Out-Null return } # 생성 직전 최종 유효성 검사 (Azure AD 중복 확인 포함) $validationResult = Test-SamAccountName -AccountName $accountNameEn -CheckAzureAD:$isSyncMode if (-not $validationResult.IsValid) { $message = "계정명 유효성 검사를 통과하지 못했습니다.`n사유: $($validationResult.Reason)" [System.Windows.Forms.MessageBox]::Show($message, "입력 오류", "OK", "Warning") | Out-Null return } # 2. 메인 작업 실행 (Invoke-Synchronous 래퍼 함수 사용) $success = Invoke-Synchronous -TriggerControl $this -StatusMessage "계정 생성 작업을 실행합니다..." -ScriptBlock { # 2.1. On-Premise AD 사용자 생성 $userPrincipalName = "$accountNameEn@$($script:Configuration.UPNSuffix)" $commonName = "$lastNameKr$firstNameKr [$accountNameEn]" Write-Log "온프레미스 AD 사용자 생성을 시작합니다: $accountNameEn" New-ADUser -SamAccountName $accountNameEn ` -UserPrincipalName $userPrincipalName ` -Name $commonName ` -DisplayName $commonName ` -GivenName $firstNameKr ` -Surname $lastNameKr ` -Path $targetOUDN ` -AccountPassword (ConvertTo-SecureString $passwordInput -AsPlainText -Force) ` -Enabled $true ` -ChangePasswordAtLogon $false ` -EmailAddress $userPrincipalName ` -Server $script:Configuration.OnPremDomainController ` -PassThru ` -ErrorAction Stop Write-Log "온프레미스 AD 사용자 '$accountNameEn' 생성 성공." if (-not $isSyncMode) { return $true } # On-Premise 전용 모드일 경우 여기서 작업 종료 # 2.2. 동기화 실행 및 Azure AD에서 사용자 확인 Invoke-AadConnectSync -PolicyType Delta Write-Log "Azure AD에서 사용자 '$userPrincipalName' 확인 시도 (최대 120초 대기)..." $azureAdUser = $null $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() while ($stopwatch.Elapsed.TotalSeconds -lt 120) { Write-Log "Azure AD에서 사용자 확인 중... ($([math]::Round($stopwatch.Elapsed.TotalSeconds))초 경과)" $azureAdUser = Get-MgUser -Filter "userPrincipalName eq '$userPrincipalName'" -ErrorAction SilentlyContinue if ($azureAdUser) { break } Start-Sleep -Seconds 10 } $stopwatch.Stop() if (-not $azureAdUser) { throw "제한 시간(120초) 내에 Azure AD에서 사용자를 찾지 못했습니다." } $elapsedSeconds = [math]::Round($stopwatch.Elapsed.TotalSeconds) Write-Log "사용자 '$userPrincipalName'을(를) Azure AD에서 찾았습니다! (소요 시간: ${elapsedSeconds}초)" -Level SUCCESS # 2.3. 사용 위치(Usage Location) 및 라이선스 할당 Write-Log "사용 위치 설정 중: $($script:Configuration.DefaultUsageLocation)" Update-MgUser -UserId $azureAdUser.UserPrincipalName -UsageLocation $script:Configuration.DefaultUsageLocation -ErrorAction Stop $selectedLicenseItem = $script:add_listBoxLicenses.SelectedItem if ($selectedLicenseItem -and $selectedLicenseItem.SkuId) { Write-Log "라이선스 할당 중: $($selectedLicenseItem.DisplayName)" $disabledPlans = [System.Collections.Generic.List[string]]::new() # 체크 해제된 서비스 플랜 목록을 구성 for ($i = 0; $i -lt $script:add_checkedListBoxServicePlans.Items.Count; $i++) { if (-not $script:add_checkedListBoxServicePlans.GetItemChecked($i)) { $servicePlanId = $script:add_checkedListBoxServicePlans.Items[$i].ServicePlanId $disabledPlans.Add($servicePlanId) } } # 라이선스 할당 API 호출 $licenseBody = @{ AddLicenses = @(@{SkuId = $selectedLicenseItem.SkuId; DisabledPlans = $disabledPlans}) RemoveLicenses = @() } Set-MgUserLicense -UserId $azureAdUser.UserPrincipalName -BodyParameter $licenseBody -ErrorAction Stop Write-Log "라이선스 및 서비스 플랜 할당 완료." } return $true } -RequiresAzureAD:$isSyncMode # 3. 최종 결과 처리 if ($success) { [System.Windows.Forms.MessageBox]::Show("모든 작업이 성공적으로 완료되었습니다.", "작업 완료", "OK", "Information") | Out-Null Reset-AddUserTab } } #endregion Write-Host "UI-Tab-AddUser.ps1 로드 완료." -ForegroundColor Cyan