param( [string[]]$Files, [switch]$Quarantine, # renomme *.ps1 -> *.ps1.bad_YYYYMMDD_HHMMSS [switch]$SummaryOnly ) # sortie UTF-8 propre try { [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding } catch {} $here = $PSScriptRoot; if(-not $here){ $here = Split-Path -Parent $MyInvocation.MyCommand.Path } $registry = Split-Path -Parent $here $logsDir = Join-Path $registry 'logs'; if(!(Test-Path $logsDir)){ New-Item -ItemType Directory -Force -Path $logsDir|Out-Null } $ts = Get-Date -Format 'yyyyMMdd_HHmmss' $log = Join-Path $logsDir ('run_ps_linter_'+$ts+'.txt') function Log([string]$m){ Add-Content -LiteralPath $log -Value $m -Encoding UTF8; Write-Host $m } # cibles $targets=@() if($Files){ foreach($f in $Files){ if(Test-Path -LiteralPath $f){ $targets += Get-Item -LiteralPath $f } } } if(-not $targets){ $targets += Get-ChildItem -LiteralPath (Join-Path $registry 'scripts') -Filter '*.ps1' -File -EA SilentlyContinue } $targets = $targets | Sort-Object FullName -Unique # patterns d'erreurs FR ou traces shell $badText = @( 'Parenthèse fermante','Jeton inattendu','Le terminateur','Impossible de lier l''argument', 'Nom de type manquant','ProviderNotFoundException','dans l’expression','CategoryInfo', 'FullyQualifiedErrorId','Au caractère Ligne','Au caractère','ParserError','MissingEnd', 'UnauthorizedAccess','PSSecurityException' ) # guillemets typographiques / espaces insécables $badChars = @([char]0x2018,[char]0x2019,[char]0x201C,[char]0x201D,[char]0x00A0) $failCount=0 Log ('PS LINTER — '+(Get-Date -Format s)) foreach($fi in $targets){ $p=$fi.FullName try{ $raw = Get-Content -LiteralPath $p -Raw -Encoding UTF8 $errors=@() [void][System.Management.Automation.PSParser]::Tokenize($raw, [ref]$errors) $hasParserErrors = ($errors -and $errors.Count -gt 0) $hasBadText = $false foreach($pat in $badText){ if($raw -match [regex]::Escape($pat)){ $hasBadText=$true; break } } $hasBadChars = $false foreach($ch in $badChars){ if($raw.IndexOf($ch) -ge 0){ $hasBadChars=$true; break } } $nonAscii = ($raw.ToCharArray() | Where-Object { [int]$_ -gt 127 }).Count if($hasParserErrors -or $hasBadText -or $hasBadChars){ $failCount++ Log ('[FAIL] '+$p) if($hasParserErrors){ Log (' ParserErrors: '+$errors.Count) } if($hasBadText){ Log (' Suspicious text: detected (FR error fragments or shell traces)') } if($hasBadChars){ Log (' Suspicious chars: curly quotes / NBSP present') } Log (' Non-ASCII chars count: '+$nonAscii) if($Quarantine){ $bak=$p+'.bad_'+(Get-Date -Format 'yyyyMMdd_HHmmss') Rename-Item -LiteralPath $p -NewName (Split-Path -Leaf $bak) -Force Log (' Quarantined -> '+$bak) } } else { Log ('[OK] '+$p+' :: Non-ASCII='+$nonAscii) } } catch { $failCount++ Log ('[ERR] '+$p+' :: '+$_.Exception.Message) } } if(-not $SummaryOnly){ Write-Host ('Log -> '+$log) } exit ($failCount)