Files
adusermanager/Main.ps1

387 lines
16 KiB
PowerShell
Raw Permalink Normal View History

2025-09-15 13:46:19 +09:00
# ================================================================================
# 파일: Main.ps1
# 역할: AD/M365 통합 관리 도구 메인 스크립트
#
# 작성자: 양범진
# 버전: 1.15
# 생성일자: 2025-06-05
# 최종 수정일자: 2025-06-12
#
# 설명: 모든 기능 및 오류 수정이 완료된 최종 안정화 버전
# 가독성 향상을 위해 코드 포매팅 및 주석 재정리
# ================================================================================
# --- 1단계: 필수 어셈블리 및 모든 스크립트 파일 로드 ---
# Windows Forms 및 Drawing 어셈블리를 로드하여 GUI를 생성
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# 스크립트 파일이 위치한 경로를 기준으로 관련 스크립트들을 로드
$scriptRoot = $PSScriptRoot
$ScriptsPath = Join-Path -Path $scriptRoot -ChildPath "Scripts"
. (Join-Path -Path $ScriptsPath -ChildPath "Common.ps1")
. (Join-Path -Path $ScriptsPath -ChildPath "UI-Tab-AddUser.ps1")
. (Join-Path -Path $ScriptsPath -ChildPath "UI-Tab-DeleteUser.ps1")
. (Join-Path -Path $ScriptsPath -ChildPath "UI-Tab-HardMatch.ps1")
. (Join-Path -Path $ScriptsPath -ChildPath "UI-Tab-BatchTask.ps1")
# --- 2단계: 전역 변수 선언 및 초기화 ---
# 스크립트 전체에서 사용될 변수들을 'script' 스코프로 선언
# 설정 및 로그 파일 경로
$script:configFilePath = Join-Path -Path $scriptRoot -ChildPath "config.json"
$script:logDir = Join-Path -Path $scriptRoot -ChildPath "logs"
if (-not (Test-Path $script:logDir)) {
New-Item -Path $script:logDir -ItemType Directory | Out-Null
}
$script:logFilePath = Join-Path -Path $script:logDir -ChildPath "ad_tool_$(Get-Date -Format 'yyyy-MM-dd').log"
# 메인 UI 컨트롤
$script:mainForm = $null
$script:richTextBoxLog = $null
$script:connectAzureADMenu = $null
$script:statusStrip = $null
$script:statusLabelAzure = $null
$script:statusLabelOnPrem = $null
$script:statusLabelJob = $null
$script:iconGreen = $null
$script:iconRed = $null
$script:iconGray = $null
# 환경 설정 및 상태 변수
$script:Configuration = $null
$script:CurrentADDomainInfo = $null
$script:CurrentADDomainDN = $null
$script:SynchronizedOURoots = @()
# '계정 생성' 탭 컨트롤
$script:add_textBoxLastNameKr = $null
$script:add_textBoxFirstNameKr = $null
$script:add_textBoxAccountNameEn = $null
$script:add_textBoxPassword = $null
$script:add_textBoxSelectedOU = $null
$script:add_treeViewOUs = $null
$script:add_labelOUSyncStatus = $null
$script:add_listBoxLicenses = $null
$script:add_buttonCreate = $null
$script:add_radioSync = $null
$script:add_radioOnPremOnly = $null
$script:add_groupLicense = $null
$script:add_pictureBoxAccountValidation = $null
$script:add_toolTip = $null
$script:add_checkedListBoxServicePlans = $null
# '계정 삭제' 탭 컨트롤
$script:del_textBoxSearchUser = $null
$script:del_listBoxFoundUsers = $null
$script:del_buttonAddToDeleteList = $null
$script:del_listBoxDeleteQueue = $null
$script:del_buttonExecuteDelete = $null
$script:del_radioSync = $null
$script:del_radioOnPremOnly = $null
$script:del_selectedADUser = $null
$script:del_deleteQueue = ([System.Collections.ArrayList]::new())
$script:del_textBoxFoundUserDisplay = $null
$script:del_textBoxFoundUserUPN = $null
# '하드 매칭' 탭 컨트롤
$script:hm_textBoxSearchAzure = $null
$script:hm_listBoxFoundAzure = $null
$script:hm_textBoxOnPremUser = $null
$script:hm_textBoxObjectGuid = $null
$script:hm_textBoxConvertedImmutableId = $null
$script:hm_textBoxAzureUser = $null
$script:hm_textBoxImmutableId = $null
$script:hm_labelGuidMatchStatus = $null
$script:hm_groupCreate = $null
$script:hm_textBoxSelectedOU = $null
$script:hm_treeViewOUs = $null
$script:hm_buttonExecuteAction = $null
$script:hm_labelOUSyncStatus = $null
$script:hm_textBoxFirstNameKr = $null
$script:hm_textBoxLastNameKr = $null
$script:hm_textBoxPassword = $null
$script:hm_selectedAzureUser = $null
$script:hm_foundOnPremUser = $null
$script:hm_currentMode = "Idle"
# '일괄 작업' 탭 컨트롤
$script:batch_comboBoxTaskType = $null
$script:batch_buttonDownloadTemplate = $null
$script:batch_textBoxCsvPath = $null
$script:batch_buttonBrowseCsv = $null
$script:batch_dataGridView = $null
$script:batch_buttonExecute = $null
$script:batch_progressBar = $null
$script:batch_loadedCsvData = $null
# --- 3단계: 함수 정의 ---
# 메인 폼 및 모든 UI 컨트롤을 생성하고 초기화하는 함수
function Initialize-MainForm {
# 메인 폼 생성
$script:mainForm = New-Object System.Windows.Forms.Form
$script:mainForm.Text = "AD/M365 통합 관리 도구 v1.0"
$script:mainForm.Size = New-Object System.Drawing.Size(950, 850)
$script:mainForm.StartPosition = "CenterScreen"
$script:mainForm.MinimumSize = New-Object System.Drawing.Size(900, 750)
$script:mainForm.ShowInTaskbar = $false # 초기 로딩 시 작업 표시줄에 보이지 않도록 설정
$script:mainForm.Opacity = 0 # 초기 로딩 시 투명하게 설정하여 깜빡임 방지
# 상단 메뉴 스트립 생성
$mainMenu = New-Object System.Windows.Forms.MenuStrip
$fileMenu = New-Object System.Windows.Forms.ToolStripMenuItem("파일(&F)")
$script:connectAzureADMenu = New-Object System.Windows.Forms.ToolStripMenuItem("Azure AD 수동 연결/재연결(&C)")
$configBackupMenu = New-Object System.Windows.Forms.ToolStripMenuItem("설정 백업(&B)")
$configRestoreMenu = New-Object System.Windows.Forms.ToolStripMenuItem("설정 복원(&R)")
$exitMenu = New-Object System.Windows.Forms.ToolStripMenuItem("종료(&X)")
$fileMenu.DropDownItems.AddRange(@(
$script:connectAzureADMenu,
(New-Object System.Windows.Forms.ToolStripSeparator),
$configBackupMenu,
$configRestoreMenu,
(New-Object System.Windows.Forms.ToolStripSeparator),
$exitMenu
)) | Out-Null
$toolsMenu = New-Object System.Windows.Forms.ToolStripMenuItem("도구(&T)")
$syncNowMenu = New-Object System.Windows.Forms.ToolStripMenuItem("AAD Connect 즉시 동기화 실행(&S)...")
$openLogFolderMenu = New-Object System.Windows.Forms.ToolStripMenuItem("로그 폴더 열기(&L)")
$openConfigFileMenu = New-Object System.Windows.Forms.ToolStripMenuItem("설정 파일 열기(&O)")
$toolsMenu.DropDownItems.AddRange(@(
$syncNowMenu,
(New-Object System.Windows.Forms.ToolStripSeparator),
$openLogFolderMenu,
$openConfigFileMenu
)) | Out-Null
$mainMenu.Items.AddRange(@($fileMenu, $toolsMenu)) | Out-Null
$script:mainForm.Controls.Add($mainMenu) | Out-Null
# 탭 컨트롤 생성
$tabControl = New-Object System.Windows.Forms.TabControl
$tabControl.Location = New-Object System.Drawing.Point(10, 30)
$tabControl.Size = New-Object System.Drawing.Size(915, 600)
$tabControl.Anchor = 'Top, Left, Right'
$script:mainForm.Controls.Add($tabControl) | Out-Null
# 각 기능 탭 페이지 생성
$tabPageAdd = New-Object System.Windows.Forms.TabPage("계정 생성")
$tabPageDel = New-Object System.Windows.Forms.TabPage("계정 삭제")
$tabPageHardMatch = New-Object System.Windows.Forms.TabPage("하드 매칭")
$tabPageBatch = New-Object System.Windows.Forms.TabPage("일괄 작업 (CSV)")
$tabControl.Controls.AddRange(@($tabPageAdd, $tabPageDel, $tabPageHardMatch, $tabPageBatch)) | Out-Null
# 각 탭의 UI를 초기화하는 함수 호출 (별도 스크립트 파일에 정의)
Initialize-AddUserTab -parentTab $tabPageAdd
Initialize-DeleteUserTab -parentTab $tabPageDel
Initialize-HardMatchTab -parentTab $tabPageHardMatch
Initialize-BatchTaskTab -parentTab $tabPageBatch
# 하단 로그 출력 영역 생성
$labelLog = New-Object System.Windows.Forms.Label
$labelLog.Text = "처리 로그:"
$labelLog.Location = New-Object System.Drawing.Point(10, 640)
$labelLog.Anchor = 'Top, Left'
$labelLog.AutoSize = $true
$script:mainForm.Controls.Add($labelLog) | Out-Null
$script:richTextBoxLog = New-Object System.Windows.Forms.RichTextBox
$script:richTextBoxLog.Location = New-Object System.Drawing.Point(10, 660)
$script:richTextBoxLog.Size = New-Object System.Drawing.Size(915, 125)
$script:richTextBoxLog.ReadOnly = $true
$script:richTextBoxLog.ScrollBars = "Vertical"
$script:richTextBoxLog.Anchor = 'Top, Left, Right, Bottom'
$script:mainForm.Controls.Add($script:richTextBoxLog) | Out-Null
# 하단 상태 표시줄 생성
$script:iconGreen = New-StatusIcon -Color ([System.Drawing.Color]::LimeGreen)
$script:iconRed = New-StatusIcon -Color ([System.Drawing.Color]::Red)
$script:iconGray = New-Object System.Drawing.Bitmap(1, 1) # 회색 아이콘 대신 기본 비트맵
$script:statusStrip = New-Object System.Windows.Forms.StatusStrip
$script:statusStrip.SizingGrip = $false
$script:statusLabelAzure = New-Object System.Windows.Forms.ToolStripStatusLabel
$script:statusLabelAzure.Image = $script:iconRed
$script:statusLabelAzure.Text = "Azure AD: 확인 중..."
$script:statusLabelOnPrem = New-Object System.Windows.Forms.ToolStripStatusLabel
$script:statusLabelOnPrem.Image = $script:iconGray
$script:statusLabelOnPrem.Text = "On-Prem DC: 확인 중..."
$script:statusLabelJob = New-Object System.Windows.Forms.ToolStripStatusLabel
$script:statusLabelJob.Spring = $true # 남은 공간을 모두 차지하도록 설정
$script:statusLabelJob.TextAlign = "MiddleRight"
$script:statusStrip.Items.AddRange(@(
$script:statusLabelAzure,
(New-Object System.Windows.Forms.ToolStripSeparator),
$script:statusLabelOnPrem,
$script:statusLabelJob
)) | Out-Null
$script:mainForm.Controls.Add($script:statusStrip) | Out-Null
# 메뉴 항목에 대한 이벤트 핸들러 연결
$syncNowMenu.Add_Click($syncNowMenu_Click)
$script:connectAzureADMenu.Add_Click({ Connect-AzureAD-WithInteraction })
$configBackupMenu.Add_Click($configBackupMenu_Click)
$configRestoreMenu.Add_Click($configRestoreMenu_Click)
$exitMenu.Add_Click({ if ($script:mainForm.IsHandleCreated) { $script:mainForm.Close() } })
$openLogFolderMenu.Add_Click({ Invoke-Item $script:logDir })
$openConfigFileMenu.Add_Click({ if (Test-Path $script:configFilePath) { Invoke-Item $script:configFilePath } })
}
# '설정 백업' 메뉴 클릭 이벤트 핸들러
$configBackupMenu_Click = {
$sfd = New-Object System.Windows.Forms.SaveFileDialog
$sfd.Filter = "JSON 파일 (*.json)|*.json"
$sfd.FileName = "config.backup.json"
if ($sfd.ShowDialog() -eq "OK") {
try {
Copy-Item -Path $script:configFilePath -Destination $sfd.FileName -Force
[System.Windows.Forms.MessageBox]::Show("설정 파일이 백업되었습니다.", "백업 완료", "OK", "Information") | Out-Null
}
catch {
Show-DetailedErrorDialog -ErrorRecord $_
}
}
}
# '설정 복원' 메뉴 클릭 이벤트 핸들러
$configRestoreMenu_Click = {
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "JSON 파일 (*.json)|*.json"
if ($ofd.ShowDialog() -eq "OK") {
try {
Copy-Item -Path $ofd.FileName -Destination $script:configFilePath -Force
[System.Windows.Forms.MessageBox]::Show("설정을 복원했습니다. 프로그램을 다시 시작해주세요.", "복원 완료", "OK", "Information") | Out-Null
$script:mainForm.Close()
}
catch {
Show-DetailedErrorDialog -ErrorRecord $_
}
}
}
# 'AAD Connect 즉시 동기화' 메뉴 클릭 이벤트 핸들러
$syncNowMenu_Click = {
# 동기화 유형을 선택하는 작은 대화상자 생성
$syncForm = New-Object System.Windows.Forms.Form
$syncForm.Text = "동기화 유형 선택"
$syncForm.Size = New-Object System.Drawing.Size(300, 180)
$syncForm.StartPosition = "CenterParent"
$syncForm.FormBorderStyle = "FixedDialog"
$syncForm.MaximizeBox = $false
$syncForm.MinimizeBox = $false
$radioDelta = New-Object System.Windows.Forms.RadioButton
$radioDelta.Text = "증분 동기화 (Delta) - 권장"
$radioDelta.Location = New-Object System.Drawing.Point(20, 20)
$radioDelta.AutoSize = $true
$radioDelta.Checked = $true
$radioFull = New-Object System.Windows.Forms.RadioButton
$radioFull.Text = "전체 동기화 (Initial/Full)"
$radioFull.Location = New-Object System.Drawing.Point(20, 50)
$radioFull.AutoSize = $true
$runButton = New-Object System.Windows.Forms.Button
$runButton.Text = "실행"
$runButton.DialogResult = "OK"
$runButton.Location = New-Object System.Drawing.Point(50, 100)
$runButton.Size = New-Object System.Drawing.Size(90, 30)
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Text = "취소"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Location = New-Object System.Drawing.Point(150, 100)
$cancelButton.Size = New-Object System.Drawing.Size(90, 30)
$syncForm.Controls.AddRange(@($radioDelta, $radioFull, $runButton, $cancelButton)) | Out-Null
$syncForm.AcceptButton = $runButton
$syncForm.CancelButton = $cancelButton
# 사용자가 '실행'을 누르면 동기화 실행
if ($syncForm.ShowDialog($script:mainForm) -eq "OK") {
$policyType = if ($radioDelta.Checked) { "Delta" } else { "Initial" }
Invoke-AadConnectSync -PolicyType $policyType
}
$syncForm.Dispose()
}
# ================================================================================
# 프로그램 실행 시작
# ================================================================================
# 1. 관리자 권한 확인
if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
[System.Windows.Forms.MessageBox]::Show("관리자 권한으로 실행해야 합니다.", "권한 오류", "OK", "Error") | Out-Null
Exit
}
# 2. 필수 PowerShell 모듈 확인
if (-not (Test-RequiredModules)) {
[System.Windows.Forms.MessageBox]::Show("필수 PowerShell 모듈이 없어 프로그램을 종료합니다.", "모듈 오류", "OK", "Error") | Out-Null
Exit
}
# 3. 설정 파일(config.json) 로드
if (Test-Path $script:configFilePath) {
try {
$script:Configuration = Get-Content -Path $script:configFilePath -Raw | ConvertFrom-Json
# 이전 버전과의 호환성을 위해 불필요한 속성 제거
if ($script:Configuration.PSObject.Properties.Name.Contains('AccountNameFormat')) {
$script:Configuration.PSObject.Properties.Remove('AccountNameFormat')
}
}
catch {
[System.Windows.Forms.MessageBox]::Show("설정 파일 읽기 오류. 기본 설정으로 시작합니다.", "설정 오류", "OK", "Warning") | Out-Null
$script:Configuration = $null
}
}
# 4. 설정 파일이 없거나 오류 발생 시 기본값으로 초기화
if ($null -eq $script:Configuration) {
$script:Configuration = [ordered]@{
OnPremDomainController = "dc.example.com"
AADConnectServerName = ""
AzureTenantId = ""
UPNSuffix = "example.com"
SynchronizedOUs = "OU=Users,DC=example,DC=com"
DefaultPassword = "Password123!"
DefaultUsageLocation = "KR"
}
}
# 5. 메인 폼 UI 초기화
Initialize-MainForm
Write-Log "AD/M365 통합 관리 도구를 시작합니다."
# 6. 설정 대화상자 표시 및 사용자 설정 확인
if (-not (Show-ConfigurationDialog)) {
Write-Log "사용자가 설정을 취소하여 프로그램을 종료합니다."
Exit
}
# 7. 스크립트 실행 환경(On-Prem AD 연결 등) 초기화
if (-not (Initialize-ScriptEnvironment)) {
[System.Windows.Forms.MessageBox]::Show("온프레미스 DC에 연결할 수 없습니다. 설정을 확인하고 다시 시작해주세요.", "초기화 오류", "OK", "Error") | Out-Null
Exit
}
# 8. 메인 폼 표시 및 이벤트 핸들러 등록
$script:mainForm.ShowInTaskbar = $true
$script:mainForm.Opacity = 1
$script:mainForm.Activate()
# 폼이 처음 표시된 후 자동으로 Azure AD에 연결하고 OU 트리를 로드
$script:mainForm.Add_Shown({
Connect-AzureAD-Silently
Update-OU-TreeView -TriggerControl $null
})
# 메인 폼을 대화상자 형태로 실행하여 사용자가 닫을 때까지 대기
$script:mainForm.ShowDialog() | Out-Null
Write-Log "프로그램을 종료합니다."