530 lines
28 KiB
PowerShell
530 lines
28 KiB
PowerShell
# ================================================================================
|
|
# 파일: Scripts/UI-Tab-HardMatch.ps1
|
|
# 역할: '하드 매칭' 탭 UI 및 기능 구현 (리팩토링 최종 완전판)
|
|
#
|
|
# 작성자: 양범진
|
|
# 버전: 1.13
|
|
# 생성일자: 2025-06-05
|
|
# 최종 수정일자: 2025-06-12
|
|
#
|
|
# 설명:
|
|
# - '3. 작업 실행' 영역의 레이아웃을 2열 구조로 변경하여 UI 잘림 문제 해결.
|
|
# - 하드 매칭으로 신규 계정 생성 시, SamAccountName 중복 확인 로직 추가.
|
|
# ================================================================================
|
|
|
|
#region '하드 매칭' 탭 UI 초기화 함수
|
|
function Initialize-HardMatchTab {
|
|
Param(
|
|
[System.Windows.Forms.TabPage]$parentTab
|
|
)
|
|
|
|
# 탭 페이지의 메인 레이아웃 (3행 1열)
|
|
$hmTabMainLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$hmTabMainLayout.Dock = "Fill"
|
|
$hmTabMainLayout.Padding = [System.Windows.Forms.Padding](10)
|
|
$hmTabMainLayout.ColumnCount = 1
|
|
$hmTabMainLayout.RowCount = 3
|
|
$hmTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 160))) | Out-Null
|
|
$hmTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 160))) | Out-Null
|
|
$hmTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$parentTab.Controls.Add($hmTabMainLayout) | Out-Null
|
|
|
|
# 1. Azure AD 사용자 검색 그룹박스
|
|
$groupSearchAzure = New-Object System.Windows.Forms.GroupBox
|
|
$groupSearchAzure.Text = "1. Azure AD 사용자 검색 (Cloud-Only 또는 동기화 오류 사용자)"
|
|
$groupSearchAzure.Dock = "Fill"
|
|
$hmTabMainLayout.Controls.Add($groupSearchAzure, 0, 0) | Out-Null
|
|
|
|
$searchAzureLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$searchAzureLayout.Dock = "Fill"
|
|
$searchAzureLayout.Padding = [System.Windows.Forms.Padding](10)
|
|
$searchAzureLayout.ColumnCount = 2
|
|
$searchAzureLayout.RowCount = 2
|
|
$searchAzureLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$searchAzureLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 150))) | Out-Null
|
|
$searchAzureLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 35))) | Out-Null
|
|
$searchAzureLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$groupSearchAzure.Controls.Add($searchAzureLayout) | Out-Null
|
|
|
|
$script:hm_textBoxSearchAzure = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxSearchAzure.Dock = "Fill"
|
|
|
|
$buttonSearchAzure = New-Object System.Windows.Forms.Button
|
|
$buttonSearchAzure.Text = "Azure AD 검색(&Z)"
|
|
$buttonSearchAzure.Dock = "Fill"
|
|
|
|
$script:hm_listBoxFoundAzure = New-Object System.Windows.Forms.ListBox
|
|
$script:hm_listBoxFoundAzure.Dock = "Fill"
|
|
|
|
$searchAzureLayout.Controls.Add($script:hm_textBoxSearchAzure, 0, 0) | Out-Null
|
|
$searchAzureLayout.Controls.Add($buttonSearchAzure, 1, 0) | Out-Null
|
|
$searchAzureLayout.Controls.Add($script:hm_listBoxFoundAzure, 0, 1) | Out-Null
|
|
$searchAzureLayout.SetColumnSpan($script:hm_listBoxFoundAzure, 2)
|
|
|
|
# 2. 사용자 정보 확인 그룹박스
|
|
$groupInfo = New-Object System.Windows.Forms.GroupBox
|
|
$groupInfo.Text = "2. 사용자 정보 확인"
|
|
$groupInfo.Dock = "Fill"
|
|
$hmTabMainLayout.Controls.Add($groupInfo, 0, 1) | Out-Null
|
|
|
|
$infoLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$infoLayout.Dock = "Fill"
|
|
$infoLayout.ColumnCount = 2
|
|
$infoLayout.RowCount = 1
|
|
$infoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
|
|
$infoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
|
|
$groupInfo.Controls.Add($infoLayout) | Out-Null
|
|
|
|
# 2-1. Azure AD 사용자 정보
|
|
$groupAzure = New-Object System.Windows.Forms.GroupBox
|
|
$groupAzure.Text = "Azure AD 사용자"
|
|
$groupAzure.Dock = "Fill"
|
|
$infoLayout.Controls.Add($groupAzure, 0, 0) | Out-Null
|
|
|
|
$script:hm_textBoxAzureUser = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxAzureUser.Location = New-Object System.Drawing.Point(15, 25)
|
|
$script:hm_textBoxAzureUser.Width = $groupAzure.ClientSize.Width - 30
|
|
$script:hm_textBoxAzureUser.Anchor = 'Top, Left, Right'
|
|
$script:hm_textBoxAzureUser.ReadOnly = $true
|
|
|
|
$script:hm_textBoxImmutableId = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxImmutableId.Location = New-Object System.Drawing.Point(15, 55)
|
|
$script:hm_textBoxImmutableId.Width = $groupAzure.ClientSize.Width - 30
|
|
$script:hm_textBoxImmutableId.Anchor = 'Top, Left, Right'
|
|
$script:hm_textBoxImmutableId.ReadOnly = $true
|
|
|
|
$script:hm_labelGuidMatchStatus = New-Object System.Windows.Forms.Label
|
|
$script:hm_labelGuidMatchStatus.Location = New-Object System.Drawing.Point(15, 85)
|
|
$script:hm_labelGuidMatchStatus.Width = $groupAzure.ClientSize.Width - 30
|
|
$script:hm_labelGuidMatchStatus.Anchor = 'Top, Left, Right'
|
|
$script:hm_labelGuidMatchStatus.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
|
|
$script:hm_labelGuidMatchStatus.TextAlign = "MiddleLeft"
|
|
$script:hm_labelGuidMatchStatus.Visible = $false
|
|
|
|
$groupAzure.Controls.AddRange(@(
|
|
$script:hm_textBoxAzureUser,
|
|
$script:hm_textBoxImmutableId,
|
|
$script:hm_labelGuidMatchStatus
|
|
)) | Out-Null
|
|
|
|
# 2-2. 온프레미스 AD 사용자 정보
|
|
$groupOnPrem = New-Object System.Windows.Forms.GroupBox
|
|
$groupOnPrem.Text = "온프레미스 AD 사용자"
|
|
$groupOnPrem.Dock = "Fill"
|
|
$groupOnPrem.Visible = $false
|
|
$infoLayout.Controls.Add($groupOnPrem, 1, 0) | Out-Null
|
|
|
|
$script:hm_textBoxOnPremUser = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxOnPremUser.Location = New-Object System.Drawing.Point(15, 25)
|
|
$script:hm_textBoxOnPremUser.Width = $groupOnPrem.ClientSize.Width - 30
|
|
$script:hm_textBoxOnPremUser.Anchor = 'Top, Left, Right'
|
|
$script:hm_textBoxOnPremUser.ReadOnly = $true
|
|
|
|
$script:hm_textBoxObjectGuid = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxObjectGuid.Location = New-Object System.Drawing.Point(15, 55)
|
|
$script:hm_textBoxObjectGuid.Width = $groupOnPrem.ClientSize.Width - 30
|
|
$script:hm_textBoxObjectGuid.Anchor = 'Top, Left, Right'
|
|
$script:hm_textBoxObjectGuid.ReadOnly = $true
|
|
|
|
$script:hm_textBoxConvertedImmutableId = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxConvertedImmutableId.Location = New-Object System.Drawing.Point(15, 85)
|
|
$script:hm_textBoxConvertedImmutableId.Width = $groupOnPrem.ClientSize.Width - 30
|
|
$script:hm_textBoxConvertedImmutableId.Anchor = 'Top, Left, Right'
|
|
$script:hm_textBoxConvertedImmutableId.ReadOnly = $true
|
|
|
|
$groupOnPrem.Controls.AddRange(@(
|
|
$script:hm_textBoxOnPremUser,
|
|
$script:hm_textBoxObjectGuid,
|
|
$script:hm_textBoxConvertedImmutableId
|
|
)) | Out-Null
|
|
|
|
# 3. 작업 실행 그룹박스
|
|
$groupAction = New-Object System.Windows.Forms.GroupBox
|
|
$groupAction.Text = "3. 작업 실행"
|
|
$groupAction.Dock = "Fill"
|
|
$hmTabMainLayout.Controls.Add($groupAction, 0, 2) | Out-Null
|
|
|
|
# '작업 실행' 그룹박스 내부의 외부 레이아웃 (컨텐츠 영역 + 실행 버튼)
|
|
$actionOuterLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$actionOuterLayout.Dock = "Fill"
|
|
$actionOuterLayout.ColumnCount = 1
|
|
$actionOuterLayout.RowCount = 2
|
|
$actionOuterLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$actionOuterLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) | Out-Null
|
|
$groupAction.Controls.Add($actionOuterLayout) | Out-Null
|
|
|
|
# '계정 생성' 옵션이 표시될 패널 (기본적으로 숨김)
|
|
$script:hm_groupCreate = New-Object System.Windows.Forms.Panel
|
|
$script:hm_groupCreate.Dock = "Fill"
|
|
$script:hm_groupCreate.Visible = $false
|
|
$actionOuterLayout.Controls.Add($script:hm_groupCreate, 0, 0) | Out-Null
|
|
|
|
# '계정 생성' 패널 내부 레이아웃 (2열)
|
|
$createInnerLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$createInnerLayout.Dock = "Fill"
|
|
$createInnerLayout.ColumnCount = 2
|
|
$createInnerLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
|
|
$createInnerLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
|
|
$script:hm_groupCreate.Controls.Add($createInnerLayout) | Out-Null
|
|
|
|
# 3-1. 신규 온프레미스 계정 정보 입력 영역
|
|
$groupCreateInfo = New-Object System.Windows.Forms.GroupBox
|
|
$groupCreateInfo.Text = "신규 온프레미스 계정 정보"
|
|
$groupCreateInfo.Dock = "Fill"
|
|
$createInnerLayout.Controls.Add($groupCreateInfo, 0, 0) | Out-Null
|
|
$createInnerLayout.SetRowSpan($groupCreateInfo, 2)
|
|
|
|
$hmCreateInfoLayout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$hmCreateInfoLayout.Dock = "Fill"
|
|
$hmCreateInfoLayout.Padding = [System.Windows.Forms.Padding](10)
|
|
$hmCreateInfoLayout.ColumnCount = 2
|
|
$hmCreateInfoLayout.RowCount = 3
|
|
$hmCreateInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 100))) | Out-Null
|
|
$hmCreateInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$rowHeight = 35
|
|
$hmCreateInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null
|
|
$hmCreateInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null
|
|
$hmCreateInfoLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, $rowHeight))) | Out-Null
|
|
$groupCreateInfo.Controls.Add($hmCreateInfoLayout) | Out-Null
|
|
|
|
$labelHMLastNameKr = New-Object System.Windows.Forms.Label
|
|
$labelHMLastNameKr.Text = "한글 성:"
|
|
$labelHMLastNameKr.Dock = "Fill"
|
|
$labelHMLastNameKr.TextAlign = "MiddleLeft"
|
|
|
|
$script:hm_textBoxLastNameKr = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxLastNameKr.Dock = "Fill"
|
|
|
|
$labelHMFirstNameKr = New-Object System.Windows.Forms.Label
|
|
$labelHMFirstNameKr.Text = "한글 이름:"
|
|
$labelHMFirstNameKr.Dock = "Fill"
|
|
$labelHMFirstNameKr.TextAlign = "MiddleLeft"
|
|
|
|
$script:hm_textBoxFirstNameKr = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxFirstNameKr.Dock = "Fill"
|
|
|
|
$labelHMPassword = New-Object System.Windows.Forms.Label
|
|
$labelHMPassword.Text = "초기 암호:"
|
|
$labelHMPassword.Dock = "Fill"
|
|
$labelHMPassword.TextAlign = "MiddleLeft"
|
|
|
|
$script:hm_textBoxPassword = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxPassword.Dock = "Fill"
|
|
$script:hm_textBoxPassword.Text = $script:Configuration.DefaultPassword
|
|
|
|
$hmCreateInfoLayout.Controls.Add($labelHMLastNameKr, 0, 0) | Out-Null
|
|
$hmCreateInfoLayout.Controls.Add($script:hm_textBoxLastNameKr, 1, 0) | Out-Null
|
|
$hmCreateInfoLayout.Controls.Add($labelHMFirstNameKr, 0, 1) | Out-Null
|
|
$hmCreateInfoLayout.Controls.Add($script:hm_textBoxFirstNameKr, 1, 1) | Out-Null
|
|
$hmCreateInfoLayout.Controls.Add($labelHMPassword, 0, 2) | Out-Null
|
|
$hmCreateInfoLayout.Controls.Add($script:hm_textBoxPassword, 1, 2) | Out-Null
|
|
|
|
# 3-2. 대상 OU 선택 영역
|
|
$groupCreateOU = New-Object System.Windows.Forms.GroupBox
|
|
$groupCreateOU.Text = "대상 OU 선택"
|
|
$groupCreateOU.Dock = "Fill"
|
|
$createInnerLayout.Controls.Add($groupCreateOU, 1, 0) | Out-Null
|
|
$createInnerLayout.SetRowSpan($groupCreateOU, 2)
|
|
|
|
# OU 그룹박스 내부 레이아웃 (선택된 OU 표시 + TreeView)
|
|
$hmCreateOU_Layout = New-Object System.Windows.Forms.TableLayoutPanel
|
|
$hmCreateOU_Layout.Dock = "Fill"
|
|
$hmCreateOU_Layout.Padding = [System.Windows.Forms.Padding](10)
|
|
$hmCreateOU_Layout.ColumnCount = 1
|
|
$hmCreateOU_Layout.RowCount = 2
|
|
$hmCreateOU_Layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 35))) | Out-Null
|
|
$hmCreateOU_Layout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
|
|
$groupCreateOU.Controls.Add($hmCreateOU_Layout) | Out-Null
|
|
|
|
$script:hm_textBoxSelectedOU = New-Object System.Windows.Forms.TextBox
|
|
$script:hm_textBoxSelectedOU.Dock = "Fill"
|
|
$script:hm_textBoxSelectedOU.ReadOnly = $true
|
|
|
|
$script:hm_treeViewOUs = New-Object System.Windows.Forms.TreeView
|
|
$script:hm_treeViewOUs.Dock = "Fill"
|
|
$script:hm_treeViewOUs.HideSelection = $false
|
|
|
|
$hmCreateOU_Layout.Controls.Add($script:hm_textBoxSelectedOU, 0, 0) | Out-Null
|
|
$hmCreateOU_Layout.Controls.Add($script:hm_treeViewOUs, 0, 1) | Out-Null
|
|
|
|
# 최종 실행 버튼
|
|
$script:hm_buttonExecuteAction = New-Object System.Windows.Forms.Button
|
|
$script:hm_buttonExecuteAction.Text = "작업 실행"
|
|
$script:hm_buttonExecuteAction.Dock = "Fill"
|
|
$script:hm_buttonExecuteAction.Enabled = $false
|
|
$script:hm_buttonExecuteAction.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold)
|
|
$actionOuterLayout.Controls.Add($script:hm_buttonExecuteAction, 0, 1) | Out-Null
|
|
|
|
# 이벤트 핸들러 연결
|
|
$buttonSearchAzure.Add_Click($hm_buttonSearchAzure_Click)
|
|
$script:hm_listBoxFoundAzure.Add_SelectedIndexChanged($hm_listBoxFoundAzure_SelectedIndexChanged)
|
|
$script:hm_listBoxFoundAzure.Add_DoubleClick({
|
|
if ($script:hm_buttonExecuteAction.Enabled) {
|
|
$script:hm_buttonExecuteAction.PerformClick()
|
|
}
|
|
})
|
|
$script:hm_treeViewOUs.Add_AfterSelect($hm_treeViewOUs_AfterSelect)
|
|
$script:hm_buttonExecuteAction.Add_Click($hm_buttonExecuteAction_Click)
|
|
}
|
|
#endregion
|
|
|
|
#region '하드 매칭' 탭 이벤트 핸들러
|
|
|
|
# Azure AD 사용자 검색 버튼 클릭 이벤트
|
|
$hm_buttonSearchAzure_Click = {
|
|
$searchTerm = $script:hm_textBoxSearchAzure.Text.Trim()
|
|
if (-not $searchTerm) {
|
|
return
|
|
}
|
|
|
|
# 비동기 호출로 Azure AD 사용자 검색
|
|
$users = Invoke-Synchronous -TriggerControl $this -StatusMessage "Azure AD의 모든 사용자를 조회하여 필터링합니다..." -ScriptBlock {
|
|
$allAzureUsers = Get-MgUser -All -Property "Id,DisplayName,UserPrincipalName,onPremisesImmutableId,Mail,GivenName,Surname" -ErrorAction Stop
|
|
return $allAzureUsers | Where-Object {
|
|
$_.DisplayName -like "*$searchTerm*" -or
|
|
$_.UserPrincipalName -like "*$searchTerm*" -or
|
|
$_.Mail -like "*$searchTerm*"
|
|
}
|
|
} -RequiresAzureAD
|
|
|
|
# 검색 결과를 리스트박스에 추가
|
|
$script:hm_listBoxFoundAzure.BeginUpdate()
|
|
$script:hm_listBoxFoundAzure.Items.Clear()
|
|
if ($users) {
|
|
foreach ($user in @($users) | Sort-Object DisplayName) {
|
|
$item = [PSCustomObject]@{
|
|
DisplayText = "$($user.DisplayName) ($($user.UserPrincipalName))"
|
|
AzureObject = $user
|
|
}
|
|
$script:hm_listBoxFoundAzure.Items.Add($item) | Out-Null
|
|
}
|
|
}
|
|
$script:hm_listBoxFoundAzure.DisplayMember = "DisplayText"
|
|
$script:hm_listBoxFoundAzure.EndUpdate()
|
|
Write-Log "총 $(@($users).Count)명의 사용자를 찾았습니다."
|
|
}
|
|
|
|
# 검색된 Azure AD 사용자 리스트에서 항목 선택 시 이벤트
|
|
$hm_listBoxFoundAzure_SelectedIndexChanged = {
|
|
$selectedItem = $this.SelectedItem
|
|
if ($null -eq $selectedItem) {
|
|
return
|
|
}
|
|
|
|
# UI 상태 초기화
|
|
$groupOnPrem = $script:hm_textBoxOnPremUser.Parent
|
|
$groupCreate = $script:hm_groupCreate
|
|
$groupOnPrem.Visible = $false
|
|
$groupCreate.Visible = $false
|
|
$script:hm_buttonExecuteAction.Enabled = $false
|
|
$script:hm_labelGuidMatchStatus.Visible = $false
|
|
|
|
# 선택된 Azure AD 사용자 정보 표시
|
|
$script:hm_selectedAzureUser = $selectedItem.AzureObject
|
|
$script:hm_textBoxAzureUser.Text = "$($script:hm_selectedAzureUser.DisplayName) ($($script:hm_selectedAzureUser.UserPrincipalName))"
|
|
$script:hm_textBoxImmutableId.Text = "ImmutableId: $($script:hm_selectedAzureUser.onPremisesImmutableId)"
|
|
|
|
# UPN을 기준으로 온프레미스 AD에서 동일한 SamAccountName을 가진 사용자 검색
|
|
$samAccountName = $script:hm_selectedAzureUser.UserPrincipalName.Split('@')[0]
|
|
$onPremUser = Invoke-Synchronous -TriggerControl $this -StatusMessage "온프레미스에서 '$samAccountName' 확인 중..." -ScriptBlock {
|
|
Get-ADUser -Filter "SamAccountName -eq '$samAccountName'" -Properties objectGUID, DisplayName, SamAccountName -Server $script:Configuration.OnPremDomainController -ErrorAction SilentlyContinue
|
|
}
|
|
$script:hm_foundOnPremUser = $onPremUser
|
|
|
|
# 분기 처리: 온프레미스에 계정이 있는 경우 vs 없는 경우
|
|
if ($script:hm_foundOnPremUser) {
|
|
# [CASE 1] 온프레미스 계정이 존재할 경우: 정보 표시 및 상태 진단
|
|
$groupOnPrem.Visible = $true
|
|
$script:hm_groupCreate.Visible = $false
|
|
$script:hm_textBoxOnPremUser.Text = "$($script:hm_foundOnPremUser.DisplayName) (sAM: $($script:hm_foundOnPremUser.SamAccountName))"
|
|
$script:hm_textBoxObjectGuid.Text = "ObjectGUID: $($script:hm_foundOnPremUser.objectGUID)"
|
|
|
|
$immutableIdFromGuid = [System.Convert]::ToBase64String($script:hm_foundOnPremUser.objectGUID.ToByteArray())
|
|
$script:hm_textBoxConvertedImmutableId.Text = "변환된 ImmutableId: $immutableIdFromGuid"
|
|
$script:hm_labelGuidMatchStatus.Visible = $true
|
|
|
|
# ImmutableId 값 비교를 통해 매칭 상태 진단
|
|
if ([string]::IsNullOrEmpty($script:hm_selectedAzureUser.onPremisesImmutableId)) {
|
|
$script:hm_labelGuidMatchStatus.Text = "⚠️ 매칭 필요 (Azure AD ImmutableId 없음)"
|
|
$script:hm_labelGuidMatchStatus.ForeColor = [System.Drawing.Color]::Orange
|
|
$script:hm_currentMode = "MatchExisting"
|
|
$script:hm_buttonExecuteAction.Text = "기존 계정과 하드 매칭 실행"
|
|
$script:hm_buttonExecuteAction.Enabled = $true
|
|
}
|
|
elseif ($script:hm_selectedAzureUser.onPremisesImmutableId -eq $immutableIdFromGuid) {
|
|
$script:hm_labelGuidMatchStatus.Text = "✅ ID 일치 (정상 동기화 상태)"
|
|
$script:hm_labelGuidMatchStatus.ForeColor = [System.Drawing.Color]::Green
|
|
$script:hm_currentMode = "Idle"
|
|
$script:hm_buttonExecuteAction.Text = "작업 불필요"
|
|
$script:hm_buttonExecuteAction.Enabled = $false
|
|
}
|
|
else {
|
|
$script:hm_labelGuidMatchStatus.Text = "❌ ID 불일치 (강제 매칭 필요)"
|
|
$script:hm_labelGuidMatchStatus.ForeColor = [System.Drawing.Color]::Red
|
|
$script:hm_currentMode = "MatchExisting"
|
|
$script:hm_buttonExecuteAction.Text = "기존 계정과 하드 매칭 실행 (덮어쓰기)"
|
|
$script:hm_buttonExecuteAction.Enabled = $true
|
|
}
|
|
} else {
|
|
# [CASE 2] 온프레미스 계정이 없을 경우: 신규 생성 및 매칭 준비
|
|
$groupCreate.Visible = $true
|
|
$script:hm_currentMode = "CreateAndMatch"
|
|
$script:hm_buttonExecuteAction.Text = "계정 생성 및 하드 매칭 실행"
|
|
$script:hm_buttonExecuteAction.Enabled = $true
|
|
|
|
# Azure AD 사용자 정보를 기반으로 생성 정보 자동 채우기
|
|
$script:hm_textBoxLastNameKr.Text = $script:hm_selectedAzureUser.Surname
|
|
$script:hm_textBoxFirstNameKr.Text = $script:hm_selectedAzureUser.GivenName
|
|
|
|
# OU 목록을 TreeView로 로드
|
|
$allOUs = Invoke-Synchronous -TriggerControl $this -StatusMessage "[하드 매칭] OU 목록을 조회합니다..." -ScriptBlock {
|
|
Get-ADOrganizationalUnit -Filter * -SearchBase $script:CurrentADDomainDN -SearchScope Subtree -Server $script:Configuration.OnPremDomainController -ErrorAction SilentlyContinue
|
|
}
|
|
if ($allOUs) {
|
|
$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
|
|
}
|
|
$script:hm_treeViewOUs.BeginUpdate()
|
|
$script:hm_treeViewOUs.Nodes.Clear()
|
|
$rootNode = New-Object System.Windows.Forms.TreeNode($script:CurrentADDomainInfo.DnsRoot)
|
|
$rootNode.Tag = $script:CurrentADDomainDN
|
|
$script:hm_treeViewOUs.Nodes.Add($rootNode) | Out-Null
|
|
Build-OU-TreeView -ParentDN $script:CurrentADDomainDN -ParentUiNode $rootNode -OUHierarchy $ouHierarchy
|
|
$rootNode.Expand()
|
|
$script:hm_treeViewOUs.EndUpdate()
|
|
}
|
|
}
|
|
}
|
|
|
|
# OU TreeView 에서 노드 선택 시 이벤트
|
|
$hm_treeViewOUs_AfterSelect = {
|
|
if ($this.SelectedNode -and $this.SelectedNode.Tag) {
|
|
$selectedOUDN = $this.SelectedNode.Tag.ToString()
|
|
$script:hm_textBoxSelectedOU.Text = $selectedOUDN
|
|
|
|
# 기본 배경색으로 초기화
|
|
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::White
|
|
|
|
# 루트 도메인은 생성 경로로 선택할 수 없음
|
|
if ($selectedOUDN -eq $script:CurrentADDomainDN) {
|
|
Write-Log "루트 도메인은 선택할 수 없습니다." -Level "WARNING"
|
|
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::LightGray
|
|
return
|
|
}
|
|
|
|
# 선택된 OU가 AD Connect 동기화 범위에 포함되는지 확인
|
|
$isSynchronized = $false
|
|
foreach ($syncRootOU in $script:SynchronizedOURoots) {
|
|
if ($selectedOUDN -eq $syncRootOU -or $selectedOUDN.EndsWith(",$syncRootOU", "OrdinalIgnoreCase")) {
|
|
$isSynchronized = $true
|
|
break
|
|
}
|
|
}
|
|
|
|
# 동기화 여부에 따라 배경색 변경으로 사용자에게 시각적 피드백 제공
|
|
if ($isSynchronized) {
|
|
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::LightGreen
|
|
} else {
|
|
Write-Log "주의: 선택된 OU($selectedOUDN)는 동기화 대상이 아닐 수 있습니다!" -Level "WARNING"
|
|
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::LightPink
|
|
}
|
|
} else {
|
|
# 선택 해제 시 텍스트박스 초기화
|
|
$script:hm_textBoxSelectedOU.Text = ""
|
|
$script:hm_textBoxSelectedOU.BackColor = [System.Drawing.Color]::White
|
|
}
|
|
}
|
|
|
|
# '작업 실행' 버튼 클릭 이벤트 (모든 하드 매칭 작업의 시작점)
|
|
$hm_buttonExecuteAction_Click = {
|
|
$isSyncMode = $script:hm_currentMode -ne "Idle"
|
|
$success = Invoke-Synchronous -TriggerControl $this -StatusMessage "하드 매칭 작업을 실행합니다..." -ScriptBlock {
|
|
if (-not $script:hm_selectedAzureUser) {
|
|
return $false
|
|
}
|
|
|
|
$onPremUserToMatch = $null
|
|
|
|
# 모드 1: 신규 계정 생성 후 매칭
|
|
if ($script:hm_currentMode -eq "CreateAndMatch") {
|
|
Write-Log "하드 매칭 모드: 신규 계정 생성 및 매칭"
|
|
$sam = $script:hm_selectedAzureUser.UserPrincipalName.Split('@')[0]
|
|
|
|
# SamAccountName 유효성 및 중복 검사
|
|
$validationResult = Test-SamAccountName -AccountName $sam
|
|
if (-not $validationResult.IsValid) {
|
|
if ($validationResult.Reason -like "*이미 사용 중인 계정명*") {
|
|
throw "온프레미스에 SamAccountName이 '$sam'인 계정이 이미 존재합니다. 해당 계정과 직접 매칭을 시도하거나, Azure AD 사용자의 UPN을 변경한 후 다시 시도하세요."
|
|
} else {
|
|
throw "생성하려는 계정명 '$sam'이 규칙에 맞지 않습니다: $($validationResult.Reason)"
|
|
}
|
|
}
|
|
|
|
$upn = "$sam@$($script:Configuration.UPNSuffix)"
|
|
$password = $script:hm_textBoxPassword.Text
|
|
$targetOU = $script:hm_textBoxSelectedOU.Text
|
|
if (-not ($password -and $targetOU)) {
|
|
throw "신규 생성 시 비밀번호와 대상 OU는 필수입니다."
|
|
}
|
|
|
|
# 신규 AD 사용자 생성을 위한 파라미터 준비
|
|
$newUserParams = @{
|
|
SamAccountName = $sam
|
|
UserPrincipalName = $upn
|
|
Name = $script:hm_selectedAzureUser.DisplayName
|
|
DisplayName = $script:hm_selectedAzureUser.DisplayName
|
|
GivenName = $script:hm_textBoxFirstNameKr.Text
|
|
Surname = $script:hm_textBoxLastNameKr.Text
|
|
Path = $targetOU
|
|
EmailAddress = $script:hm_selectedAzureUser.Mail
|
|
AccountPassword = (ConvertTo-SecureString $password -AsPlainText -Force)
|
|
Enabled = $true
|
|
}
|
|
$onPremUserToMatch = New-ADUser @newUserParams -PassThru -Server $script:Configuration.OnPremDomainController -ErrorAction Stop
|
|
Write-Log "온프레미스 AD 사용자 '$($onPremUserToMatch.Name)' 생성 성공."
|
|
|
|
# 모드 2: 기존 계정과 매칭
|
|
} elseif ($script:hm_currentMode -eq "MatchExisting") {
|
|
Write-Log "하드 매칭 모드: 기존 계정과 매칭"
|
|
$onPremUserToMatch = $script:hm_foundOnPremUser
|
|
} else {
|
|
throw "알 수 없는 작업 모드입니다: $($script:hm_currentMode)"
|
|
}
|
|
|
|
if (-not $onPremUserToMatch) {
|
|
throw "매칭할 온프레미스 AD 계정을 식별할 수 없습니다."
|
|
}
|
|
|
|
# 대상 온프레미스 계정의 ObjectGUID를 ImmutableId로 변환하여 Azure AD에 설정
|
|
$immutableId = [System.Convert]::ToBase64String((Get-ADUser -Identity $onPremUserToMatch.SamAccountName -Properties objectGUID).ObjectGUID.ToByteArray())
|
|
Write-Log "하드 매칭 실행: '$($script:hm_selectedAzureUser.UserPrincipalName)'의 ImmutableId를 '$immutableId'(으)로 설정합니다."
|
|
Set-MgUser -UserId $script:hm_selectedAzureUser.Id -OnPremisesImmutableId $immutableId -ErrorAction Stop
|
|
|
|
# 즉시 델타 동기화 실행
|
|
Invoke-AadConnectSync -PolicyType Delta
|
|
|
|
# 최종 검증: ImmutableId가 올바르게 업데이트되었는지 확인 (최대 30초 대기)
|
|
Write-Log "검증 단계: 업데이트된 ImmutableId를 다시 조회합니다 (최대 30초 대기)..."
|
|
for ($i = 0; $i -lt 3; $i++) {
|
|
Start-Sleep -Seconds 10
|
|
$refreshedUser = Get-MgUser -UserId $script:hm_selectedAzureUser.Id -Property "onPremisesImmutableId" -ErrorAction SilentlyContinue
|
|
if ($refreshedUser.onPremisesImmutableId -eq $immutableId) {
|
|
return $true
|
|
}
|
|
}
|
|
throw "검증 실패: ImmutableId가 업데이트되지 않았습니다."
|
|
} -RequiresAzureAD:$isSyncMode
|
|
|
|
if ($success) {
|
|
[System.Windows.Forms.MessageBox]::Show("하드 매칭 및 동기화, 최종 검증까지 성공적으로 완료되었습니다.", "모든 작업 성공", "OK", "Information") | Out-Null
|
|
Reset-HardMatchTab
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
Write-Host "UI-Tab-HardMatch.ps1 로드 완료." -ForegroundColor Cyan |