From 821e3179d1b89255ec8a2a7a85a217b0bd0fd3b3 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Sun, 24 May 2026 09:23:53 -0400 Subject: [PATCH] Wax/Trace triad: switch to SHA256 hashes (FIPS-compliant) + separate hash-failure path from copy-failure path Backup-FormtracepakSettings observed 17 Errors on a real shopfloor PC (G5PRTW04ESF / WJF00159 capture) - all of the form: WARNING: Failed to copy ...App.ini: Exception calling ".ctor" with "0" argument(s): "This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms." Cause: Windows FIPS policy is enabled on West Jefferson shopfloor PCs. The per-file Get-FileHash -Algorithm MD5 call throws a hard .NET exception that bypasses -ErrorAction SilentlyContinue (the throw is from the MD5 constructor, not the cmdlet's parameter binder). That exception was caught by the broad try/catch around both Copy-Item + manifest add, producing a misleading "Failed to copy" message even though Copy-Item already succeeded. Net effect: files copied fine, but manifest rows were missing for those files (Install would fall back to its bulk-copy path). Two fixes: - Switch the hash algorithm from MD5 to SHA256 in both Backup (manifest row capture) and Install (Restore-FileItem hash-skip compare). SHA256 is Get-FileHash's default and is FIPS-compliant. Old MD5-hashed backups remain restorable because Install computes hashes fresh from disk at restore time and does not read the Hash column from file_manifest.csv. - Split the broad try/catch in Backup's Copy-ToStaging into two try/catches: the first wraps only Copy-Item (real copy failure -> Errors counter + skip the file), the second wraps only Get-FileHash (hash failure -> log warning, manifest row gets a null Hash and is still recorded). A hash failure no longer pretends the copy failed. - Install's hash compare is wrapped in try/catch too so a hash exception falls through to overwrite-mode rather than crashing the restore. Smoke tested on win11 VM: SHA256 round-trip works (64-char hashes in file_manifest.csv), Backup reports 0 Errors, Install hash-skip path correctly skips Identical files on second-run idempotency check. --- .../scripts/Backup-FormtracepakSettings.ps1 | 30 ++++++++++++++----- .../scripts/Install-FormtracepakSettings.ps1 | 16 ++++++++-- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 index b6986b0..d481f0b 100755 --- a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Backup-FormtracepakSettings.ps1 @@ -221,18 +221,32 @@ function Copy-ToStaging { } Copy-Item -Path $f.FullName -Destination $destFull -Force $counters[$CounterKey]++ - - $manifestRows.Add([PSCustomObject]@{ - RelativePath = $destRelPath - OriginalPath = $f.FullName - SizeBytes = $f.Length - LastModified = $f.LastWriteTime.ToString('o') - Hash = (Get-FileHash $f.FullName -Algorithm MD5 -ErrorAction SilentlyContinue).Hash - }) } catch { Write-Warning " Failed to copy $($f.FullName): $_" $counters.Errors++ + continue } + + # Hash computation runs in its own try so a hash failure (e.g. FIPS + # policy banning MD5, missing file, permission) does NOT roll back + # the copy we just succeeded at. SHA256 is the default and is + # FIPS-compliant; older Backup runs may have written MD5 hashes + # into file_manifest.csv but Install never reads those - it computes + # fresh hashes at restore time for the [SKIP] Identical check. + $hash = $null + try { + $hash = (Get-FileHash -LiteralPath $f.FullName -Algorithm SHA256 -ErrorAction Stop).Hash + } catch { + Write-Warning " Hash skipped for $($f.FullName): $_" + } + + $manifestRows.Add([PSCustomObject]@{ + RelativePath = $destRelPath + OriginalPath = $f.FullName + SizeBytes = $f.Length + LastModified = $f.LastWriteTime.ToString('o') + Hash = $hash + }) } } diff --git a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 index 8de70fc..409436c 100755 --- a/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-waxtrace/scripts/Install-FormtracepakSettings.ps1 @@ -212,9 +212,19 @@ function Restore-FileItem { } if ((Test-Path $DestFile) -and -not $Force) { - $srcHash = (Get-FileHash $SourceFile -Algorithm MD5).Hash - $destHash = (Get-FileHash $DestFile -Algorithm MD5).Hash - if ($srcHash -eq $destHash) { + # SHA256 not MD5: MD5 throws under Windows FIPS policy + # ("This implementation is not part of the Windows Platform FIPS + # validated cryptographic algorithms"). SHA256 is FIPS-compliant + # and is Get-FileHash's default. + $srcHash = $null + $destHash = $null + try { + $srcHash = (Get-FileHash -LiteralPath $SourceFile -Algorithm SHA256 -ErrorAction Stop).Hash + $destHash = (Get-FileHash -LiteralPath $DestFile -Algorithm SHA256 -ErrorAction Stop).Hash + } catch { + Write-Warning " Hash compare failed for ${DestFile}: $_ - falling through to overwrite" + } + if ($srcHash -and $destHash -and $srcHash -eq $destHash) { Write-Host " [SKIP] Identical: $DestFile" -ForegroundColor DarkGray $counters.Skipped++ return