param( [string[]]$Files, [ValidateSet("Rules","BootPack","KB","Scripts","All")] [string]$Scope="Rules", [switch]$Preview, [switch]$Write, [switch]$RecalcManifest ) . (Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) "encoding_policy_resolver_v1.0.ps1") $Utf8NoBom = [Text.UTF8Encoding]::new($false) $Utf8Bom = [Text.UTF8Encoding]::new($true) $Ascii = [Text.ASCIIEncoding]::new() $Cp1252 = [Text.Encoding]::GetEncoding(1252) function Get-Targets{ $registry = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition) $t=@() switch($Scope){ "Rules" { $t += Get-ChildItem -LiteralPath (Join-Path $registry 'rules') -Filter '*.txt' -File -EA SilentlyContinue } "BootPack"{ $t += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter '*.txt' -File -EA SilentlyContinue } "KB" { $t += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter 'bootpack_paste.txt' -File -EA SilentlyContinue } "Scripts" { $t += Get-ChildItem -LiteralPath (Join-Path $registry 'scripts') -Filter '*.ps1' -File -EA SilentlyContinue $t += Get-ChildItem -LiteralPath (Join-Path $registry 'scripts') -Filter '*.cmd' -File -EA SilentlyContinue $t += Get-ChildItem -LiteralPath (Join-Path $registry 'scripts') -Filter '*.sh' -File -EA SilentlyContinue } "All" { $t += Get-ChildItem -LiteralPath (Join-Path $registry 'rules') -Filter '*.txt' -File -EA SilentlyContinue $t += Get-ChildItem -LiteralPath (Join-Path $registry 'bootpack') -Filter '*.txt' -File -EA SilentlyContinue $t += Get-ChildItem -LiteralPath (Join-Path $registry 'scripts') -Filter '*.ps1','*.cmd','*.sh' -File -EA SilentlyContinue } } if($Files){ foreach($f in $Files){ if(Test-Path $f){ $t += Get-Item -LiteralPath $f } } } $t | Sort-Object FullName -Unique } function HasBomUtf8([byte[]]$b){ $b.Length -ge 3 -and $b[0]-eq 0xEF -and $b[1]-eq 0xBB -and $b[2]-eq 0xBF } function ReadNoBom([byte[]]$b){ if(HasBomUtf8 $b){ $b=$b[3..($b.Length-1)] }; [Text.UTF8Encoding]::new($false).GetString($b) } function Normalize([string]$s,[string]$nl){ $t=$s -replace "`r`n","`n"; $t=$t -replace "`r","`n"; if($nl -eq 'CRLF'){ [string]::Join("`r`n",$t -split "`n") } else { $t } } function MojibakeScore([string]$s){ ([regex]::Matches($s,'Ã.')).Count + ([regex]::Matches($s,'â.{0,2}')).Count + ([regex]::Matches($s,'Â')).Count } function TargetPolicy([string]$path){ $profile = Get-ProjectProfile $ext=([IO.Path]::GetExtension($path) ?? '').ToLowerInvariant() switch($ext){ ".ps1" { @{enc= ($profile -eq 'SEEDBOX') ? 'UTF8BOM' : 'UTF8NOBOM'; nl='CRLF'; ascii=$false; recalc=$false } } ".cmd" { @{enc='ASCII'; nl='CRLF'; ascii=$true; recalc=$false } } ".sh" { @{enc='ASCII'; nl='LF'; ascii=$true; recalc=$false } } default{ @{enc='UTF8NOBOM'; nl='CRLF'; ascii=$false; recalc=([IO.Path]::GetFileName($path) -ieq 'bootpack.txt') } } } } function SafeWrite([string]$p,[string]$text,[string]$enc,[string]$nl){ $text = Normalize $text $nl $e = switch($enc){ 'UTF8BOM'{$Utf8Bom} 'UTF8NOBOM'{$Utf8NoBom} 'ASCII'{$Ascii} default{$Utf8NoBom} } $bak = $p + '.bak_' + (Get-Date -Format 'yyyyMMdd_HHmmss') if(Test-Path $p){ Copy-Item -LiteralPath $p -Destination $bak -Force } $tmp = $p + '.tmp' [IO.File]::WriteAllText($tmp,$text,$e) $shaTmp=(Get-FileHash -Algorithm SHA256 -LiteralPath $tmp).Hash Move-Item -LiteralPath $tmp -Destination $p -Force $shaFin=(Get-FileHash -Algorithm SHA256 -LiteralPath $p).Hash @{bak=$bak; shaTmp=$shaTmp; shaFinal=$shaFin} } function RecalcManifestSha([string]$raw){ $m=[regex]::Match($raw,'(?ms)^\[SYNC_MANIFEST\]\s*(.*?)(?=^\[|\Z)'); if(-not $m.Success){ return $null } $blk=$m.Groups[1].Value $core=@(); foreach($ln in ($blk -split "`r?`n")){ if($ln -match '^\s*MANIFEST_SHA256\s*:'){ continue }; $core += $ln.TrimEnd() } $str=[string]::Join("`r`n",$core) [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($str))).Replace('-','').ToLower() } $targets = Get-Targets $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $log=Join-Path (Join-Path (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition)) 'logs') ('run_encoding_guard_'+$ts+'.txt') Add-Content -LiteralPath $log -Value ("ENCODING GUARD (policy-aware) - "+(Get-Date -Format s)) -Encoding UTF8 foreach($fi in $targets){ $p=$fi.FullName try{ $bytes=[IO.File]::ReadAllBytes($p); $hadBom=HasBomUtf8 $bytes; $txt=ReadNoBom $bytes $score=MojibakeScore $txt $pol=TargetPolicy $p $action='OK'; $fixed=$txt if($score -gt 3){ $action='FIX: cp1252->utf8'; $fixed=[Text.Encoding]::GetEncoding(1252).GetString([Text.Encoding]::GetEncoding(1252).GetBytes($txt)) } $norm = Normalize $fixed $pol.nl if($norm -ne $fixed){ $action = ($(if($action -eq 'OK'){'FIX: NL'} else {$action+' + NL'})); $fixed=$norm } if($pol.enc -eq 'UTF8BOM' -and -not $hadBom){ $action = ($(if($action -eq 'OK'){'FIX: add BOM'} else {$action+' + add BOM'})) } if($pol.enc -eq 'UTF8NOBOM' -and $hadBom){ $action = ($(if($action -eq 'OK'){'FIX: strip BOM'} else {$action+' + strip BOM'})) } if($pol.ascii){ # contrôle caractères non-ASCII $nonAscii = ($fixed.ToCharArray() | Where-Object { [int]$_ -gt 127 }).Count if($nonAscii -gt 0){ $action = ($(if($action -eq 'OK'){'WARN: non-ASCII'} else {$action+' + WARN non-ASCII'})) } } Add-Content -LiteralPath $log -Value ("{0} :: target={1}/{2} :: score={3} :: {4}" -f $p,$pol.enc,$pol.nl,$score,$action) -Encoding UTF8 $needRecalc = ($RecalcManifest -and $pol.recalc -and ($fixed -ne $txt)) if(-not $Write -and -not $needRecalc){ continue } if($needRecalc){ $sha=RecalcManifestSha $fixed if($sha){ $fixed=[regex]::Replace($fixed,'(?mi)^(MANIFEST_SHA256\s*:\s*)([0-9a-f]+)\s*$',('$1'+$sha),1) Add-Content -LiteralPath $log -Value (" manifest recalculé => {0}" -f $sha) -Encoding UTF8 } } if($Write){ $res=SafeWrite -p $p -text $fixed -enc $pol.enc -nl $pol.nl Add-Content -LiteralPath $log -Value (" Backup: {0}" -f $res.bak) -Encoding UTF8 Add-Content -LiteralPath $log -Value (" SHA tmp/final: {0} / {1}" -f $res.shaTmp,$res.shaFinal) -Encoding UTF8 Add-Content -LiteralPath $log -Value (" STATUS=WRITE-OK") -Encoding UTF8 } else { Add-Content -LiteralPath $log -Value (" (preview) aucune écriture") -Encoding UTF8 } } catch { Add-Content -LiteralPath $log -Value ("[ERR] {0} :: {1}" -f $p,$_.Exception.Message) -Encoding UTF8 } } Write-Host ("Log -> "+$log)