#requires -modules ActiveDirectory Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Xaml, System.Windows.Forms # AD 모듈 로드 try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[오류] AD 모듈 로드 실패"; exit 1 } # AD 함수 정의 function Get-LockedADAccounts { try { $pdc = (Get-ADDomain).PDCEmulator $adAccounts = @(Search-ADAccount -LockedOut -Server $pdc | Get-ADUser -Properties Name, SamAccountName, LockedOut, LastLogonDate, BadPwdCount, DisplayName -Server $pdc) $list = @() foreach ($account in $adAccounts) { $lastLogon = if ($account.LastLogonDate -and $account.LastLogonDate.Year -ne 1601) { $account.LastLogonDate } else { $null } $list += [PSCustomObject]@{ Name = if ([string]::IsNullOrWhiteSpace($account.DisplayName)) { $account.Name } else { $account.DisplayName } SamAccountName = $account.SamAccountName LockedOut = $account.LockedOut LastLogonDate = $lastLogon BadPwdCount = $account.BadPwdCount } } return ,$list } catch { Write-Log "AD 조회 오류: $($_.Exception.Message)" "Red"; return $null } } function Unlock-ADUserAccount { param([string]$SamAccountName) try { $pdc = (Get-ADDomain).PDCEmulator Unlock-ADAccount -Identity $SamAccountName -Server $pdc return $true } catch { Write-Log "잠금 해제 실패: $SamAccountName - $($_.Exception.Message)" "Red"; return $false } } function Get-ADLockoutEvent { param([string]$SamAccountName) $dcs = (Get-ADDomainController -Filter *).Name $latest = $null foreach ($dc in $dcs) { try { $filter = @{ LogName = 'Security'; ID = 4740; StartTime = (Get-Date).AddDays(-1) } $event = Get-WinEvent -ComputerName $dc -FilterHashtable $filter -ErrorAction SilentlyContinue | Where-Object { $_.Properties[0].Value -eq $SamAccountName } | Sort-Object TimeCreated -Descending | Select-Object -First 1 if ($event -and ($null -eq $latest -or $event.TimeCreated -gt $latest.TimeCreated)) { $latest = $event } } catch {} } if (-not $latest) { return $null } $lockoutTime = $latest.TimeCreated $sourceDC = $latest.MachineName $caller = $latest.Properties[3].Value if ([string]::IsNullOrWhiteSpace($caller) -or $caller -eq 'S-1-5-18' -or $caller -eq $sourceDC) { try { $filter4625 = @{ LogName='Security'; ID=4625; StartTime=$lockoutTime.AddMinutes(-5); EndTime=$lockoutTime } $fail = Get-WinEvent -ComputerName $sourceDC -FilterHashtable $filter4625 -ErrorAction SilentlyContinue | Where-Object { ($_.Properties[5].Value -like "*$SamAccountName*") -and ($_.Properties[10].Value -eq '3' -or $_.Properties[10].Value -eq '10') } | Sort-Object TimeCreated -Descending | Select-Object -First 1 if ($fail) { $ws = $fail.Properties[13].Value $ip = $fail.Properties[19].Value $caller = if ($ws -and $ws -ne '-') { "$ws (IP: $ip)" } else { "IP: $ip" } } else { $caller = "$sourceDC (자체 발생 - 4625 없음)" } } catch {} } return [PSCustomObject]@{ TimeCreated=$lockoutTime; CallerComputer=$caller; DC=$sourceDC } } # 로그 출력 function Write-Log { param([string]$Message,[string]$Color="White") try { $mainForm.Dispatcher.Invoke({$para=New-Object Windows.Documents.Paragraph;$run=New-Object Windows.Documents.Run($Message);$run.Foreground=(New-Object Windows.Media.BrushConverter).ConvertFromString($Color);$para.Inlines.Add($run);$LogBox.Document.Blocks.Add($para);$LogBox.ScrollToEnd()}) | Out-Null } catch { Write-Host $Message } } # XAML GUI 정의 (Dark, DataGrid, 버튼, 로그) [xml]$xaml=@"