Files
ModernAdminStudio/Modules/ADService.ps1
T

379 lines
15 KiB
PowerShell

# Modules\ADService.ps1
# Active Directory Service Module (.NET System.DirectoryServices Live & Fallback)
function Test-ADConnection {
param(
[string]$DomainController,
[string]$BaseOU,
[string]$Username,
[string]$Password,
[bool]$UseLdaps = $false
)
try {
$protocol = if ($UseLdaps) { "LDAP://$DomainController:636" } else { "LDAP://$DomainController" }
$path = if ([string]::IsNullOrWhiteSpace($BaseOU)) { "$protocol/RootDSE" } else { "$protocol/$BaseOU" }
$entry = if ([string]::IsNullOrWhiteSpace($Username)) {
New-Object System.DirectoryServices.DirectoryEntry($path)
} else {
New-Object System.DirectoryServices.DirectoryEntry($path, $Username, $Password)
}
# 바인딩 강제 테스트
$null = $entry.NativeObject
return [PSCustomObject]@{
Success = $true
Message = "Active Directory DC ($DomainController) 연결 성공 (RootDSE 응답 확인)"
LdapPath = $path
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "AD 연결 실패: $($_.Exception.Message)"
LdapPath = $path
}
}
}
function Get-ADUserList {
param(
[string]$Filter = "",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$dc = $Config.ActiveDirectory.DomainController
$baseOu = $Config.ActiveDirectory.BaseOU
$useLdaps = [bool]$Config.ActiveDirectory.UseLdaps
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
try {
$prefix = if ($useLdaps) { "LDAP://$dc:636" } else { "LDAP://$dc" }
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { $prefix } else { "$prefix/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
}
$searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
$searcher.PageSize = 1000
if ([string]::IsNullOrWhiteSpace($Filter)) {
$searcher.Filter = "(&(objectCategory=person)(objectClass=user))"
} else {
$esc = $Filter -replace '[*()\\\x00]', ''
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(|(sAMAccountName=*$esc*)(displayName=*$esc*)(department=*$esc*)))"
}
$props = @("sAMAccountName", "displayName", "department", "title", "userPrincipalName", "mail", "userAccountControl", "lockoutTime", "lastLogonTimestamp", "distinguishedName")
foreach ($p in $props) { [void]$searcher.PropertiesToLoad.Add($p) }
$results = $searcher.FindAll()
$userList = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($res in $results) {
$p = $res.Properties
$sam = if ($p["samaccountname"].Count -gt 0) { $p["samaccountname"][0] } else { "" }
$disp = if ($p["displayname"].Count -gt 0) { $p["displayname"][0] } else { $sam }
$dept = if ($p["department"].Count -gt 0) { $p["department"][0] } else { "-" }
$title = if ($p["title"].Count -gt 0) { $p["title"][0] } else { "-" }
$upn = if ($p["userprincipalname"].Count -gt 0) { $p["userprincipalname"][0] }
elseif ($p["mail"].Count -gt 0) { $p["mail"][0] } else { "$sam@domain.local" }
$dn = if ($p["distinguishedname"].Count -gt 0) { $p["distinguishedname"][0] } else { "" }
$uac = if ($p["useraccountcontrol"].Count -gt 0) { [int]$p["useraccountcontrol"][0] } else { 512 }
$isDisabled = ($uac -band 2) -eq 2
$statusText = if ($isDisabled) { "비활성" } else { "활성" }
$lockVal = if ($p["lockouttime"].Count -gt 0) { [int64]$p["lockouttime"][0] } else { 0 }
$isLocked = ($lockVal -gt 0)
$lockedText = if ($isLocked) { "잠김" } else { "정상" }
$lastLogonStr = "-"
if ($p["lastlogontimestamp"].Count -gt 0) {
try {
$fileTime = [int64]$p["lastlogontimestamp"][0]
if ($fileTime -gt 0) {
$lastLogonStr = [DateTime]::FromFileTime($fileTime).ToString("yyyy-MM-dd HH:mm:ss")
}
} catch {}
}
$userList.Add([PSCustomObject]@{
SamAccountName = $sam
DisplayName = $disp
Department = $dept
Title = $title
UserPrincipalName = $upn
StatusText = $statusText
LockedText = $lockedText
LastLogonDate = $lastLogonStr
Enabled = (-not $isDisabled)
IsLocked = $isLocked
DistinguishedName = $dn
IsLive = $true
})
}
return $userList.ToArray()
}
catch {
Write-Warning "실제 AD 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
}
}
# Fallback / Mock Data
return Get-ADSampleUsers -Filter $Filter
}
function Unlock-ADUserAccount {
param(
[string]$SamAccountName,
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$dc = $Config.ActiveDirectory.DomainController
$baseOu = $Config.ActiveDirectory.BaseOU
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
try {
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { "LDAP://$dc" } else { "LDAP://$dc/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
}
$searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$SamAccountName))"
$result = $searcher.FindOne()
if ($null -eq $result) {
return [PSCustomObject]@{ Success = $false; Message = "AD에서 [$SamAccountName] 사용자를 찾을 수 없습니다." }
}
$userEntry = $result.GetDirectoryEntry()
$userEntry.Properties["lockoutTime"].Value = 0
$userEntry.CommitChanges()
return [PSCustomObject]@{
Success = $true
Message = "[$SamAccountName] 계정의 잠금이 실제 AD에서 해제되었습니다."
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "AD 잠금 해제 실패: $($_.Exception.Message)"
}
}
}
# Demo Simulation
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] [$SamAccountName] 계정의 잠금이 해제되었습니다."
}
}
function Reset-ADUserPassword {
param(
[string]$SamAccountName,
[string]$NewPassword = "Passw0rd!#$((Get-Random -Minimum 1000 -Maximum 9999))",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$dc = $Config.ActiveDirectory.DomainController
$baseOu = $Config.ActiveDirectory.BaseOU
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
try {
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { "LDAP://$dc:636" } else { "LDAP://$dc:636/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
}
$searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$SamAccountName))"
$result = $searcher.FindOne()
if ($null -eq $result) {
return [PSCustomObject]@{ Success = $false; Message = "AD에서 [$SamAccountName] 사용자를 찾을 수 없습니다." }
}
$userEntry = $result.GetDirectoryEntry()
$userEntry.Invoke("SetPassword", @($NewPassword))
$userEntry.Properties["pwdLastSet"].Value = 0 # 다음 로그온 시 암호 변경 강제
$userEntry.CommitChanges()
return [PSCustomObject]@{
Success = $true
Message = "[$SamAccountName] 암호가 성공적으로 초기화되었습니다.`n임시 암호: $NewPassword"
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "AD 암호 초기화 실패: $($_.Exception.Message)`n(참고: AD 암호 변경은 보안상 LDAPS 636 포트 연결이 필요합니다.)"
}
}
}
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] [$SamAccountName] 계정의 암호가 초기화되었습니다.`n임시 암호: $NewPassword"
}
}
function Set-ADUserAccountState {
param(
[string]$SamAccountName,
[bool]$Enable,
[object]$Config = $null
)
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$dc = $Config.ActiveDirectory.DomainController
$baseOu = $Config.ActiveDirectory.BaseOU
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
try {
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { "LDAP://$dc" } else { "LDAP://$dc/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
}
$searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$SamAccountName))"
$result = $searcher.FindOne()
if ($null -eq $result) {
return [PSCustomObject]@{ Success = $false; Message = "사용자를 찾을 수 없습니다." }
}
$userEntry = $result.GetDirectoryEntry()
$uac = [int]$userEntry.Properties["userAccountControl"].Value
if ($Enable) {
$userEntry.Properties["userAccountControl"].Value = $uac -band (-bnot 2)
} else {
$userEntry.Properties["userAccountControl"].Value = $uac -bor 2
}
$userEntry.CommitChanges()
$action = if ($Enable) { "활성화" } else { "비활성화" }
return [PSCustomObject]@{ Success = $true; Message = "[$SamAccountName] 계정이 $action 되었습니다." }
} catch {
return [PSCustomObject]@{ Success = $false; Message = "AD 상태 변경 실패: $($_.Exception.Message)" }
}
}
$action = if ($Enable) { "활성화" } else { "비활성화" }
return [PSCustomObject]@{ Success = $true; Message = "[데모 모드] [$SamAccountName] 계정이 $action 되었습니다." }
}
function Get-ADSampleUsers {
param([string]$Filter = "")
$users = @(
[PSCustomObject]@{
SamAccountName = "gdhong"
DisplayName = "홍길동"
Department = "클라우드인프라팀"
Title = "수석 엔지니어"
UserPrincipalName = "gdhong@corp.local"
StatusText = "활성"
LockedText = "정상"
LastLogonDate = "2026-10-20 09:14:22"
Enabled = $true
IsLocked = $false
DistinguishedName = "CN=홍길동,OU=Engineers,OU=Users,DC=corp,DC=local"
IsLive = $false
},
[PSCustomObject]@{
SamAccountName = "cskim"
DisplayName = "김철수"
Department = "데이터플랫폼팀"
Title = "DBA"
UserPrincipalName = "cskim@corp.local"
StatusText = "활성"
LockedText = "정상"
LastLogonDate = "2026-10-20 11:45:01"
Enabled = $true
IsLocked = $false
DistinguishedName = "CN=김철수,OU=DBA,OU=Users,DC=corp,DC=local"
IsLive = $false
},
[PSCustomObject]@{
SamAccountName = "yylee"
DisplayName = "이영희"
Department = "보안관제팀"
Title = "정보보안책임"
UserPrincipalName = "yylee@corp.local"
StatusText = "잠김"
LockedText = "잠김 (5회 실패)"
LastLogonDate = "2026-10-19 18:22:10"
Enabled = $true
IsLocked = $true
DistinguishedName = "CN=이영희,OU=Security,OU=Users,DC=corp,DC=local"
IsLive = $false
},
[PSCustomObject]@{
SamAccountName = "mschoi"
DisplayName = "최민수"
Department = "개발운영팀"
Title = "DevOps 엔지니어"
UserPrincipalName = "mschoi@corp.local"
StatusText = "비활성"
LockedText = "정상"
LastLogonDate = "2026-09-01 14:02:00"
Enabled = $false
IsLocked = $false
DistinguishedName = "CN=최민수,OU=DevOps,OU=Users,DC=corp,DC=local"
IsLive = $false
},
[PSCustomObject]@{
SamAccountName = "jhbak"
DisplayName = "박지훈"
Department = "시스템관리팀"
Title = "스토리지 관리자"
UserPrincipalName = "jhbak@corp.local"
StatusText = "활성"
LockedText = "정상"
LastLogonDate = "2026-10-20 13:02:15"
Enabled = $true
IsLocked = $false
DistinguishedName = "CN=박지훈,OU=Admins,OU=Users,DC=corp,DC=local"
IsLive = $false
}
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $users
} else {
return @($users | Where-Object {
$_.SamAccountName -like "*$Filter*" -or
$_.DisplayName -like "*$Filter*" -or
$_.Department -like "*$Filter*"
})
}
}