Files
linux-scripts/windows-container-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

638 lines
22 KiB
PowerShell

###############################################################################
#### windows-container-smoke-tests.ps1 — Verify Windows container health ####
#### Checks Docker/containerd service, container lifecycle, isolation ####
#### modes, NAT networking, port mapping, volumes, and event logs. ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version: 1.0 ####
#### ####
#### Usage: .\windows-container-smoke-tests.ps1 ####
#### .\windows-container-smoke-tests.ps1 -ContainerRuntime docker ####
#### .\windows-container-smoke-tests.ps1 -OutputFormat tap ####
#### ####
#### See -Help for all options. ####
###############################################################################
[CmdletBinding()]
param(
[ValidateSet("docker","containerd")]
[string]$ContainerRuntime = "docker",
[string]$TestImage = "mcr.microsoft.com/windows/nanoserver:ltsc2022",
[ValidateSet("text","tap")]
[string]$OutputFormat = "text",
[switch]$NoColor,
[switch]$Help
)
$ErrorActionPreference = "Continue"
# ============================================================================
# HELP
# ============================================================================
if ($Help) {
@"
Usage: .\windows-container-smoke-tests.ps1 [OPTIONS]
Smoke-test Windows container infrastructure. PowerShell 5.1+.
Designed for Windows Server or Windows 10/11 with containers enabled.
Parameters:
-ContainerRuntime RT Container runtime: docker (default), containerd
-TestImage IMAGE Test image (default: mcr.microsoft.com/windows/nanoserver:ltsc2022)
-OutputFormat FORMAT Output: text (default), tap
-NoColor Disable coloured output
-Verbose Show debug output
-Help Show this help
Examples:
.\windows-container-smoke-tests.ps1
.\windows-container-smoke-tests.ps1 -ContainerRuntime containerd
.\windows-container-smoke-tests.ps1 -TestImage mcr.microsoft.com/windows/servercore:ltsc2022
.\windows-container-smoke-tests.ps1 -OutputFormat tap -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 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"
}
}
function Remove-TestContainer {
param([string]$Name)
try { docker rm -f $Name 2>&1 | Out-Null } catch {}
}
function Test-DockerReady {
param([string]$TestName, [switch]$NeedImage)
if (-not (Test-CommandExists "docker")) {
Record-Skip $TestName "docker command not found"; return $false
}
if ($NeedImage -and -not $script:ImageAvailable) {
Record-Skip $TestName "test image not available"; return $false
}
return $true
}
$script:ImageAvailable = $false
$script:TestContainerPrefix = "smoketest-container"
# ============================================================================
# TESTS
# ============================================================================
# -- 1. Container Service Running -------------------------------------------
function Test-ContainerService {
Write-Section "Service"
$serviceName = if ($ContainerRuntime -eq "containerd") { "containerd" } else { "docker" }
try {
$svc = Get-Service -Name $serviceName -ErrorAction Stop
if ($svc.Status -eq "Running") {
Record-Pass "Container service running" "$serviceName ($($svc.Status))"
} else {
Record-Fail "Container service running" "$serviceName is $($svc.Status)"
}
} catch {
Record-Fail "Container service running" "$serviceName service not found"
}
}
# -- 2. Daemon Responding ---------------------------------------------------
function Test-DaemonHealth {
if ($ContainerRuntime -eq "containerd") {
if (Test-CommandExists "ctr") {
try {
$output = ctr version 2>&1 | Out-String
if ($output -match "Version:" -or $output -match "Revision:") {
Record-Pass "Daemon responding" "containerd responding"
} else {
Record-Fail "Daemon responding" "ctr version returned unexpected output"
}
} catch {
Record-Fail "Daemon responding" "ctr error - $($_.Exception.Message)"
}
} else {
Record-Fail "Daemon responding" "ctr command not found"
}
return
}
if (-not (Test-CommandExists "docker")) {
Record-Fail "Daemon responding" "docker command not found"
return
}
try {
$output = docker info --format '{{.ServerVersion}}' 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $output -match "\d+\.\d+") {
Record-Pass "Daemon responding" "Docker Engine v$($output.Trim())"
} else {
Record-Fail "Daemon responding" "Docker daemon not responding"
}
} catch {
Record-Fail "Daemon responding" "docker error - $($_.Exception.Message)"
}
}
# -- 3. Container Runtime Mode ----------------------------------------------
function Test-RuntimeMode {
Write-Section "Runtime"
if ($ContainerRuntime -eq "containerd") {
Record-Skip "Container runtime mode" "mode detection not applicable to containerd"
return
}
if (-not (Test-DockerReady "Container runtime mode")) { return }
try {
$info = docker info 2>&1 | Out-String
if ($info -match "OSType:\s*windows") {
Record-Pass "Container runtime mode" "Windows containers"
} elseif ($info -match "OSType:\s*linux") {
Record-Fail "Container runtime mode" "Linux containers mode - switch to Windows containers"
} else {
Record-Fail "Container runtime mode" "unable to determine container mode"
}
} catch {
Record-Fail "Container runtime mode" "docker info error - $($_.Exception.Message)"
}
}
# -- 4. Image Pull -----------------------------------------------------------
function Test-ImagePull {
Write-Section "Images"
if (-not (Test-DockerReady "Image pull")) { return }
try {
# Check if image already exists locally
$existing = docker images --format '{{.Repository}}:{{.Tag}}' 2>&1 | Out-String
if ($existing -match [regex]::Escape($TestImage)) {
$script:ImageAvailable = $true
Record-Pass "Image pull" "$TestImage (already present)"
return
}
# Attempt pull
$pullOutput = docker pull $TestImage 2>&1 | Out-String
if ($LASTEXITCODE -eq 0) {
$script:ImageAvailable = $true
Record-Pass "Image pull" $TestImage
} elseif ($pullOutput -match "timeout|network|unreachable|no match") {
Record-Skip "Image pull" "network unavailable or image not found"
} else {
Record-Fail "Image pull" "pull failed - $($pullOutput.Trim())"
}
} catch {
Record-Skip "Image pull" "pull error - $($_.Exception.Message)"
}
}
# -- 5. Container Lifecycle --------------------------------------------------
function Test-ContainerLifecycle {
Write-Section "Lifecycle"
if (-not (Test-DockerReady "Container lifecycle" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-lifecycle"
Remove-TestContainer $name
try {
# Create
docker create --name $name $TestImage cmd /c "echo smoketest" 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Record-Fail "Container lifecycle" "create failed"
Remove-TestContainer $name
return
}
docker start $name 2>&1 | Out-Null
Start-Sleep -Seconds 2
docker stop $name -t 5 2>&1 | Out-Null
docker rm $name 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) {
Record-Pass "Container lifecycle" "create, start, stop, remove"
} else {
Record-Fail "Container lifecycle" "remove failed"
}
} catch {
Record-Fail "Container lifecycle" $_.Exception.Message
Remove-TestContainer $name
}
}
# -- 6. Process Isolation Mode -----------------------------------------------
function Test-ProcessIsolation {
Write-Section "Isolation"
if (-not (Test-DockerReady "Process isolation mode" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-process"
Remove-TestContainer $name
try {
$output = docker run --rm --isolation=process --name $name $TestImage cmd /c "echo process-ok" 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $output -match "process-ok") {
Record-Pass "Process isolation mode" "process isolation available"
} elseif ($output -match "not supported|not available|HCS") {
Record-Skip "Process isolation mode" "not supported on this host (requires Windows Server)"
} else {
Record-Fail "Process isolation mode" "unexpected result - $($output.Trim())"
}
} catch {
Record-Fail "Process isolation mode" $_.Exception.Message
} finally {
Remove-TestContainer $name
}
}
# -- 7. Hyper-V Isolation Mode -----------------------------------------------
function Test-HyperVIsolation {
if (-not (Test-DockerReady "Hyper-V isolation mode" -NeedImage)) { return }
# Check if Hyper-V is installed (client vs server detection)
$hypervEnabled = $false
try {
$hyperv = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -ErrorAction Stop
$hypervEnabled = ($hyperv.State -eq "Enabled")
} catch {
try {
$hypervRole = Get-WindowsFeature -Name Hyper-V -ErrorAction Stop
$hypervEnabled = $hypervRole.Installed
} catch {}
}
if (-not $hypervEnabled) {
Record-Skip "Hyper-V isolation mode" "Hyper-V not installed"
return
}
$name = "$($script:TestContainerPrefix)-hyperv"
Remove-TestContainer $name
try {
$output = docker run --rm --isolation=hyperv --name $name $TestImage cmd /c "echo hyperv-ok" 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $output -match "hyperv-ok") {
Record-Pass "Hyper-V isolation mode" "Hyper-V isolation available"
} elseif ($output -match "not supported|not available|HCS") {
Record-Skip "Hyper-V isolation mode" "Hyper-V isolation not available"
} else {
Record-Fail "Hyper-V isolation mode" "unexpected result - $($output.Trim())"
}
} catch {
Record-Fail "Hyper-V isolation mode" $_.Exception.Message
} finally {
Remove-TestContainer $name
}
}
# -- 8. NAT Network Exists ---------------------------------------------------
function Test-NATNetwork {
Write-Section "Networking"
if (-not (Test-DockerReady "NAT network exists")) { return }
try {
$networks = docker network ls --format '{{.Name}}' 2>&1 | Out-String
if ($networks -match "(?m)^nat$") {
Record-Pass "NAT network exists" "default nat network present"
} else {
Record-Fail "NAT network exists" "nat network not found"
}
} catch {
Record-Fail "NAT network exists" $_.Exception.Message
}
}
# -- 9. Outbound Connectivity -----------------------------------------------
function Test-OutboundConnectivity {
if (-not (Test-DockerReady "Container outbound connectivity" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-outbound"
Remove-TestContainer $name
try {
$output = docker run --rm --name $name $TestImage cmd /c "ping -n 1 8.8.8.8" 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $output -match "Reply from|bytes=") {
Record-Pass "Container outbound connectivity" "ping to 8.8.8.8 succeeded"
} elseif ($output -match "timed out|unreachable|could not find|General failure") {
Record-Fail "Container outbound connectivity" "no outbound connectivity"
} else {
Record-Fail "Container outbound connectivity" "exit code $LASTEXITCODE"
}
} catch {
Record-Fail "Container outbound connectivity" $_.Exception.Message
} finally {
Remove-TestContainer $name
}
}
# -- 10. Port Mapping --------------------------------------------------------
function Test-PortMapping {
if (-not (Test-DockerReady "Port mapping" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-portmap"
$hostPort = 48199
Remove-TestContainer $name
try {
docker run -d --rm --name $name -p "${hostPort}:80" $TestImage cmd /c "ping -n 30 127.0.0.1 >nul" 2>&1 | Out-Null
Start-Sleep -Seconds 3
$state = docker inspect --format '{{.State.Running}}' $name 2>&1 | Out-String
if ($state.Trim() -eq "true") {
$portInfo = docker port $name 2>&1 | Out-String
if ($portInfo -match "$hostPort" -or $portInfo -match "80/tcp") {
Record-Pass "Port mapping" "port $hostPort mapped to container"
} else {
Record-Pass "Port mapping" "container running with port binding"
}
} else {
Record-Fail "Port mapping" "container did not stay running for port test"
}
} catch {
Record-Fail "Port mapping" $_.Exception.Message
} finally {
Remove-TestContainer $name
}
}
# -- 11. Volume Mount --------------------------------------------------------
function Test-VolumeMount {
Write-Section "Storage"
if (-not (Test-DockerReady "Volume mount" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-volume"
$tempDir = Join-Path $env:TEMP "container-smoke-test-vol"
Remove-TestContainer $name
try {
if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force }
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
Set-Content -Path (Join-Path $tempDir "testfile.txt") -Value "smoke-test-data"
$output = docker run --rm --name $name -v "${tempDir}:C:\testmount" $TestImage cmd /c "type C:\testmount\testfile.txt" 2>&1 | Out-String
if ($LASTEXITCODE -eq 0 -and $output -match "smoke-test-data") {
Record-Pass "Volume mount" "bind mount read successful"
} else {
Record-Fail "Volume mount" "bind mount failed - $($output.Trim())"
}
} catch {
Record-Fail "Volume mount" $_.Exception.Message
} finally {
Remove-TestContainer $name
if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# -- 12. Container Logs ------------------------------------------------------
function Test-ContainerLogs {
Write-Section "Logging"
if (-not (Test-DockerReady "Container logs" -NeedImage)) { return }
$name = "$($script:TestContainerPrefix)-logs"
Remove-TestContainer $name
try {
docker run --name $name $TestImage cmd /c "echo log-output-test" 2>&1 | Out-Null
Start-Sleep -Seconds 1
$logs = docker logs $name 2>&1 | Out-String
if ($logs -match "log-output-test") {
Record-Pass "Container logs" "docker logs returned expected output"
} else {
Record-Fail "Container logs" "docker logs did not return expected output"
}
} catch {
Record-Fail "Container logs" $_.Exception.Message
} finally {
Remove-TestContainer $name
}
}
# -- 13. Image List ----------------------------------------------------------
function Test-ImageList {
if (-not (Test-DockerReady "Image list")) { return }
try {
$images = docker images --format '{{.Repository}}:{{.Tag}}' 2>&1
$imageCount = ($images | Where-Object { $_ -match "\S" -and $_ -notmatch "ERROR" } | Measure-Object).Count
if ($imageCount -gt 0) {
Record-Pass "Image list" "$imageCount image(s)"
} else {
Record-Fail "Image list" "no images found"
}
} catch {
Record-Fail "Image list" $_.Exception.Message
}
}
# -- 14. Event Log -----------------------------------------------------------
function Test-EventLog {
Write-Section "Event Log"
try {
$containerErrors = @()
foreach ($logName in @('System', 'Application')) {
try {
$events = Get-WinEvent -FilterHashtable @{
LogName = $logName
Level = 2 # Error
StartTime = (Get-Date).AddHours(-24)
} -ErrorAction Stop | Where-Object {
$_.ProviderName -match "docker|container|Hyper-V" -or
$_.Message -match "container|docker|containerd"
}
if ($events) { $containerErrors += $events }
} catch [Exception] {
if ($_.Exception.Message -notmatch "No events were found") {
Write-Verbose "$logName log query error: $($_.Exception.Message)"
}
}
}
$errorCount = ($containerErrors | Measure-Object).Count
if ($errorCount -eq 0) {
Record-Pass "Event log" "no container errors in last 24h"
} else {
$latest = $containerErrors | Sort-Object TimeCreated -Descending | Select-Object -First 1
$detail = "$errorCount error(s) in last 24h - latest: $($latest.ProviderName) - $($latest.Message.Substring(0, [Math]::Min(80, $latest.Message.Length)))"
Record-Fail "Event log" $detail
}
} catch {
Record-Skip "Event log" "unable to query event logs - $($_.Exception.Message)"
}
}
# ============================================================================
# OUTPUT
# ============================================================================
function Write-Header {
if ($OutputFormat -eq "tap") {
Write-Host "TAP version 13"
} else {
Write-Host ""
Write-Color "Windows Container Smoke Tests" "White"
Write-Host "Runtime: $ContainerRuntime"
Write-Host "Image: $TestImage"
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 $ContainerRuntime ($TestImage)" "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
# Run all tests
Test-ContainerService
Test-DaemonHealth
Test-RuntimeMode
Test-ImagePull
Test-ContainerLifecycle
Test-ProcessIsolation
Test-HyperVIsolation
Test-NATNetwork
Test-OutboundConnectivity
Test-PortMapping
Test-VolumeMount
Test-ContainerLogs
Test-ImageList
Test-EventLog
Write-Summary
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }