initial commit

This commit is contained in:
2026-09-06 02:03:36 +01:00
commit 57d2f3619e
42 changed files with 3379 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
# ============================================================
# Write-MigrationReport.ps1
# ============================================================
# Writes a unified CSV report for all processed servers
# (Windows + Linux) and optionally a rollback JSON file.
# Also contains Write-RollbackFile (Task 10).
# ============================================================
function Write-MigrationReport {
<#
.SYNOPSIS
Writes a unified CSV migration report for all processed servers.
.DESCRIPTION
Accepts an array of result objects from Set-WindowsDns and
Invoke-LinuxDns. Writes to CSV with -Append so re-runs don't
overwrite prior results. Prints a summary table to the console.
.PARAMETER Results
Array of PSCustomObjects returned by the DNS change functions.
.PARAMETER OutputPath
Full path to the CSV file. Created if it does not exist.
.PARAMETER Append
If set, appends to an existing CSV. Default: $true
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[PSCustomObject[]]$Results,
[Parameter(Mandatory)]
[string]$OutputPath,
[switch]$NoAppend
)
# Ensure output directory exists
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
}
# Normalise results to CSV-safe objects (strip internal _RollbackData field)
$csvRows = foreach ($r in $Results) {
[PSCustomObject]@{
Hostname = $r.Hostname
ResolvedIP = $r.ResolvedIP
OS = $r.OS
Distro = $r.Distro
OldDNS = $r.OldDNS
NewDNS = $r.NewDNS
ConnectionMethod = $r.ConnectionMethod
ChangeStatus = $r.ChangeStatus
ValidationStatus = $r.ValidationStatus
ModifiedFiles = $r.ModifiedFiles
ServiceRestarted = $r.ServiceRestarted
ErrorMessage = $r.ErrorMessage
DryRun = $r.DryRun
Timestamp = $r.Timestamp
}
}
# Write CSV
if ($NoAppend -or -not (Test-Path $OutputPath)) {
$csvRows | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
}
else {
$csvRows | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -Append
}
# ── Console summary table ─────────────────────────────────────────────────
$total = $Results.Count
$success = @($Results | Where-Object { $_.ChangeStatus -eq "Success" }).Count
$dryRun = @($Results | Where-Object { $_.ChangeStatus -eq "DryRun" }).Count
$failed = @($Results | Where-Object { $_.ChangeStatus -eq "Failed" }).Count
$connFailed = @($Results | Where-Object { $_.ChangeStatus -eq "ConnectionFailed" }).Count
$skipped = @($Results | Where-Object { $_.ChangeStatus -eq "Skipped" }).Count
$valSuccess = @($Results | Where-Object { $_.ValidationStatus -eq "Success" }).Count
$valFailed = @($Results | Where-Object { $_.ValidationStatus -eq "ValidationFailed" }).Count
Write-Host ""
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host " DNS MIGRATION REPORT SUMMARY" -ForegroundColor Cyan
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host (" Total servers processed : {0}" -f $total)
Write-Host (" DNS change success : {0}" -f $success) -ForegroundColor Green
Write-Host (" DNS change failed : {0}" -f $failed) -ForegroundColor $(if ($failed -gt 0) { "Red" } else { "White" })
Write-Host (" Connection failed : {0}" -f $connFailed) -ForegroundColor $(if ($connFailed -gt 0) { "Red" } else { "White" })
Write-Host (" Skipped (unresolvable) : {0}" -f $skipped) -ForegroundColor $(if ($skipped -gt 0) { "Yellow" } else { "White" })
Write-Host (" Dry run (no changes) : {0}" -f $dryRun) -ForegroundColor $(if ($dryRun -gt 0) { "Cyan" } else { "White" })
Write-Host "───────────────────────────────────────────────────────────────" -ForegroundColor Cyan
Write-Host (" Validation success : {0}" -f $valSuccess) -ForegroundColor Green
Write-Host (" Validation failed : {0}" -f $valFailed) -ForegroundColor $(if ($valFailed -gt 0) { "Yellow" } else { "White" })
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host (" Report written to: {0}" -f $OutputPath) -ForegroundColor Gray
Write-Host ""
# ── Per-server table (truncated to 80 chars per row for readability) ───────
$colWidth = @{ H=32; S=18; V=18; E=30 }
$header = "{0,-$($colWidth.H)} {1,-$($colWidth.S)} {2,-$($colWidth.V)} {3}" `
-f "Hostname", "ChangeStatus", "ValidationStatus", "Error/Note"
$divider = "-" * ($colWidth.H + $colWidth.S + $colWidth.V + $colWidth.E + 3)
Write-Host $divider
Write-Host $header
Write-Host $divider
foreach ($r in $Results | Sort-Object OS, Hostname) {
$changeColor = switch ($r.ChangeStatus) {
"Success" { "Green" }
"DryRun" { "Cyan" }
"Failed" { "Red" }
"ConnectionFailed"{ "Red" }
"Skipped" { "Yellow" }
default { "White" }
}
$valColor = switch ($r.ValidationStatus) {
"Success" { "Green" }
"ValidationFailed"{ "Yellow" }
"DryRun_OK" { "Cyan" }
"DryRun_NoResolve"{ "Yellow" }
default { "Gray" }
}
$hn = $r.Hostname.PadRight($colWidth.H).Substring(0, [Math]::Min($r.Hostname.Length, $colWidth.H)).PadRight($colWidth.H)
$st = $r.ChangeStatus.PadRight($colWidth.S)
$vl = $r.ValidationStatus.PadRight($colWidth.V)
$err = if ($r.ErrorMessage) {
$r.ErrorMessage.Substring(0, [Math]::Min($r.ErrorMessage.Length, $colWidth.E))
} else { "" }
Write-Host ("{0} " -f $hn) -NoNewline
Write-Host ("{0} " -f $st) -NoNewline -ForegroundColor $changeColor
Write-Host ("{0} " -f $vl) -NoNewline -ForegroundColor $valColor
Write-Host $err
}
Write-Host $divider
Write-Host ""
}
function Write-RollbackFile {
<#
.SYNOPSIS
Serializes pre-change DNS state to a timestamped JSON rollback file.
.DESCRIPTION
Called BEFORE any DNS changes are made. Captures:
- Windows: NIC name + old DNS IP list
- Linux: modified file paths + base64 ContentBefore + service name
The rollback file is written atomically covering all discovered
servers, even if the migration script is interrupted mid-run.
.PARAMETER Results
Array of result objects. For pre-change capture, these should have
OldDNS populated (from read-only probe) and _RollbackData for Linux.
.PARAMETER Results
Array of result objects from Set-WindowsDns / Invoke-LinuxDns.
Each entry's _RollbackData property carries the Linux file content.
For Windows entries, the NICName is extracted from the ModifiedFiles field.
.PARAMETER OutputDir
Directory to write the rollback JSON file.
.OUTPUTS
Full path to the written rollback JSON file.
#>
[CmdletBinding()]
param(
[PSCustomObject[]]$Results,
[string]$OutputDir = ".\logs"
)
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$filePath = Join-Path $OutputDir "dns-rollback-$timestamp.json"
$entries = [System.Collections.Generic.List[object]]::new()
foreach ($r in $Results) {
if ($r.OS -eq "Windows") {
# Windows rollback: NIC name + old DNS IPs
$nicName = if ($r.ModifiedFiles -match "NIC:(.+)") { $Matches[1] } else { "" }
$entries.Add([ordered]@{
Hostname = $r.Hostname
OS = "Windows"
Distro = $r.Distro
ConnectionMethod = $r.ConnectionMethod
OldDNS = @($r.OldDNS -split ";" | Where-Object { $_ -ne "" })
NewDNS = @($r.NewDNS -split ";" | Where-Object { $_ -ne "" })
NICName = $nicName
ModifiedFiles = @()
ServiceRestarted = ""
ResolvConfBefore = ""
ResolvConfAfter = ""
Timestamp = $r.Timestamp
})
}
elseif ($r.OS -eq "Linux") {
# Linux rollback: full file contents from _RollbackData
$rd = $r._RollbackData
if ($null -ne $rd) {
# Normalise ModifiedFiles to serialisable format
$mf = @($rd.ModifiedFiles | ForEach-Object {
[ordered]@{
Path = $_.Path
ContentBefore = $_.ContentBefore
ContentAfter = $_.ContentAfter
}
})
$entries.Add([ordered]@{
Hostname = $rd.Hostname
OS = "Linux"
Distro = $rd.Distro
ConnectionMethod = $rd.ConnectionMethod
OldDNS = @($rd.OldDNS | Where-Object { $_ -ne "" })
NewDNS = @($rd.NewDNS | Where-Object { $_ -ne "" })
NICName = $rd.NICName
ModifiedFiles = $mf
ServiceRestarted = $rd.ServiceRestarted
ResolvConfBefore = $rd.ResolvConfBefore
ResolvConfAfter = $rd.ResolvConfAfter
Timestamp = $rd.Timestamp
})
}
else {
# No rollback data — record minimal entry so it appears in rollback file
$entries.Add([ordered]@{
Hostname = $r.Hostname
OS = "Linux"
Distro = $r.Distro
ConnectionMethod = "SSH"
OldDNS = @($r.OldDNS -split ";" | Where-Object { $_ -ne "" })
NewDNS = @($r.NewDNS -split ";" | Where-Object { $_ -ne "" })
NICName = ""
ModifiedFiles = @()
ServiceRestarted = $r.ServiceRestarted
ResolvConfBefore = ""
ResolvConfAfter = ""
Timestamp = $r.Timestamp
})
}
}
}
$rollbackData = [ordered]@{
SchemaVersion = "1.0"
CreatedAt = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
TotalServers = $entries.Count
Servers = $entries.ToArray()
}
$rollbackData | ConvertTo-Json -Depth 10 | Set-Content -Path $filePath -Encoding UTF8
Write-Host " Rollback file written: $filePath" -ForegroundColor Green
return $filePath
}
function Read-RollbackFile {
<#
.SYNOPSIS
Reads and validates a rollback JSON file. Returns the parsed object.
.PARAMETER Path
Path to the dns-rollback-<timestamp>.json file.
#>
param(
[Parameter(Mandatory)]
[string]$Path
)
if (-not (Test-Path $Path)) {
throw "Rollback file not found: $Path"
}
try {
$data = Get-Content $Path -Raw -Encoding UTF8 | ConvertFrom-Json
}
catch {
throw "Failed to parse rollback file '$Path': $($_.Exception.Message)"
}
if ($null -eq $data.Servers) {
throw "Rollback file has no 'Servers' array: $Path"
}
Write-Host " Rollback file loaded: $Path" -ForegroundColor Cyan
Write-Host (" Contains {0} server entries (created {1})" -f $data.TotalServers, $data.CreatedAt) -ForegroundColor Gray
return $data
}