Files
linux-scripts/windows-iis-smoke-tests.ps1
T
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

603 lines
20 KiB
PowerShell

###############################################################################
#### windows-iis-smoke-tests.ps1 — Verify IIS health ####
#### Checks W3SVC, WAS, app pools, site responses, SSL certs, ####
#### bindings, ARR, URL Rewrite, logs, FREB, and event log. ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version: 1.0 ####
#### ####
#### Usage: .\windows-iis-smoke-tests.ps1 ####
#### .\windows-iis-smoke-tests.ps1 -SiteNames "Default Web Site" ####
#### .\windows-iis-smoke-tests.ps1 -OutputFormat tap ####
#### ####
#### See -Help for all options. ####
###############################################################################
[CmdletBinding()]
param(
[string[]]$SiteNames = @(),
[int]$CertExpiryDays = 30,
[string]$TestUrl = "",
[ValidateSet("text","tap")]
[string]$OutputFormat = "text",
[switch]$NoColor,
[switch]$Help
)
$ErrorActionPreference = "Continue"
# ============================================================================
# HELP
# ============================================================================
if ($Help) {
@"
Usage: .\windows-iis-smoke-tests.ps1 [OPTIONS]
Smoke-test IIS (Internet Information Services). PowerShell 5.1+.
Requires the WebAdministration module (IIS Management Tools).
Parameters:
-SiteNames "Site1","Site2" Sites to test (default: all sites)
-CertExpiryDays N Warn if cert expires within N days (default: 30)
-TestUrl URL Optional specific URL to test
-OutputFormat FORMAT Output: text (default), tap
-NoColor Disable coloured output
-Verbose Show debug output
-Help Show this help
Examples:
.\windows-iis-smoke-tests.ps1
.\windows-iis-smoke-tests.ps1 -SiteNames "Default Web Site","IntranetApp"
.\windows-iis-smoke-tests.ps1 -CertExpiryDays 60 -OutputFormat tap
.\windows-iis-smoke-tests.ps1 -TestUrl "https://intranet.corp.local/health"
.\windows-iis-smoke-tests.ps1 -NoColor
"@
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 Write-Section {
param([string]$Name)
if ($OutputFormat -eq "text") {
Write-Host ""
Write-Color $Name "White"
}
}
function Get-TargetSites {
if ($SiteNames.Count -gt 0) {
return @(Get-Website | Where-Object { $SiteNames -contains $_.Name })
}
return @(Get-Website)
}
# ============================================================================
# TESTS
# ============================================================================
# -- 1. W3SVC Service -------------------------------------------------------
function Test-W3SVC {
Write-Section "Services"
try {
$svc = Get-Service -Name W3SVC -ErrorAction Stop
if ($svc.Status -eq "Running") {
Record-Pass "W3SVC service running"
} else {
Record-Fail "W3SVC service running" "status: $($svc.Status)"
}
} catch {
Record-Fail "W3SVC service running" "service not found"
}
}
# -- 2. WAS Service ----------------------------------------------------------
function Test-WAS {
try {
$svc = Get-Service -Name WAS -ErrorAction Stop
if ($svc.Status -eq "Running") {
Record-Pass "WAS service running"
} else {
Record-Fail "WAS service running" "status: $($svc.Status)"
}
} catch {
Record-Fail "WAS service running" "service not found"
}
}
# -- 3. Application Pool States ---------------------------------------------
function Test-AppPoolStates {
Write-Section "Application Pools"
try {
$pools = @(Get-WebAppPoolState -ErrorAction Stop)
if ($pools.Count -eq 0) {
Record-Skip "application pools" "no pools found"
return
}
$started = @($pools | Where-Object { $_.Value -eq "Started" })
$stopped = @($pools | Where-Object { $_.Value -ne "Started" })
foreach ($p in $started) {
$poolName = ($p.ItemXPath -replace ".*@name='([^']+)'.*", '$1')
Record-Pass "app pool started" $poolName
}
foreach ($p in $stopped) {
$poolName = ($p.ItemXPath -replace ".*@name='([^']+)'.*", '$1')
Record-Fail "app pool stopped" "$poolName state: $($p.Value)"
}
} catch {
Record-Fail "application pools" $_.Exception.Message
}
}
# -- 4. Site HTTP Responses --------------------------------------------------
function Test-SiteResponses {
Write-Section "Site Responses"
$sites = Get-TargetSites
if ($sites.Count -eq 0) {
Record-Skip "site responses" "no sites found"
return
}
foreach ($site in $sites) {
if ($site.State -ne "Started") {
Record-Fail "site responds ($($site.Name))" "site not started"
continue
}
$bindings = @($site.Bindings.Collection)
if ($bindings.Count -eq 0) {
Record-Skip "site responds ($($site.Name))" "no bindings"
continue
}
$binding = $bindings[0]
$proto = $binding.protocol
$info = $binding.bindingInformation -split ":"
$port = if ($info.Count -ge 2) { $info[1] } else { if ($proto -eq "https") { "443" } else { "80" } }
$host_ = if ($info.Count -ge 3 -and $info[2]) { $info[2] } else { "localhost" }
$url = "${proto}://${host_}:${port}/"
try {
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
Record-Pass "site responds ($($site.Name))" "$url HTTP $($resp.StatusCode)"
} catch {
$code = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { "timeout" }
if ($code -ge 200 -and $code -lt 500) {
Record-Pass "site responds ($($site.Name))" "$url HTTP $code"
} else {
Record-Fail "site responds ($($site.Name))" "$url HTTP $code"
}
}
}
}
# -- 5. SSL Certificate Validation ------------------------------------------
function Test-SSLCerts {
Write-Section "SSL Certificates"
$sites = Get-TargetSites
$httpsBindings = @()
foreach ($site in $sites) {
foreach ($b in $site.Bindings.Collection) {
if ($b.protocol -eq "https" -and $b.certificateHash) {
$httpsBindings += [PSCustomObject]@{
SiteName = $site.Name
Hash = $b.certificateHash
Store = if ($b.certificateStoreName) { $b.certificateStoreName } else { "My" }
Binding = $b.bindingInformation
}
}
}
}
if ($httpsBindings.Count -eq 0) {
Record-Skip "SSL certificates" "no HTTPS bindings found"
return
}
$checked = @{}
foreach ($hb in $httpsBindings) {
if ($checked.ContainsKey($hb.Hash)) { continue }
$checked[$hb.Hash] = $true
try {
$cert = Get-ChildItem "Cert:\LocalMachine\$($hb.Store)\$($hb.Hash)" -ErrorAction Stop
$daysLeft = [math]::Floor(($cert.NotAfter - (Get-Date)).TotalDays)
$cn = $cert.Subject -replace "CN=", "" -replace ",.*", ""
if ($cert.NotAfter -lt (Get-Date)) {
Record-Fail "SSL certificate ($cn)" "EXPIRED on $($cert.NotAfter.ToString('yyyy-MM-dd'))"
} elseif ($daysLeft -lt $CertExpiryDays) {
Record-Fail "certificate expiry ($cn)" "$daysLeft days remaining (threshold: $CertExpiryDays)"
} else {
Record-Pass "SSL certificate valid ($cn)" "expires $($cert.NotAfter.ToString('yyyy-MM-dd')), $daysLeft days"
}
} catch {
Record-Fail "SSL certificate ($($hb.SiteName))" "cert hash $($hb.Hash) not found in $($hb.Store) store"
}
}
}
# -- 6. Site Bindings --------------------------------------------------------
function Test-SiteBindings {
Write-Section "Configuration"
$sites = Get-TargetSites
$totalBindings = 0
$validBindings = 0
foreach ($site in $sites) {
foreach ($b in $site.Bindings.Collection) {
$totalBindings++
$info = $b.bindingInformation
$proto = $b.protocol
if ($proto -in @("http","https") -and $info -match "^\*?:?\d+:") {
$validBindings++
} elseif ($proto -in @("net.tcp","net.pipe","net.msmq","msmq.formatname")) {
$validBindings++
}
}
}
if ($totalBindings -eq 0) {
Record-Skip "site bindings" "no bindings configured"
} elseif ($validBindings -eq $totalBindings) {
Record-Pass "site bindings valid" "$($sites.Count) sites, $totalBindings bindings"
} else {
$invalid = $totalBindings - $validBindings
Record-Fail "site bindings" "$invalid invalid out of $totalBindings bindings"
}
}
# -- 7. Default Document ----------------------------------------------------
function Test-DefaultDocument {
$sites = Get-TargetSites
if ($sites.Count -eq 0) { return }
foreach ($site in $sites) {
try {
$docs = Get-WebConfiguration -Filter "system.webServer/defaultDocument/files/add" -PSPath "IIS:\Sites\$($site.Name)" -ErrorAction Stop
if ($docs -and @($docs).Count -gt 0) {
$first = @($docs)[0].value
Record-Pass "default document ($($site.Name))" $first
} else {
Record-Fail "default document ($($site.Name))" "none configured"
}
} catch {
Record-Skip "default document ($($site.Name))" "could not read config"
}
}
}
# -- 8. Custom URL Test -----------------------------------------------------
function Test-CustomUrl {
if (-not $TestUrl) { return }
Write-Section "Custom URL"
try {
$resp = Invoke-WebRequest -Uri $TestUrl -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
Record-Pass "custom URL" "$TestUrl HTTP $($resp.StatusCode)"
} catch {
$code = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { "timeout" }
Record-Fail "custom URL" "$TestUrl HTTP $code"
}
}
# -- 9. ARR Health -----------------------------------------------------------
function Test-ARRHealth {
Write-Section "Modules"
try {
$arr = Get-WebGlobalModule -Name "ApplicationRequestRouting" -ErrorAction Stop
if (-not $arr) {
Record-Skip "ARR health" "ARR not installed"
return
}
} catch {
Record-Skip "ARR health" "ARR not installed"
return
}
try {
$farms = Get-WebConfiguration -Filter "webFarms/webFarm" -PSPath "MACHINE/WEBROOT/APPHOST" -ErrorAction Stop
if (-not $farms -or @($farms).Count -eq 0) {
Record-Pass "ARR health" "ARR installed, no server farms configured"
return
}
foreach ($farm in @($farms)) {
$farmName = $farm.name
$servers = @($farm.Collection)
$healthy = @($servers | Where-Object { $_.enabled -eq $true })
if ($healthy.Count -eq $servers.Count) {
Record-Pass "ARR farm ($farmName)" "$($servers.Count) servers enabled"
} else {
$disabled = $servers.Count - $healthy.Count
Record-Fail "ARR farm ($farmName)" "$disabled of $($servers.Count) servers disabled"
}
}
} catch {
Record-Fail "ARR health" $_.Exception.Message
}
}
# -- 10. URL Rewrite Module -------------------------------------------------
function Test-URLRewrite {
try {
$mod = Get-WebGlobalModule -Name "RewriteModule" -ErrorAction Stop
if ($mod) {
Record-Pass "URL Rewrite module installed"
} else {
Record-Skip "URL Rewrite module" "not installed"
}
} catch {
Record-Skip "URL Rewrite module" "not installed"
}
}
# -- 11. Log Directory -------------------------------------------------------
function Test-LogDirectory {
Write-Section "Logging"
try {
$logDir = (Get-WebConfigurationProperty -Filter "system.applicationHost/sites/siteDefaults/logFile" -Name "directory" -ErrorAction Stop).Value
$expanded = [System.Environment]::ExpandEnvironmentVariables($logDir)
if (Test-Path $expanded) {
try {
$testFile = Join-Path $expanded ".iis-smoke-test-$(Get-Random).tmp"
[IO.File]::WriteAllText($testFile, "test")
Remove-Item $testFile -Force
Record-Pass "log directory accessible" $expanded
} catch {
Record-Fail "log directory writable" "$expanded - not writable"
}
} else {
Record-Fail "log directory exists" "$expanded - not found"
}
} catch {
Record-Skip "log directory" "could not read log configuration"
}
}
# -- 12. Failed Request Tracing ----------------------------------------------
function Test-FREB {
$sites = Get-TargetSites
$frebFound = $false
foreach ($site in $sites) {
try {
$tracing = Get-WebConfiguration -Filter "system.webServer/tracing/traceFailedRequests" -PSPath "IIS:\Sites\$($site.Name)" -ErrorAction Stop
if ($tracing -and @($tracing).Count -gt 0) {
$frebFound = $true
$frebDir = "$($site.TraceFailedRequestsLogging.Directory)"
if ($frebDir -and (Test-Path ([System.Environment]::ExpandEnvironmentVariables($frebDir)))) {
Record-Pass "Failed Request Tracing ($($site.Name))" "enabled, logs accessible"
} else {
Record-Pass "Failed Request Tracing ($($site.Name))" "enabled"
}
}
} catch { }
}
if (-not $frebFound) {
Record-Skip "Failed Request Tracing" "FREB not enabled on any site"
}
}
# -- 13. Event Log -----------------------------------------------------------
function Test-EventLog {
Write-Section "Event Log"
try {
$events = @()
$sources = @("IIS", "W3SVC", "WAS", "Microsoft-Windows-IIS*")
foreach ($src in $sources) {
try {
$filter = @{
LogName = "System","Application"
Level = 2
StartTime = (Get-Date).AddHours(-24)
}
$found = @(Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue |
Where-Object { $_.ProviderName -like "*IIS*" -or $_.ProviderName -like "W3SVC" -or $_.ProviderName -like "WAS" })
$events += $found
} catch { }
}
if ($events.Count -eq 0) {
Record-Pass "event log clean" "0 IIS errors in last 24h"
} else {
$latest = $events[0]
$msg = $latest.Message.Substring(0, [math]::Min(80, $latest.Message.Length))
Record-Fail "event log" "$($events.Count) IIS error(s) in 24h — latest: $msg"
}
} catch {
if ($_.Exception.Message -match "No events were found") {
Record-Pass "event log clean" "0 IIS errors in last 24h"
} else {
Record-Fail "event log" $_.Exception.Message
}
}
}
# ============================================================================
# OUTPUT
# ============================================================================
function Write-Header {
if ($OutputFormat -eq "tap") {
Write-Host "TAP version 13"
} else {
Write-Host ""
Write-Color "Windows IIS Smoke Tests" "White"
Write-Host "Server: $env:COMPUTERNAME"
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 $env:COMPUTERNAME" "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
# ============================================================================
Write-Header
# Load WebAdministration module
try {
Import-Module WebAdministration -ErrorAction Stop
Write-Verbose "WebAdministration module loaded"
} catch {
Write-Err "WebAdministration module not available. Install IIS Management Tools."
exit 1
}
# Run all tests
Test-W3SVC
Test-WAS
Test-AppPoolStates
Test-SiteResponses
Test-SSLCerts
Test-SiteBindings
Test-DefaultDocument
Test-CustomUrl
Test-ARRHealth
Test-URLRewrite
Test-LogDirectory
Test-FREB
Test-EventLog
Write-Summary
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }