Files
adusermanager/Scripts/UI-Tab-DeleteUser.ps1

375 lines
19 KiB
PowerShell
Raw Normal View History

2025-09-15 13:46:19 +09:00
# ================================================================================
# 파일: Scripts/UI-Tab-DeleteUser.ps1
# 역할: '계정 삭제' 탭 UI 및 기능 구현
#
# 작성자: 양범진
# 버전: 1.13
# 생성일자: 2025-06-05
# 최종 수정일자: 2025-06-12
#
# 설명: 안정화된 동기 실행 방식에 맞춰 코드 수정 및 가독성 개선, PSScriptAnalyzer 경고 수정
# ================================================================================
#region '계정 삭제' 탭 UI 초기화 함수
function Initialize-DeleteUserTab {
Param(
[System.Windows.Forms.TabPage]$parentTab
)
# ==========================================================
# 탭 전체 레이아웃 설정
# ==========================================================
$delTabMainLayout = New-Object System.Windows.Forms.TableLayoutPanel
$delTabMainLayout.Dock = "Fill"
$delTabMainLayout.Padding = [System.Windows.Forms.Padding](10)
$delTabMainLayout.ColumnCount = 1
$delTabMainLayout.RowCount = 3
$delTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 80))) | Out-Null
$delTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 250))) | Out-Null
$delTabMainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
$parentTab.Controls.Add($delTabMainLayout) | Out-Null
# ==========================================================
# 1. 작업 모드 선택 그룹박스
# ==========================================================
$groupMode = New-Object System.Windows.Forms.GroupBox
$groupMode.Text = "작업 모드 선택"
$groupMode.Dock = "Fill"
$delTabMainLayout.Controls.Add($groupMode, 0, 0) | Out-Null
# '온프레미스 AD 삭제 및 Azure AD 동기화' 라디오 버튼
$script:del_radioSync = New-Object System.Windows.Forms.RadioButton
$script:del_radioSync.Text = "온프레미스 AD 삭제 및 Azure AD 동기화"
$script:del_radioSync.Location = New-Object System.Drawing.Point(25, 30)
$script:del_radioSync.AutoSize = $true
$script:del_radioSync.Checked = $true
# '온프레미스 AD에서만 삭제' 라디오 버튼
$script:del_radioOnPremOnly = New-Object System.Windows.Forms.RadioButton
$script:del_radioOnPremOnly.Text = "온프레미스 AD에서만 삭제 (동기화 안함)"
$script:del_radioOnPremOnly.Location = New-Object System.Drawing.Point(450, 30)
$script:del_radioOnPremOnly.AutoSize = $true
$groupMode.Controls.AddRange(@($script:del_radioSync, $script:del_radioOnPremOnly)) | Out-Null
# ==========================================================
# 2. 사용자 검색 및 선택 그룹박스
# ==========================================================
$groupSearch = New-Object System.Windows.Forms.GroupBox
$groupSearch.Text = "1. 삭제할 사용자 검색 및 선택"
$groupSearch.Dock = "Fill"
$delTabMainLayout.Controls.Add($groupSearch, 0, 1) | Out-Null
# 검색 그룹박스 내부 레이아웃
$searchLayout = New-Object System.Windows.Forms.TableLayoutPanel
$searchLayout.Dock = "Fill"
$searchLayout.Padding = [System.Windows.Forms.Padding](10)
$searchLayout.ColumnCount = 2
$searchLayout.RowCount = 4
$searchLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
$searchLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 150))) | Out-Null
$searchLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 35))) | Out-Null
$searchLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
$searchLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 35))) | Out-Null
$searchLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 45))) | Out-Null
$groupSearch.Controls.Add($searchLayout) | Out-Null
# 사용자 검색 텍스트박스 및 검색 버튼
$script:del_textBoxSearchUser = New-Object System.Windows.Forms.TextBox
$script:del_textBoxSearchUser.Dock = "Fill"
$buttonSearchUserOnPrem = New-Object System.Windows.Forms.Button
$buttonSearchUserOnPrem.Text = "온프레미스 검색(&S)"
$buttonSearchUserOnPrem.Dock = "Fill"
# 검색된 사용자 목록을 보여주는 리스트박스 (다중 선택 활성화)
$script:del_listBoxFoundUsers = New-Object System.Windows.Forms.ListBox
$script:del_listBoxFoundUsers.Dock = "Fill"
$script:del_listBoxFoundUsers.SelectionMode = "MultiExtended"
# 선택된 사용자의 상세 정보를 표시하는 레이아웃
$userInfoLayout = New-Object System.Windows.Forms.TableLayoutPanel
$userInfoLayout.Dock = "Fill"
$userInfoLayout.ColumnCount = 2
$userInfoLayout.RowCount = 1
$userInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
$userInfoLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 50))) | Out-Null
$script:del_textBoxFoundUserDisplay = New-Object System.Windows.Forms.TextBox
$script:del_textBoxFoundUserDisplay.Dock = "Fill"
$script:del_textBoxFoundUserDisplay.ReadOnly = $true
$script:del_textBoxFoundUserDisplay.Text = "표시 이름:"
$script:del_textBoxFoundUserUPN = New-Object System.Windows.Forms.TextBox
$script:del_textBoxFoundUserUPN.Dock = "Fill"
$script:del_textBoxFoundUserUPN.ReadOnly = $true
$script:del_textBoxFoundUserUPN.Text = "UPN:"
$userInfoLayout.Controls.AddRange(@($script:del_textBoxFoundUserDisplay, $script:del_textBoxFoundUserUPN)) | Out-Null
# '삭제 목록에 추가' 버튼
$script:del_buttonAddToDeleteList = New-Object System.Windows.Forms.Button
$script:del_buttonAddToDeleteList.Text = "삭제 목록에 추가(&A) ⬇"
$script:del_buttonAddToDeleteList.Dock = "Fill"
$script:del_buttonAddToDeleteList.Enabled = $false
# 검색 레이아웃에 컨트롤 추가
$searchLayout.Controls.Add($script:del_textBoxSearchUser, 0, 0) | Out-Null
$searchLayout.Controls.Add($buttonSearchUserOnPrem, 1, 0) | Out-Null
$searchLayout.Controls.Add($script:del_listBoxFoundUsers, 0, 1) | Out-Null
$searchLayout.SetColumnSpan($script:del_listBoxFoundUsers, 2)
$searchLayout.Controls.Add($userInfoLayout, 0, 2) | Out-Null
$searchLayout.SetColumnSpan($userInfoLayout, 2)
$searchLayout.Controls.Add($script:del_buttonAddToDeleteList, 0, 3) | Out-Null
$searchLayout.SetColumnSpan($script:del_buttonAddToDeleteList, 2)
# ==========================================================
# 3. 삭제 대기 목록 그룹박스
# ==========================================================
$groupQueue = New-Object System.Windows.Forms.GroupBox
$groupQueue.Text = "2. 삭제 대기 목록 (우클릭으로 항목 관리)"
$groupQueue.Dock = "Fill"
$delTabMainLayout.Controls.Add($groupQueue, 0, 2) | Out-Null
# 대기 목록 그룹박스 내부 레이아웃
$queueLayout = New-Object System.Windows.Forms.TableLayoutPanel
$queueLayout.Dock = "Fill"
$queueLayout.Padding = [System.Windows.Forms.Padding](10)
$queueLayout.ColumnCount = 1
$queueLayout.RowCount = 2
$queueLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
$queueLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) | Out-Null
$queueLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 50))) | Out-Null
$groupQueue.Controls.Add($queueLayout) | Out-Null
# 삭제 대기열 리스트박스 및 컨텍스트 메뉴
$script:del_listBoxDeleteQueue = New-Object System.Windows.Forms.ListBox
$script:del_listBoxDeleteQueue.Dock = "Fill"
$contextMenu = New-Object System.Windows.Forms.ContextMenuStrip
$menuItemRemove = New-Object System.Windows.Forms.ToolStripMenuItem("선택 항목 제거")
$menuItemRemove.Name = "menuItemRemove"
$menuItemClear = New-Object System.Windows.Forms.ToolStripMenuItem("목록 전체 비우기")
$contextMenu.Items.AddRange(@($menuItemRemove, $menuItemClear)) | Out-Null
$script:del_listBoxDeleteQueue.ContextMenuStrip = $contextMenu
# '삭제 실행' 버튼
$script:del_buttonExecuteDelete = New-Object System.Windows.Forms.Button
$script:del_buttonExecuteDelete.Dock = "Fill"
$script:del_buttonExecuteDelete.Enabled = $false
$script:del_buttonExecuteDelete.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold)
# 대기 목록 레이아웃에 컨트롤 추가
$queueLayout.Controls.Add($script:del_listBoxDeleteQueue, 0, 0) | Out-Null
$queueLayout.Controls.Add($script:del_buttonExecuteDelete, 0, 1) | Out-Null
# ==========================================================
# 이벤트 핸들러 연결
# ==========================================================
$script:del_radioSync.add_CheckedChanged($del_ModeChangedHandler)
$script:del_radioOnPremOnly.add_CheckedChanged($del_ModeChangedHandler)
$buttonSearchUserOnPrem.Add_Click($del_buttonSearchUserOnPrem_Click)
$script:del_listBoxFoundUsers.Add_SelectedIndexChanged($del_listBoxFoundUsers_SelectedIndexChanged)
$script:del_listBoxFoundUsers.Add_DoubleClick($del_listBoxFoundUsers_DoubleClick)
$script:del_buttonAddToDeleteList.Add_Click({ Move-SelectedUsersToDeleteQueue })
$script:del_buttonExecuteDelete.Add_Click($del_buttonExecuteDelete_Click)
$menuItemRemove.Add_Click($del_contextMenuRemove_Click)
$menuItemClear.Add_Click($del_contextMenuClear_Click)
# 컨텍스트 메뉴가 열릴 때 '선택 항목 제거' 메뉴의 활성화 여부 결정
$contextMenu.Add_Opening({
param($sourceItem, $e)
$menuItem = $sourceItem.Items["menuItemRemove"]
if ($menuItem) {
$menuItem.Enabled = ($null -ne $script:del_listBoxDeleteQueue.SelectedItem)
}
})
# 초기 모드 설정에 따라 버튼 텍스트 업데이트
$del_ModeChangedHandler.Invoke($script:del_radioSync, $null)
}
#endregion
#region '계정 삭제' 탭 이벤트 핸들러 및 함수
# 작업 모드 라디오 버튼 변경 시 '삭제 실행' 버튼의 텍스트를 변경하는 핸들러
$del_ModeChangedHandler = {
param($sourceControl, $e)
# 체크된 라디오 버튼에 대해서만 동작
if (-not $sourceControl.Checked) { return }
if ($script:del_radioSync.Checked) {
$script:del_buttonExecuteDelete.Text = "목록 삭제 및 동기화 실행(&E)"
}
else {
$script:del_buttonExecuteDelete.Text = "목록 전체 삭제 실행 (On-Prem Only)(&E)"
}
}
# '온프레미스 검색' 버튼 클릭 이벤트 핸들러
$del_buttonSearchUserOnPrem_Click = {
$searchTerm = $script:del_textBoxSearchUser.Text.Trim()
if ([string]::IsNullOrEmpty($searchTerm)) { return }
# 동기 함수 호출 래퍼를 사용하여 사용자 검색 실행 (UI 응답성 유지)
$users = Invoke-Synchronous -TriggerControl $this -StatusMessage "온프레미스 AD에서 사용자 '$searchTerm' 검색 중..." -ScriptBlock {
# SamAccountName 또는 DisplayName에 검색어가 포함된 사용자를 찾음
Get-ADUser -Filter "(SamAccountName -like '*$searchTerm*') -or (DisplayName -like '*$searchTerm*')" -Properties DisplayName, UserPrincipalName, SamAccountName -Server $script:Configuration.OnPremDomainController -ErrorAction SilentlyContinue
}
# UI 업데이트 전 BeginUpdate() 호출로 깜빡임 방지
$script:del_listBoxFoundUsers.BeginUpdate()
$script:del_listBoxFoundUsers.Items.Clear() | Out-Null
if ($users) {
# 찾은 사용자들을 이름순으로 정렬하여 리스트박스에 추가
foreach ($user in @($users) | Sort-Object DisplayName) {
$display = "$($user.DisplayName) (sAM: $($user.SamAccountName))"
$item = [PSCustomObject]@{
DisplayText = $display
ADObject = $user
}
$script:del_listBoxFoundUsers.Items.Add($item) | Out-Null
}
}
$script:del_listBoxFoundUsers.DisplayMember = "DisplayText"
$script:del_listBoxFoundUsers.EndUpdate()
Write-Log "$(@($users).Count)명의 사용자를 찾았습니다."
}
# 검색된 사용자 리스트박스에서 선택이 변경될 때의 이벤트 핸들러
$del_listBoxFoundUsers_SelectedIndexChanged = {
# 다중 선택을 지원하므로 SelectedItems 속성 사용
$selectedItems = $this.SelectedItems
if ($selectedItems.Count -gt 0) {
# 정보 표시는 첫 번째 선택된 항목 기준으로 함
$firstSelectedItem = $selectedItems[0]
$script:del_selectedADUser = $firstSelectedItem.ADObject
$script:del_textBoxFoundUserDisplay.Text = "표시 이름: $($script:del_selectedADUser.DisplayName)"
$script:del_textBoxFoundUserUPN.Text = "UPN: $($script:del_selectedADUser.UserPrincipalName)"
$script:del_buttonAddToDeleteList.Enabled = $true
}
else {
# 선택된 항목이 없으면 정보 초기화 및 버튼 비활성화
$script:del_selectedADUser = $null
$script:del_textBoxFoundUserDisplay.Text = "표시 이름:"
$script:del_textBoxFoundUserUPN.Text = "UPN:"
$script:del_buttonAddToDeleteList.Enabled = $false
}
}
# 검색된 사용자 리스트박스에서 항목을 더블클릭할 때의 이벤트 핸들러
$del_listBoxFoundUsers_DoubleClick = {
# 더블클릭 시 해당 항목 하나만 삭제 대기 목록으로 이동
$selectedItem = $script:del_listBoxFoundUsers.SelectedItem
if ($null -eq $selectedItem) { return }
$userObject = $selectedItem.ADObject
# 이미 대기 목록에 있는지 확인 (고유 식별자인 DistinguishedName 기준)
$isAlreadyInQueue = $script:del_deleteQueue.DistinguishedName -contains $userObject.DistinguishedName
if (-not $isAlreadyInQueue) {
$script:del_deleteQueue.Add($userObject) | Out-Null
$script:del_listBoxDeleteQueue.Items.Add($selectedItem.DisplayText) | Out-Null
$script:del_buttonExecuteDelete.Enabled = $true
$script:del_listBoxFoundUsers.Items.Remove($selectedItem) | Out-Null
}
else {
[System.Windows.Forms.MessageBox]::Show("해당 사용자는 이미 삭제 대기 목록에 있습니다.", "중복 추가", "OK", "Information") | Out-Null
}
}
# 검색 리스트에서 선택된 사용자들을 삭제 대기 목록으로 옮기는 함수
function Move-SelectedUsersToDeleteQueue {
$selectedItems = $script:del_listBoxFoundUsers.SelectedItems
if ($selectedItems.Count -eq 0) { return }
# 여러 항목 변경 시 UI 깜빡임 방지를 위해 BeginUpdate/EndUpdate 사용
$script:del_listBoxFoundUsers.BeginUpdate()
$script:del_listBoxDeleteQueue.BeginUpdate()
# 반복 중 컬렉션에서 항목을 제거하면 문제가 발생하므로, 복사본을 만들어 순회
$itemsToMove = @($selectedItems)
foreach ($item in $itemsToMove) {
$userObject = $item.ADObject
$isAlreadyInQueue = $script:del_deleteQueue.DistinguishedName -contains $userObject.DistinguishedName
if (-not $isAlreadyInQueue) {
$script:del_deleteQueue.Add($userObject) | Out-Null
$script:del_listBoxDeleteQueue.Items.Add($item.DisplayText) | Out-Null
$script:del_listBoxFoundUsers.Items.Remove($item) | Out-Null
}
}
$script:del_buttonExecuteDelete.Enabled = ($script:del_deleteQueue.Count -gt 0)
$script:del_listBoxFoundUsers.EndUpdate()
$script:del_listBoxDeleteQueue.EndUpdate()
}
# 삭제 대기 목록 컨텍스트 메뉴 - '선택 항목 제거' 클릭 이벤트 핸들러
$del_contextMenuRemove_Click = {
$selectedIndex = $script:del_listBoxDeleteQueue.SelectedIndex
if ($selectedIndex -ne -1) {
# 데이터 목록과 UI 목록에서 모두 제거
$script:del_deleteQueue.RemoveAt($selectedIndex)
$script:del_listBoxDeleteQueue.Items.RemoveAt($selectedIndex) | Out-Null
$script:del_buttonExecuteDelete.Enabled = ($script:del_deleteQueue.Count -gt 0)
}
}
# 삭제 대기 목록 컨텍스트 메뉴 - '목록 전체 비우기' 클릭 이벤트 핸들러
$del_contextMenuClear_Click = {
$script:del_deleteQueue.Clear()
$script:del_listBoxDeleteQueue.Items.Clear() | Out-Null
$script:del_buttonExecuteDelete.Enabled = $false
}
# '삭제 실행' 버튼 클릭 이벤트 핸들러
$del_buttonExecuteDelete_Click = {
if ($script:del_deleteQueue.Count -eq 0) { return }
# 삭제할 사용자 목록을 만들어 사용자에게 최종 확인
$userList = ($script:del_deleteQueue | ForEach-Object { "- $($_.DisplayName)" }) -join "`n"
$message = "다음 $($script:del_deleteQueue.Count)명의 사용자를 영구적으로 삭제하시겠습니까?`n이 작업은 되돌릴 수 없습니다.`n`n$userList"
$confirmResult = [System.Windows.Forms.MessageBox]::Show($message, "최종 삭제 확인", "YesNo", "Warning")
if ($confirmResult -ne "Yes") {
Write-Log "사용자가 삭제 작업을 취소했습니다." -Level "WARNING"
return
}
# 동기 함수 래퍼를 통해 실제 삭제 작업 수행
$deletedCount = Invoke-Synchronous -TriggerControl $this -StatusMessage "사용자 삭제 작업을 실행합니다..." -ScriptBlock {
$count = 0
# 반복 중 컬렉션 변경에 안전하도록 ToArray()로 복사본 사용
foreach ($userToDelete in $script:del_deleteQueue.ToArray()) {
try {
Write-Log "AD 사용자 '$($userToDelete.SamAccountName)' 삭제 시도..."
Remove-ADUser -Identity $userToDelete.DistinguishedName -Confirm:$false -Server $script:Configuration.OnPremDomainController -ErrorAction Stop
Write-Log " -> AD 사용자 '$($userToDelete.SamAccountName)' 삭제 성공."
$count++
}
catch {
Write-Log " -> AD 사용자 '$($userToDelete.SamAccountName)' 삭제 실패: $($_.Exception.Message)" -Level "ERROR"
}
}
# '동기화' 모드이고 한 명 이상 성공적으로 삭제된 경우, Azure AD Connect 델타 동기화 실행
if ($script:del_radioSync.Checked -and $count -gt 0) {
Invoke-AadConnectSync -PolicyType Delta
}
# 성공적으로 삭제된 사용자 수 반환
return $count
}
# 작업 완료 후 결과 메시지 표시 및 탭 초기화
# 수정: $null을 비교 연산자 왼쪽에 배치 (PSScriptAnalyzer 규칙 준수)
if ($null -ne $deletedCount) {
[System.Windows.Forms.MessageBox]::Show("$deletedCount 명의 사용자에 대한 삭제 작업이 완료되었습니다.", "작업 완료", "OK", "Information") | Out-Null
Reset-DeleteUserTab
}
}
#endregion
Write-Host "UI-Tab-DeleteUser.ps1 로드 완료." -ForegroundColor Cyan