Files
pxe-server/scripts/diagnostics/Capture-MclCache.ps1

161 lines
6.7 KiB
PowerShell

<#
.SYNOPSIS
Captures the layout of a Media Creator Lite cache, plus its small
control files, so the PXE import can be checked against what the
current MCL actually writes.
.DESCRIPTION
Run on the PC where Media Creator Lite just FINISHED a run. Run it once
after the Windows 11 Pro run and once after the Windows 11 LTSC run,
before starting the next MCL run (MCL deletes the cache when it starts).
.\Capture-MclCache.ps1 -Label pro
.\Capture-MclCache.ps1 -Label ltsc
It writes, under the Desktop:
mcl-capture-<label>-<timestamp>\
tree.csv every file: path, size, last write time (UTC)
summary.txt tag files and their contents, file counts, totals
files\ copies of the small text files (*.json, *.xml,
*.tag, *.txt, *.ini, *.log, *.cmd, *.ps1, *.bat)
under 5 MB, same relative paths
mcl-capture-<label>-<timestamp>.zip
Nothing large is copied: no WIM, driver, MSU or installer. Text copies
have any Azure SAS signature (sig=...) blanked, so the zip carries no
storage credential. Read-only on the cache; needs no admin rights.
.PARAMETER Label
Short name for this run, for example pro or ltsc.
.PARAMETER Root
The Media Creator folder. Default C:\ProgramData\GEAerospace\MediaCreator.
.PARAMETER OutDir
Where to write the capture. Default: the current user's Desktop, or
this script's folder when there is no Desktop.
#>
param(
[Parameter(Mandatory = $true)][ValidatePattern('^[A-Za-z0-9_-]{1,20}$')][string]$Label,
[string]$Root = "C:\ProgramData\GEAerospace\MediaCreator",
[string]$OutDir = [Environment]::GetFolderPath("Desktop")
)
$ErrorActionPreference = "Stop"
if (-not $OutDir) { $OutDir = $PSScriptRoot } # no Desktop (e.g. run as SYSTEM)
if (-not (Test-Path -LiteralPath $Root -PathType Container)) {
Write-Host "Media Creator folder not found: $Root" -ForegroundColor Red
exit 1
}
$Root = (Resolve-Path -LiteralPath $Root).ProviderPath.TrimEnd('\')
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$dest = Join-Path $OutDir "mcl-capture-$Label-$stamp"
$filesDir = Join-Path $dest "files"
New-Item -ItemType Directory -Path $filesDir -Force | Out-Null
$textExt = @(".json", ".xml", ".tag", ".txt", ".ini", ".log", ".cmd", ".ps1", ".bat")
$maxCopy = 5MB
# Azure SAS signature in a URL or connection string.
$sasPattern = '(?i)(sig=)[^&"''\s<]+'
Write-Host "Reading $Root ..."
$all = @(Get-ChildItem -LiteralPath $Root -Recurse -Force -File -ErrorAction SilentlyContinue)
$rows = foreach ($f in $all) {
[pscustomobject]@{
Path = $f.FullName.Substring($Root.Length + 1)
Length = $f.Length
LastWriteUtc = $f.LastWriteTimeUtc.ToString("yyyy-MM-ddTHH:mm:ssZ")
}
}
$rows | Sort-Object Path | Export-Csv -LiteralPath (Join-Path $dest "tree.csv") -NoTypeInformation -Encoding UTF8
$copied = 0
$redacted = 0
$skippedBig = New-Object System.Collections.Generic.List[string]
$unreadable = New-Object System.Collections.Generic.List[string]
foreach ($f in $all) {
if ($textExt -notcontains $f.Extension.ToLower()) { continue }
$rel = $f.FullName.Substring($Root.Length + 1)
if ($f.Length -gt $maxCopy) { $skippedBig.Add("$rel ($($f.Length) bytes)"); continue }
$target = Join-Path $filesDir $rel
New-Item -ItemType Directory -Path (Split-Path $target -Parent) -Force | Out-Null
# Share mode lets this read the log MCL still has open.
try {
$fs = New-Object System.IO.FileStream($f.FullName, [System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read, ([System.IO.FileShare]::ReadWrite -bor [System.IO.FileShare]::Delete))
try {
$bytes = New-Object byte[] $fs.Length
$read = 0
while ($read -lt $bytes.Length) {
$n = $fs.Read($bytes, $read, $bytes.Length - $read)
if ($n -le 0) { break }
$read += $n
}
if ($read -lt $bytes.Length) { $bytes = $bytes[0..($read - 1)] }
} finally { $fs.Dispose() }
} catch {
$unreadable.Add("$rel ($($_.Exception.InnerException.Message))")
continue
}
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text -match $sasPattern) {
$text = [regex]::Replace($text, $sasPattern, '${1}REDACTED')
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
$redacted++
}
[System.IO.File]::WriteAllBytes($target, $bytes)
$copied++
}
$tags = @($all | Where-Object { $_.Extension -eq ".tag" })
$sum = New-Object System.Collections.Generic.List[string]
$sum.Add("Media Creator Lite cache capture")
$sum.Add("Label: $Label")
$sum.Add("Captured: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')")
$sum.Add("Computer: $env:COMPUTERNAME")
$sum.Add("Root: $Root")
$sum.Add("PowerShell $($PSVersionTable.PSVersion)")
$sum.Add("")
$sum.Add("Files: $($all.Count), total $([math]::Round((($all | Measure-Object Length -Sum).Sum) / 1GB, 2)) GB")
$sum.Add("Text files copied: $copied (SAS signatures blanked in $redacted)")
$sum.Add("")
$sum.Add("Tag files ($($tags.Count)):")
foreach ($t in $tags) {
$content = (Get-Content -LiteralPath $t.FullName -Raw -ErrorAction SilentlyContinue)
if ($content) { $content = $content.Trim() }
$sum.Add(" $($t.FullName.Substring($Root.Length + 1)) [$content] written $($t.LastWriteTime)")
}
if ($tags.Count -eq 0) { $sum.Add(" NONE - the MCL run did not finish") }
$sum.Add("")
$sum.Add("Top-level folders:")
foreach ($g in ($rows | Group-Object { ($_.Path -split '\\')[0..1] -join '\' } | Sort-Object Name)) {
$gb = [math]::Round((($g.Group | Measure-Object Length -Sum).Sum) / 1GB, 2)
$sum.Add(" $($g.Name): $($g.Count) files, $gb GB")
}
if ($unreadable.Count) {
$sum.Add("")
$sum.Add("Text files that could not be read, not copied:")
foreach ($s in $unreadable) { $sum.Add(" $s") }
}
if ($skippedBig.Count) {
$sum.Add("")
$sum.Add("Text files over 5 MB, not copied:")
foreach ($s in $skippedBig) { $sum.Add(" $s") }
}
$sum | Set-Content -LiteralPath (Join-Path $dest "summary.txt") -Encoding UTF8
$zip = "$dest.zip"
Compress-Archive -Path (Join-Path $dest "*") -DestinationPath $zip -Force
Write-Host ""
Write-Host "Captured $($all.Count) files ($copied text files copied)." -ForegroundColor Green
if ($tags.Count -eq 0) {
Write-Host "WARNING: no .tag file. MCL did not finish this run." -ForegroundColor Yellow
}
if ($unreadable.Count) {
Write-Host "$($unreadable.Count) file(s) could not be read; listed in summary.txt." -ForegroundColor Yellow
}
Write-Host "Send back: $zip"