???param([switch]$Preview=$true,[switch]$Execute) # Paths $Root = "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $BugKbDir = Join-Path $Root "bug_kb" $BugKbPath = Join-Path $BugKbDir "BUG_KB.json.txt" # JSON stocke dans un .txt pour TXT-ONLY $BootpackPath = Join-Path (Join-Path $Root "bootpack") "bootpack.txt" $UpdatesDir = Join-Path $Root "updates\2025\10\bugs" $LogsDir = Join-Path $Root "logs" $ArchiveRoot = Join-Path $Root "threads_archive" $Stage = "C:\Temp_Gouvernance" # Policy $Iso = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK" $Policy = "TXT-ONLY v1.0; SAFE-WRITE v1.1; GOV_SCRIPT_GATE v1.3" $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) foreach($d in @($BugKbDir,$UpdatesDir,$LogsDir,$ArchiveRoot,$Stage)){ if(!(Test-Path $d)){ New-Item -ItemType Directory -Force -Path $d | Out-Null } } # Helpers function Get-FileSHA256([string]$p){ $sha=[Security.Cryptography.SHA256]::Create() $fs=[IO.File]::Open($p,'Open','Read','ReadWrite') try{([BitConverter]::ToString($sha.ComputeHash($fs))).Replace('-','').ToLower()}finally{$fs.Dispose();$sha.Dispose()} } function Write-SafeTxt([string]$Dest,[string[]]$Lines,[string]$SourceTag){ if(!(Test-Path (Split-Path $Dest))){ New-Item -ItemType Directory -Force -Path (Split-Path $Dest)|Out-Null } if(!(Test-Path $Stage)){ New-Item -ItemType Directory -Force -Path $Stage|Out-Null } $footer=@('','--- DOC-VERSION-FOOTER ---',"Generated: $Iso",'SHA-256: ',"Policy: $Policy","Source: "+$SourceTag) $stageFile = Join-Path $Stage ([IO.Path]::GetFileName($Dest)) [IO.File]::WriteAllText($stageFile,(($Lines+$footer) -join "`r`n"),$Utf8NoBom) $h0=Get-FileSHA256 $stageFile [IO.File]::WriteAllText($stageFile,([IO.File]::ReadAllText($stageFile,$Utf8NoBom).Replace('SHA-256: ',"SHA-256: $h0")),$Utf8NoBom) $h1=Get-FileSHA256 $stageFile $tmp="$Dest.tmp"; Copy-Item $stageFile $tmp -Force if((Get-FileSHA256 $tmp) -ne $h1){ throw "SAFE-WRITE mismatch for $Dest" } if($Preview -and -not $Execute){ Remove-Item $tmp -Force; return @{Path=$Dest;Hash=$h1;Preview=$true} } if(Test-Path $Dest){ Copy-Item $Dest ($Dest+'.bak_'+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force } Move-Item $tmp $Dest -Force @{Path=$Dest;Hash=$h1;Preview=$false} } function Load-BugKb([string]$Path){ if(!(Test-Path $Path)){ return [pscustomobject]@{ entries=@(); updated=$Iso } } $raw = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop try{ $obj = $raw | ConvertFrom-Json if($obj.entries){ return $obj } if($obj.issues){ return [pscustomobject]@{ entries=$obj.issues; updated=$Iso } } return [pscustomobject]@{ entries=@(); updated=$Iso } } catch { # JSON corrompu: on sauve un backup et on repart propre Copy-Item $Path ($Path+'.corrupt_'+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force return [pscustomobject]@{ entries=@(); updated=$Iso } } } function Save-BugKb([object]$Obj,[string]$Path){ $Obj.updated = $Iso $json = $Obj | ConvertTo-Json -Depth 10 $lines = $json -split "`r?`n" Write-SafeTxt -Dest $Path -Lines $lines -SourceTag "BUG_KB_UPDATE" } function Merge-ById([array]$Old,[array]$New){ $map=@{} foreach($e in $Old){ if($e.id){ $map[$e.id]=$e } } foreach($n in $New){ if(-not $n.id){ continue }; if($map.ContainsKey($n.id)){ # merge simple: on preserve les anciens champs et on met a jour blocking/note/tags/workaround $m=$map[$n.id] foreach($k in @('title','blocking','note','workaround','tags','seen_in_threads')){ if($n.PSObject.Properties.Name -contains $k){ $m | Add-Member -NotePropertyName $k -NotePropertyValue $n.$k -Force } } $map[$n.id]=$m } else { $map[$n.id]=$n } } return @($map.Values) } function Update-Bootpack-BugJson([string]$BootpackPath,[string]$JsonString){ if(!(Test-Path $BootpackPath)){ return $null } $bp = Get-Content -LiteralPath $BootpackPath -Raw $nl = "`r`n" $start = [regex]::Match($bp,'(?ms)^\[BUG_KB_JSON\]\s*') if($start.Success){ $before = $bp.Substring(0,$start.Index + $start.Length) $afterMatch = [regex]::Match($bp.Substring($start.Index + $start.Length),'(?ms)^\[\w[^\]]*\]') if($afterMatch.Success){ $afterIdx = $start.Index + $start.Length + $afterMatch.Index $new = $before + $JsonString + $nl + $bp.Substring($afterIdx) } else { $new = $before + $JsonString + $nl } } else { $new = $bp.TrimEnd() + $nl + "[BUG_KB_JSON]" + $nl + $JsonString + $nl } $tmp = "$BootpackPath.tmp" [IO.File]::WriteAllText($tmp,$new,$Utf8NoBom) $h0 = Get-FileSHA256 $tmp if($Preview -and -not $Execute){ Remove-Item $tmp -Force; return @{Path=$BootpackPath;Hash=$h0;Preview=$true} } if(Test-Path $BootpackPath){ Copy-Item $BootpackPath ($BootpackPath+'.bak_'+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force } Move-Item $tmp $BootpackPath -Force return @{Path=$BootpackPath;Hash=$h0;Preview=$false} } # 1) Charger KB existante $kb = Load-BugKb -Path $BugKbPath # 2) Nouvelles entrees a consigner (les 3 soucis rencontres) $new = @( [pscustomobject]@{ id='KB-PS1-ENCODING-UTF8BOM' title='Windows PowerShell 5.1 parsing requires UTF-8 with BOM or pure ASCII for .ps1' blocking=$false workaround='Save .ps1 as UTF-8 BOM; avoid typographic punctuation; or run in pwsh Core.' tags=@('powershell','encoding','utf8-bom','compat') seen_in_threads=@('2025-10-18') }, [pscustomobject]@{ id='KB-PS1-TYPO-PUNCT' title='Typographic quotes and em/en dash break PowerShell parser when file is read as ANSI' blocking=$false workaround='Normalize to ASCII: replace curly quotes and dashes and NBSP; keep scripts ASCII-safe.' tags=@('powershell','quotes','emdash','ansi') seen_in_threads=@('2025-10-18') }, [pscustomobject]@{ id='KB-EXEC-POLICY-BYPASS' title='ExecutionPolicy AllSigned blocks unsigned scripts on network shares' blocking=$false workaround='Use -ExecutionPolicy Bypass per-process; generate .cmd wrappers that call powershell.exe -ExecutionPolicy Bypass' tags=@('powershell','executionpolicy','bypass','netshare') seen_in_threads=@('2025-10-18') } ) # 3) Fusion idempotente $kb.entries = Merge-ById -Old $kb.entries -New $new # 4) Ecrire KB (TXT-ONLY JSON dans .txt) $rKB = Save-BugKb -Obj $kb -Path $BugKbPath # 5) Synchroniser la section [BUG_KB_JSON] du bootpack avec la JSON actuelle $jsonForBootpack = ($kb | ConvertTo-Json -Depth 10) $rBP = Update-Bootpack-BugJson -BootpackPath $BootpackPath -JsonString $jsonForBootpack # 6) Ecrire un update horodate dans updates/YYYY/MM/bugs $updPath = Join-Path $UpdatesDir ("BUG_UPDATE_"+(Get-Date -Format 'yyyy-MM-dd')+".txt") $updBody = @( "BUG_UPDATE = "+(Get-Date -Format 'yyyy-MM-dd'), "EntriesAddedOrUpdated = "+($new.id -join ','), "Policy = "+$Policy, "BugKbPath = "+$BugKbPath, "BootpackPath = "+$BootpackPath ) $rUPD = Write-SafeTxt -Dest $updPath -Lines $updBody -SourceTag "BUG_KB_UPDATE" # 7) Archive snapshot minimal dans threads_archive (TXT-ONLY) $day=(Get-Date -Format 'yyyy-MM-dd'); $t=(Get-Date -Format 'HHmmss') $archDir = Join-Path (Join-Path $ArchiveRoot $day) $t $archBody = @( "ArchivedAt = "+$Iso, "Type = BUG_KB_UPDATE", "BugIds = "+($new.id -join ','), "Policy = "+$Policy ) $rARCH = Write-SafeTxt -Dest (Join-Path $archDir 'bug_kb_update_snapshot.txt') -Lines $archBody -SourceTag "BUG_KB_UPDATE" # 8) Log $logPath = Join-Path $LogsDir 'registry_activity.log' $log = @(); if(Test-Path $logPath){ $log += Get-Content -LiteralPath $logPath -ErrorAction SilentlyContinue } $log += "[{0}] bugkb_update: KB={1} ; bootpack={2} ; update={3} ; archive={4}" -f $Iso,$rKB.Path,$rBP.Path,$rUPD.Path,$rARCH.Path Write-SafeTxt -Dest $logPath -Lines $log -SourceTag "BUG_KB_UPDATE" | Out-Null # Preview recap "=== PREVIEW apply_bugkb_update_and_archive ===" "KB: $($rKB.Path) preview=$($rKB.Preview)" "Bootpack:$($rBP.Path) preview=$($rBP.Preview)" "Update: $($rUPD.Path) preview=$($rUPD.Preview)" "Archive: $($rARCH.Path) preview=$($rARCH.Preview)"