<# .SYNOPSIS Patches all Keyence VR-series .exe.config files to opt the app out of .NET FIPS policy enforcement so the EEPROM MD5 calibration check does not throw 'This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms.' Currently approved only for VR-3000 G2. Before running on a production bay, report the PC hostname to GE InfoSec and confirm the bay is on the documented FIPS-exception list. Imaging-time auto-apply (in 09-Setup-Keyence.ps1) gates this script behind model=vr3000. .DESCRIPTION Scans C:\Program Files\KEYENCE and C:\Program Files (x86)\KEYENCE for any .exe.config under VR-3000 G2, VR-5000, VR-6000 (and any other Keyence model that ships in the same root). Injects: as a sibling under . Preserves any existing children (generatePublisherEvidence, useLegacyJit, etc). Idempotent. Re-running is a no-op for already-patched configs. This patch is SCOPED to the Keyence app's CLR. It does NOT disable the OS-wide LSA FIPS policy. Intune / Defender baseline policies that enforce the LSA key remain in effect. .PARAMETER InstallRoots Override the default search roots. By default both C:\Program Files\KEYENCE and C:\Program Files (x86)\KEYENCE are scanned. .PARAMETER WhatIf Show what would be patched without writing any files. .EXAMPLE .\Patch-KeyenceFipsConfigs.ps1 .\Patch-KeyenceFipsConfigs.ps1 -WhatIf .\Patch-KeyenceFipsConfigs.ps1 -InstallRoots 'D:\KEYENCE' .NOTES Affected EXEs (Profilometer3.exe, VRAnalyzer*.exe, VRInspection_*.exe, etc.) must be restarted after the patch for the new config to take effect. #> [CmdletBinding(SupportsShouldProcess)] param( [string[]]$InstallRoots = @( 'C:\Program Files\KEYENCE', 'C:\Program Files (x86)\KEYENCE' ) ) $ErrorActionPreference = 'Continue' $patched = 0 $skipped = 0 $errors = 0 foreach ($root in $InstallRoots) { if (-not (Test-Path -LiteralPath $root)) { Write-Host "Root not present, skipping: $root" continue } Write-Host "Scanning: $root" $configs = Get-ChildItem -LiteralPath $root -Filter '*.exe.config' -Recurse -ErrorAction SilentlyContinue foreach ($cfg in $configs) { try { [xml]$xml = Get-Content -LiteralPath $cfg.FullName $cfgRoot = $xml.configuration if (-not $cfgRoot) { Write-Warning " [SKIP] No root: $($cfg.FullName)" $errors++ continue } $rt = $cfgRoot.SelectSingleNode('runtime') if (-not $rt) { $rt = $xml.CreateElement('runtime') $cfgRoot.AppendChild($rt) | Out-Null } $existing = $rt.SelectSingleNode("enforceFIPSPolicy[@enabled='false']") if ($existing) { Write-Host " [SKIP] already patched: $($cfg.FullName)" $skipped++ continue } # Remove any other enforceFIPSPolicy variants (enabled=true or no attr). $rt.SelectNodes('enforceFIPSPolicy') | ForEach-Object { $rt.RemoveChild($_) | Out-Null } $fips = $xml.CreateElement('enforceFIPSPolicy') $fips.SetAttribute('enabled', 'false') $rt.AppendChild($fips) | Out-Null if ($PSCmdlet.ShouldProcess($cfg.FullName, 'Inject enforceFIPSPolicy')) { $xml.Save($cfg.FullName) Write-Host " [OK] patched: $($cfg.FullName)" $patched++ } else { Write-Host " [WHATIF] would patch: $($cfg.FullName)" } } catch { Write-Warning " [ERR] $($cfg.FullName): $_" $errors++ } } } Write-Host "" Write-Host "Summary: patched=$patched skipped=$skipped errors=$errors" Write-Host "" Write-Host "Restart Keyence apps (Profilometer3.exe, VRAnalyzer*.exe, VRLauncher.exe," Write-Host "VRInspection_*.exe, etc.) for the new config to take effect. No reboot needed."