?param( [string[]]$Files, [ValidateSet("Rules","BootPack","KB","All")] [string]$Scope="Rules", [switch]$Preview, [switch]$Write, [switch]$RecalcManifest ) $Utf8NoBom = [Text.UTF8Encoding]::new($false) $Cp1252 = [Text.Encoding]::GetEncoding(1252) # Racine _registry depuis l'emplacement du script $scriptPath = $MyInvocation.MyCommand.Definition $scriptDir = Split-Path -Parent $scriptPath $registry = Split-Path -Parent $scriptDir function Ensure-Dir([string]$p){ if($p -and !(Test-Path $p)){ New-Item -ItemType Directory -Force -Path $p|Out-Null } } function ReadBytes([string]$p){ [IO.File]::ReadAllBytes($p) } function HasBomUtf8([byte[]]$b){ $b.Length -ge 3 -and $b[0]-eq 0xEF -and $b[1]-eq 0xBB -and $b[2]-eq 0xBF } function BytesToUtf8NoBom([byte[]]$b){ if(HasBomUtf8 $b){ $b = $b[3..($b.Length-1)] }; $Utf8NoBom.GetString($b) } function NormalizeCRLF([string]$s){ $t=$s -replace "`r`n","`n"; $t=$t -replace "`r","`n"; [string]::Join("`r`n",$t -split "`n") } function MojibakeScore([string]$s){ ([regex]::Matches($s,'?.')).Count + ([regex]::Matches($s,'?.{0,2}')).Count + ([regex]::Matches($s,'?')).Count } function Cp1252ToUtf8([string]$s){ $bytes=$Cp1252.GetBytes($s); $Utf8NoBom.GetString($bytes) } function RecalcManifestSha([string]$raw){ $m=[regex]::Match($raw,'(?ms)^\[SYNC_MANIFEST\]\s*(.*?)(?=^\[|\Z)'); if(-not $m.Success){ return $null } $blk=$m.Groups[1].Value; $coreLines=@() foreach($ln in ($blk -split "`r?`n")){ if($ln -match '^\s*MANIFEST_SHA256\s*:'){ continue }; $coreLines += $ln.TrimEnd() } $core=[string]::Join("`r`n",$coreLines) [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($core))).Replace('-','').ToLower() } function SafeWrite([string]$path,[string]$content){ $bak = $path + '.bak_' + (Get-Date -Format 'yyyyMMdd_HHmmss') if(Test-Path $path){ Copy-Item -LiteralPath $path -Destination $bak -Force } $tmp = $path + '.tmp' [IO.File]::WriteAllText($tmp,$content,$Utf8NoBom) $shaTmp = (Get-FileHash -Algorithm SHA256 -LiteralPath $tmp).Hash Move-Item -LiteralPath $tmp -Destination $path -Force $shaFinal = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash return @{bak=$bak; shaTmp=$shaTmp; shaFinal=$shaFinal} } # Log : append UTF-8 no BOM coh?rent $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $logsDir = Join-Path $registry 'logs'; Ensure-Dir $logsDir $log = Join-Path $logsDir ('run_encoding_guard_'+$ts+'.txt') function LogLine([string]$msg){ Add-Content -LiteralPath $log -Value $msg -Encoding UTF8; Write-Host $msg } # Cibles $targets=@() switch($Scope){ 'Rules' { $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'rules') -Filter '*.txt' -File -ErrorAction SilentlyContinue } 'BootPack'{ $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter '*.txt' -File -ErrorAction SilentlyContinue } 'KB' { $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter 'bootpack_paste.txt' -File -ErrorAction SilentlyContinue } 'All' { $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'rules') -Filter '*.txt' -File -ErrorAction SilentlyContinue $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter '*.txt' -File -ErrorAction SilentlyContinue } } if($Files){ foreach($f in $Files){ if(Test-Path $f){ $targets += Get-Item -LiteralPath $f } } } $targets = $targets | Sort-Object FullName -Unique LogLine ("ENCODING GUARD - "+(Get-Date -Format s)) foreach($fi in $targets){ $p=$fi.FullName try{ $bytes = ReadBytes $p $hadBom = HasBomUtf8 $bytes $txt = BytesToUtf8NoBom $bytes $score = MojibakeScore $txt $action='OK'; $fixed=$txt if($score -gt 3){ $action='FIX: cp1252->utf8'; $fixed = Cp1252ToUtf8 $txt } $norm = NormalizeCRLF $fixed if($norm -ne $fixed){ $action = ($(if($action -eq 'OK'){'FIX: CRLF'}else{$action+' + CRLF'})); $fixed=$norm } if($hadBom){ $action = ($(if($action -eq 'OK'){'FIX: strip BOM'}else{$action+' + strip BOM'})) } $needRecalc=$false if($RecalcManifest -and ([IO.Path]::GetFileName($p) -ieq 'bootpack.txt') -and ($fixed -ne $txt)){ $needRecalc=$true } LogLine ("{0} :: score={1} :: {2}" -f $p,$score,$action) if($action -eq 'OK' -and -not $needRecalc){ if($Preview){ LogLine " (preview) aucune ?criture" } continue } if(-not $Write){ LogLine " (preview) aucune ?criture" continue } if($needRecalc){ $sha = RecalcManifestSha $fixed if($null -ne $sha){ $fixed = [regex]::Replace($fixed,'(?mi)^(MANIFEST_SHA256\s*:\s*)([0-9a-f]+)\s*$', ('$1'+$sha), 1) LogLine (" manifest recalcul? => {0}" -f $sha) } else { LogLine " [WARN] pas de section [SYNC_MANIFEST] d?tect?e; pas de recalcul." } } $res = SafeWrite -path $p -content $fixed LogLine (" Backup: {0}" -f $res.bak) LogLine (" SHA tmp/final: {0} / {1}" -f $res.shaTmp,$res.shaFinal) LogLine " STATUS=WRITE-OK" } catch{ LogLine ("[ERR] {0} :: {1}" -f $p,$_.Exception.Message) } } Write-Host ("Log -> "+$log)