135 lines
5.6 KiB
PowerShell
135 lines
5.6 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]
|
|
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
|
|
$bytes = [System.IO.File]::ReadAllBytes($f.FullName)
|
|
$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 ($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
|
|
}
|
|
Write-Host "Send back: $zip"
|