Files
linux-scripts/windows-ad-smoke-tests.ps1
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

656 lines
23 KiB
PowerShell

###############################################################################
#### windows-ad-smoke-tests.ps1 — Verify Active Directory health ####
#### Checks DC connectivity, LDAP, DNS, replication, FSMO, shares, ####
#### Kerberos, Group Policy, ADWS, trusts, and time sync. ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version: 1.0 ####
#### ####
#### Usage: .\windows-ad-smoke-tests.ps1 ####
#### .\windows-ad-smoke-tests.ps1 -DomainController DC01 ####
#### .\windows-ad-smoke-tests.ps1 -OutputFormat tap ####
#### ####
#### See -Help for all options. ####
###############################################################################
[CmdletBinding()]
param(
[string]$DomainController = "",
[string]$Domain = "",
[ValidateSet("text","tap")]
[string]$OutputFormat = "text",
[switch]$NoColor,
[switch]$Help
)
$ErrorActionPreference = "Continue"
# ============================================================================
# HELP
# ============================================================================
if ($Help) {
@"
Usage: .\windows-ad-smoke-tests.ps1 [OPTIONS]
Smoke-test Active Directory infrastructure. PowerShell 5.1+.
Designed for domain-joined Windows machines.
Parameters:
-DomainController DC Target DC hostname or IP (default: auto-detect via nltest)
-Domain FQDN AD domain FQDN (default: auto-detect via USERDNSDOMAIN)
-OutputFormat FORMAT Output: text (default), tap
-NoColor Disable coloured output
-Verbose Show debug output
-Help Show this help
Examples:
.\windows-ad-smoke-tests.ps1
.\windows-ad-smoke-tests.ps1 -DomainController DC01.corp.local
.\windows-ad-smoke-tests.ps1 -Domain corp.local -OutputFormat tap
.\windows-ad-smoke-tests.ps1 -NoColor -Verbose
"@
exit 0
}
# ============================================================================
# STATE
# ============================================================================
$script:Pass = 0
$script:Fail = 0
$script:Skip = 0
$script:Total = 0
$script:Results = @()
$script:StartTime = Get-Date
# ============================================================================
# COLORS
# ============================================================================
function Write-Color {
param([string]$Text, [string]$Color = "White")
if ($NoColor) {
Write-Host $Text
} else {
Write-Host $Text -ForegroundColor $Color
}
}
function Write-Log { param([string]$Msg) Write-Color "[INFO] $Msg" "Cyan" }
function Write-Warn { param([string]$Msg) Write-Color "[WARN] $Msg" "Yellow" }
function Write-Err { param([string]$Msg) Write-Color "[ERROR] $Msg" "Red" }
# ============================================================================
# TEST RESULT RECORDING
# ============================================================================
function Record-Pass {
param([string]$Name, [string]$Detail = "")
$script:Pass++
$script:Total++
$script:Results += [PSCustomObject]@{ Status="PASS"; Name=$Name; Detail=$Detail }
if ($OutputFormat -eq "tap") {
Write-Host "ok $($script:Total) - $Name$(if($Detail){" ($Detail)"})"
} else {
$mark = if ($NoColor) { "[PASS]" } else { [char]0x2713 }
$msg = " $mark $Name"
if ($Detail) { $msg += " - $Detail" }
Write-Color $msg "Green"
}
}
function Record-Fail {
param([string]$Name, [string]$Detail = "")
$script:Fail++
$script:Total++
$script:Results += [PSCustomObject]@{ Status="FAIL"; Name=$Name; Detail=$Detail }
if ($OutputFormat -eq "tap") {
Write-Host "not ok $($script:Total) - $Name"
if ($Detail) { Write-Host " # $Detail" }
} else {
$mark = if ($NoColor) { "[FAIL]" } else { [char]0x2717 }
$msg = " $mark $Name"
if ($Detail) { $msg += " - $Detail" }
Write-Color $msg "Red"
}
}
function Record-Skip {
param([string]$Name, [string]$Reason = "")
$script:Skip++
$script:Total++
$script:Results += [PSCustomObject]@{ Status="SKIP"; Name=$Name; Detail=$Reason }
if ($OutputFormat -eq "tap") {
Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
} else {
$mark = if ($NoColor) { "[SKIP]" } else { [char]0x2298 }
$msg = " $mark $Name"
if ($Reason) { $msg += " - $Reason" }
Write-Color $msg "Yellow"
}
}
# ============================================================================
# HELPERS
# ============================================================================
function Test-CommandExists {
param([string]$Command)
$null -ne (Get-Command $Command -ErrorAction SilentlyContinue)
}
function Write-Section {
param([string]$Name)
if ($OutputFormat -eq "text") {
Write-Host ""
Write-Color $Name "White"
}
}
# ============================================================================
# AUTO-DETECTION
# ============================================================================
function Resolve-DomainController {
if ($DomainController) {
Write-Verbose "Using specified DC: $DomainController"
return $DomainController
}
try {
$nltest = nltest /dsgetdc: 2>&1
$dcLine = $nltest | Where-Object { $_ -match "DC: \\\\" }
if ($dcLine -match "DC: \\\\(.+)$") {
$detected = $Matches[1].Trim()
Write-Verbose "Auto-detected DC: $detected"
return $detected
}
} catch {}
try {
$dcLocator = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dc = $dcLocator.FindDomainController()
if ($dc.Name) {
Write-Verbose "Auto-detected DC via .NET: $($dc.Name)"
return $dc.Name
}
} catch {}
Write-Err "Cannot auto-detect domain controller. Use -DomainController parameter."
exit 1
}
function Resolve-Domain {
if ($Domain) {
Write-Verbose "Using specified domain: $Domain"
return $Domain
}
if ($env:USERDNSDOMAIN) {
Write-Verbose "Auto-detected domain: $($env:USERDNSDOMAIN)"
return $env:USERDNSDOMAIN
}
try {
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
if ($dom.Name) {
Write-Verbose "Auto-detected domain via .NET: $($dom.Name)"
return $dom.Name
}
} catch {}
Write-Err "Cannot auto-detect domain. Use -Domain parameter."
exit 1
}
# ============================================================================
# TESTS
# ============================================================================
# -- 1. DC Connectivity ----------------------------------------------------
function Test-DCConnectivity {
Write-Section "Connectivity"
try {
$ping = Test-Connection -ComputerName $script:DC -Count 2 -Quiet -ErrorAction Stop
if ($ping) {
Record-Pass "DC connectivity" $script:DC
} else {
Record-Fail "DC connectivity" "$($script:DC) - no response"
}
} catch {
Record-Fail "DC connectivity" "$($script:DC) - $($_.Exception.Message)"
}
}
# -- 2. LDAP Bind -----------------------------------------------------------
function Test-LDAPBind {
Write-Section "LDAP"
try {
$ldapPath = "LDAP://$($script:DC)"
$entry = New-Object System.DirectoryServices.DirectoryEntry($ldapPath)
$searcher = New-Object System.DirectoryServices.DirectorySearcher($entry)
$searcher.SearchScope = "Base"
$searcher.PropertiesToLoad.Add("defaultNamingContext") | Out-Null
$result = $searcher.FindOne()
if ($result) {
$nc = $result.Properties["defaultnamingcontext"][0]
Record-Pass "LDAP bind" "$($script:DC) - $nc"
} else {
Record-Fail "LDAP bind" "$($script:DC) - bind succeeded but no result"
}
$searcher.Dispose()
$entry.Dispose()
} catch {
Record-Fail "LDAP bind" "$($script:DC) - $($_.Exception.Message)"
}
}
# -- 3. LDAPS Connectivity --------------------------------------------------
function Test-LDAPS {
try {
$tcp = Test-NetConnection -ComputerName $script:DC -Port 636 -WarningAction SilentlyContinue -ErrorAction Stop
if ($tcp.TcpTestSucceeded) {
Record-Pass "LDAPS connectivity" "$($script:DC):636"
} else {
Record-Fail "LDAPS connectivity" "$($script:DC):636 - port closed"
}
} catch {
if (Test-CommandExists "Test-NetConnection") {
Record-Fail "LDAPS connectivity" "$($script:DC):636 - $($_.Exception.Message)"
} else {
Record-Skip "LDAPS connectivity" "Test-NetConnection not available"
}
}
}
# -- 4. DNS SRV Records ----------------------------------------------------
function Test-DNSSrvRecords {
Write-Section "DNS"
$srvRecord = "_ldap._tcp.dc._msdcs.$($script:DomainFQDN)"
try {
if (Test-CommandExists "Resolve-DnsName") {
$results = Resolve-DnsName -Name $srvRecord -Type SRV -ErrorAction Stop
if ($results) {
$count = ($results | Where-Object { $_.QueryType -eq "SRV" }).Count
Record-Pass "DNS SRV records" "$srvRecord - $count record(s)"
} else {
Record-Fail "DNS SRV records" "$srvRecord - no records returned"
}
} else {
$nslookup = nslookup -type=srv $srvRecord 2>&1
if ($nslookup -match "svr hostname") {
Record-Pass "DNS SRV records" $srvRecord
} else {
Record-Fail "DNS SRV records" "$srvRecord - lookup failed"
}
}
} catch {
Record-Fail "DNS SRV records" "$srvRecord - $($_.Exception.Message)"
}
}
# -- 5. AD Replication ------------------------------------------------------
function Test-ADReplication {
Write-Section "Replication"
$adModuleLoaded = Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
if (-not $adModuleLoaded) {
try { Import-Module ActiveDirectory -ErrorAction Stop } catch {}
$adModuleLoaded = Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
}
if ($adModuleLoaded) {
try {
$partners = Get-ADReplicationPartnerMetadata -Target $script:DC -ErrorAction Stop
$failures = $partners | Where-Object { $_.ConsecutiveReplicationFailures -gt 0 }
if ($failures) {
$failCount = ($failures | Measure-Object).Count
Record-Fail "AD replication status" "$failCount partner(s) with failures"
} else {
$partnerCount = ($partners | Measure-Object).Count
Record-Pass "AD replication status" "$partnerCount partner(s), no failures"
}
return
} catch {
Write-Verbose "Get-ADReplicationPartnerMetadata failed: $($_.Exception.Message)"
}
}
if (Test-CommandExists "repadmin") {
try {
$output = repadmin /replsummary $script:DC 2>&1 | Out-String
if ($output -match "failed") {
Record-Fail "AD replication status" "repadmin reports failures"
} elseif ($output -match "Source DSA") {
Record-Pass "AD replication status" "repadmin reports no failures"
} else {
Record-Pass "AD replication status" "repadmin completed"
}
} catch {
Record-Fail "AD replication status" "repadmin error - $($_.Exception.Message)"
}
} else {
Record-Skip "AD replication status" "neither AD module nor repadmin available"
}
}
# -- 6. FSMO Roles ----------------------------------------------------------
function Test-FSMORoles {
Write-Section "FSMO"
if (Test-CommandExists "netdom") {
try {
$output = netdom query fsmo 2>&1 | Out-String
$roles = @(
"Schema master",
"Domain naming master",
"PDC",
"RID pool manager",
"Infrastructure master"
)
$found = 0
foreach ($role in $roles) {
if ($output -match "$role\s+\S+") {
$found++
}
}
if ($found -eq 5) {
Record-Pass "FSMO roles assigned" "all 5 roles located"
} elseif ($found -gt 0) {
Record-Fail "FSMO roles assigned" "only $found of 5 roles found"
} else {
Record-Fail "FSMO roles assigned" "no roles found in netdom output"
}
} catch {
Record-Fail "FSMO roles assigned" "netdom error - $($_.Exception.Message)"
}
} else {
$adModuleLoaded = Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
if ($adModuleLoaded) {
try {
$forest = Get-ADForest -ErrorAction Stop
$domain = Get-ADDomain -ErrorAction Stop
$roleCount = 0
if ($forest.SchemaMaster) { $roleCount++ }
if ($forest.DomainNamingMaster) { $roleCount++ }
if ($domain.PDCEmulator) { $roleCount++ }
if ($domain.RIDMaster) { $roleCount++ }
if ($domain.InfrastructureMaster) { $roleCount++ }
if ($roleCount -eq 5) {
Record-Pass "FSMO roles assigned" "all 5 roles located via AD module"
} else {
Record-Fail "FSMO roles assigned" "only $roleCount of 5 roles found"
}
} catch {
Record-Fail "FSMO roles assigned" "AD module error - $($_.Exception.Message)"
}
} else {
Record-Skip "FSMO roles assigned" "neither netdom nor AD module available"
}
}
}
# -- 7. SYSVOL Share --------------------------------------------------------
function Test-SYSVOLShare {
Write-Section "Shares"
$sysvolPath = "\\$($script:DC)\SYSVOL"
try {
if (Test-Path $sysvolPath -ErrorAction Stop) {
Record-Pass "SYSVOL share accessible" $sysvolPath
} else {
Record-Fail "SYSVOL share accessible" "$sysvolPath - not accessible"
}
} catch {
Record-Fail "SYSVOL share accessible" "$sysvolPath - $($_.Exception.Message)"
}
}
# -- 8. NETLOGON Share ------------------------------------------------------
function Test-NETLOGONShare {
$netlogonPath = "\\$($script:DC)\NETLOGON"
try {
if (Test-Path $netlogonPath -ErrorAction Stop) {
Record-Pass "NETLOGON share accessible" $netlogonPath
} else {
Record-Fail "NETLOGON share accessible" "$netlogonPath - not accessible"
}
} catch {
Record-Fail "NETLOGON share accessible" "$netlogonPath - $($_.Exception.Message)"
}
}
# -- 9. Kerberos -----------------------------------------------------------
function Test-Kerberos {
Write-Section "Authentication"
try {
$secure = Test-ComputerSecureChannel -ErrorAction Stop
if ($secure) {
Record-Pass "Kerberos secure channel" "secure channel OK"
} else {
Record-Fail "Kerberos secure channel" "secure channel broken"
}
} catch {
if (Test-CommandExists "klist") {
try {
$klist = klist 2>&1 | Out-String
if ($klist -match "krbtgt") {
Record-Pass "Kerberos secure channel" "TGT present (klist)"
} else {
Record-Fail "Kerberos secure channel" "no TGT found (klist)"
}
} catch {
Record-Fail "Kerberos secure channel" "klist error - $($_.Exception.Message)"
}
} else {
Record-Fail "Kerberos secure channel" $_.Exception.Message
}
}
}
# -- 10. Group Policy -------------------------------------------------------
function Test-GroupPolicy {
if (Test-CommandExists "gpresult") {
try {
$output = gpresult /r 2>&1 | Out-String
if ($output -match "Applied Group Policy Objects" -or $output -match "Last time Group Policy was applied") {
$lastApplied = ""
if ($output -match "Last time Group Policy was applied:\s*(.+)") {
$lastApplied = $Matches[1].Trim()
}
Record-Pass "Group Policy processing" $(if ($lastApplied) { "last applied: $lastApplied" } else { "policy applied" })
} elseif ($output -match "ERROR\b|Access is denied") {
Record-Fail "Group Policy processing" "gpresult returned an error"
} else {
Record-Fail "Group Policy processing" "could not confirm policy application"
}
} catch {
Record-Fail "Group Policy processing" "gpresult error - $($_.Exception.Message)"
}
} else {
Record-Skip "Group Policy processing" "gpresult not available"
}
}
# -- 11. AD Web Services (ADWS) --------------------------------------------
function Test-ADWS {
Write-Section "Services"
try {
$tcp = Test-NetConnection -ComputerName $script:DC -Port 9389 -WarningAction SilentlyContinue -ErrorAction Stop
if ($tcp.TcpTestSucceeded) {
Record-Pass "AD Web Services" "$($script:DC):9389"
} else {
Record-Fail "AD Web Services" "$($script:DC):9389 - port closed"
}
} catch {
if (Test-CommandExists "Test-NetConnection") {
Record-Fail "AD Web Services" "$($script:DC):9389 - $($_.Exception.Message)"
} else {
Record-Skip "AD Web Services" "Test-NetConnection not available"
}
}
}
# -- 12. Domain Trusts ------------------------------------------------------
function Test-DomainTrusts {
Write-Section "Trusts"
if (Test-CommandExists "nltest") {
try {
$output = nltest /trusted_domains 2>&1 | Out-String
if ($output -match "The command completed successfully") {
$trustLines = ($output -split "`n") | Where-Object {
$_ -match "\S" -and
$_ -notmatch "The command completed" -and
$_ -notmatch "^$"
}
$trustCount = ($trustLines | Measure-Object).Count
Record-Pass "Domain trusts" "$trustCount trust(s) enumerated"
} elseif ($output -match "ERROR_NO_SUCH_DOMAIN") {
Record-Fail "Domain trusts" "domain not found"
} else {
Record-Fail "Domain trusts" "nltest failed"
}
} catch {
Record-Fail "Domain trusts" "nltest error - $($_.Exception.Message)"
}
} else {
$adModuleLoaded = Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
if ($adModuleLoaded) {
try {
$trusts = Get-ADTrust -Filter * -ErrorAction Stop
$trustCount = ($trusts | Measure-Object).Count
Record-Pass "Domain trusts" "$trustCount trust(s) found"
} catch {
Record-Fail "Domain trusts" "Get-ADTrust error - $($_.Exception.Message)"
}
} else {
Record-Skip "Domain trusts" "neither nltest nor AD module available"
}
}
}
# -- 13. Time Sync ----------------------------------------------------------
function Test-TimeSync {
Write-Section "Time"
if (Test-CommandExists "w32tm") {
try {
$output = w32tm /monitor /computers:$($script:DC) /nowarn 2>&1 | Out-String
if ($output -match "NTP:\s*([+-]?\d+\.\d+)s") {
$skew = [math]::Abs([double]$Matches[1])
$skewStr = "{0:N2}" -f $skew
if ($skew -le 5.0) {
Record-Pass "Time sync" "skew ${skewStr}s against $($script:DC)"
} else {
Record-Fail "Time sync" "skew ${skewStr}s exceeds 5s threshold"
}
} elseif ($output -match "error|timeout|unreachable" ) {
Record-Fail "Time sync" "w32tm could not reach $($script:DC)"
} else {
Record-Pass "Time sync" "w32tm completed against $($script:DC)"
}
} catch {
Record-Fail "Time sync" "w32tm error - $($_.Exception.Message)"
}
} else {
Record-Skip "Time sync" "w32tm not available"
}
}
# ============================================================================
# OUTPUT
# ============================================================================
function Write-Header {
if ($OutputFormat -eq "tap") {
Write-Host "TAP version 13"
} else {
Write-Host ""
Write-Color "Windows AD Smoke Tests" "White"
Write-Host "DC: $($script:DC)"
Write-Host "Domain: $($script:DomainFQDN)"
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
}
}
function Write-Summary {
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
if ($OutputFormat -eq "tap") {
Write-Host "1..$($script:Total)"
Write-Host "# pass $($script:Pass)"
Write-Host "# fail $($script:Fail)"
Write-Host "# skip $($script:Skip)"
} else {
Write-Host ""
$separator = [string]::new([char]0x2500, 40)
Write-Color $separator "White"
Write-Color "Summary $($script:DC) ($($script:DomainFQDN))" "White"
$summaryLine = " $($script:Pass) passed $($script:Fail) failed $($script:Skip) skipped (${duration}s)"
Write-Host $summaryLine
Write-Color $separator "White"
if ($script:Fail -eq 0) {
Write-Color "All tests passed." "Green"
} else {
Write-Color "$($script:Fail) test(s) failed." "Red"
}
}
}
# ============================================================================
# MAIN
# ============================================================================
# Resolve targets
$script:DC = Resolve-DomainController
$script:DomainFQDN = Resolve-Domain
Write-Header
# Run all tests
Test-DCConnectivity
Test-LDAPBind
Test-LDAPS
Test-DNSSrvRecords
Test-ADReplication
Test-FSMORoles
Test-SYSVOLShare
Test-NETLOGONShare
Test-Kerberos
Test-GroupPolicy
Test-ADWS
Test-DomainTrusts
Test-TimeSync
Write-Summary
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }