commit e6080ad87ea9d570c446dab22ff9b0e9fadb04fc Author: TigErJin Date: Mon Sep 15 13:39:33 2025 +0900 convert to gitea diff --git a/ADLockUser.exe b/ADLockUser.exe new file mode 100644 index 0000000..4d50200 Binary files /dev/null and b/ADLockUser.exe differ diff --git a/AccountUnlocker.ps1 b/AccountUnlocker.ps1 new file mode 100644 index 0000000..db25d4c --- /dev/null +++ b/AccountUnlocker.ps1 @@ -0,0 +1,217 @@ +#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=@" + + + + + + + + + + + + + + + +