Update psscriptanalyzer-sarif-reports.yml #21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PowerShell Corporate Lint (PSScriptAnalyzer + SARIF + Reports) [Report-Only] | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - "**/*.ps1" | |
| - "**/*.psm1" | |
| - "**/*.psd1" | |
| - ".psscriptanalyzer.psd1" | |
| - ".github/workflows/psscriptanalyzer-sarif-reports.yml" | |
| pull_request: | |
| branches: [main, develop] | |
| paths: | |
| - "**/*.ps1" | |
| - "**/*.psm1" | |
| - "**/*.psd1" | |
| - ".psscriptanalyzer.psd1" | |
| - ".github/workflows/psscriptanalyzer-sarif-reports.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: powershell-lint-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| security-events: write | |
| jobs: | |
| psa-analyze: | |
| name: PSScriptAnalyzer Report-Only (SARIF + Artifacts) | |
| runs-on: windows-latest | |
| timeout-minutes: 35 | |
| env: | |
| PSA_VERSION: "1.24.0" | |
| SETTINGS_FILE: ".psscriptanalyzer.psd1" | |
| OUT_DIR: "lint-reports" | |
| OUT_JSON: "psa-results.json" | |
| OUT_CSV: "psa-results.csv" | |
| OUT_MD: "psa-results.md" | |
| OUT_SARIF: "psa-results.sarif" | |
| OUT_FAILTXT: "psa-invocation-failed.txt" | |
| OUT_SUMMARY: "psa-summary.txt" | |
| ANALYZE_ROOTS: >- | |
| BlueTeam-Tools | |
| Core-ScriptLibrary | |
| ITSM-Templates-SVR | |
| ITSM-Templates-WKS | |
| ProSuite-Hub | |
| SysAdmin-Tools | |
| EXCLUDE_SUBTREE: "SysAdmin-Tools/GroupPolicyObjects-Templates" | |
| PRUNE_DIR_REGEX: '(?i)[\\/](\.git|node_modules|dist|build|artifacts|\.next)[\\/]' | |
| SARIF_CATEGORY: "powershell/psscriptanalyzer" | |
| steps: | |
| - name: Checkout (sparse) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| sparse-checkout-cone-mode: false | |
| sparse-checkout: | | |
| BlueTeam-Tools | |
| Core-ScriptLibrary | |
| ITSM-Templates-SVR | |
| ITSM-Templates-WKS | |
| ProSuite-Hub | |
| SysAdmin-Tools | |
| .psscriptanalyzer.psd1 | |
| !SysAdmin-Tools/GroupPolicyObjects-Templates | |
| - name: Git long paths | |
| shell: pwsh | |
| run: | | |
| git config --global core.longpaths true | |
| - name: Prepare output folder | |
| shell: pwsh | |
| run: | | |
| $outDir = Join-Path $env:GITHUB_WORKSPACE $env:OUT_DIR | |
| New-Item -ItemType Directory -Force -Path $outDir | Out-Null | |
| - name: Initialize SARIF baseline (always create file) | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $outDir = Join-Path $env:GITHUB_WORKSPACE $env:OUT_DIR | |
| $sarifOut = Join-Path $outDir $env:OUT_SARIF | |
| New-Item -ItemType Directory -Force -Path $outDir | Out-Null | |
| $baseline = @{ | |
| '$schema' = 'https://json.schemastore.org/sarif-2.1.0.json' | |
| version = '2.1.0' | |
| runs = @(@{ tool = @{ driver = @{ name = 'PSScriptAnalyzer'; version = '0' } }; results = @() }) | |
| } | |
| $baseline | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $sarifOut | |
| Write-Host "Baseline SARIF created: $sarifOut" | |
| - name: Cache PowerShell modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~\Documents\PowerShell\Modules | |
| ~\Documents\WindowsPowerShell\Modules | |
| key: psmodules-${{ runner.os }}-psa-${{ env.PSA_VERSION }} | |
| - name: Install PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $v = $env:PSA_VERSION | |
| $have = Get-Module -ListAvailable PSScriptAnalyzer | | |
| Sort-Object Version -Descending | | |
| Select-Object -First 1 | |
| if (-not $have -or $have.Version.ToString() -ne $v) { | |
| Install-Module PSScriptAnalyzer -RequiredVersion $v -Force -Scope CurrentUser -AllowClobber | |
| } | |
| Import-Module PSScriptAnalyzer -RequiredVersion $v -Force | |
| Write-Host "PSScriptAnalyzer version loaded: $((Get-Module PSScriptAnalyzer).Version)" | |
| - name: Run PSScriptAnalyzer + Build Reports (REPORT-ONLY) | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $outDir = Join-Path $env:GITHUB_WORKSPACE $env:OUT_DIR | |
| New-Item -ItemType Directory -Force -Path $outDir | Out-Null | |
| $jsonOut = Join-Path $outDir $env:OUT_JSON | |
| $csvOut = Join-Path $outDir $env:OUT_CSV | |
| $mdOut = Join-Path $outDir $env:OUT_MD | |
| $sarifOut = Join-Path $outDir $env:OUT_SARIF | |
| $failOut = Join-Path $outDir $env:OUT_FAILTXT | |
| $sumOut = Join-Path $outDir $env:OUT_SUMMARY | |
| $root = $env:GITHUB_WORKSPACE | |
| $prune = [regex]::new($env:PRUNE_DIR_REGEX) | |
| $excludeSub = ($env:EXCLUDE_SUBTREE -replace '/', [IO.Path]::DirectorySeparatorChar) | |
| $excludeSubEsc = [regex]::Escape($excludeSub) | |
| $roots = @() | |
| foreach ($token in ($env:ANALYZE_ROOTS -split '\s+')) { | |
| if ([string]::IsNullOrWhiteSpace($token)) { continue } | |
| $p = Join-Path $root $token | |
| if (Test-Path $p) { $roots += $p } | |
| } | |
| $files = @() | |
| if ($roots.Count -gt 0) { | |
| $files = foreach ($r in $roots) { | |
| Get-ChildItem -Path $r -Recurse -File -Include *.ps1,*.psm1,*.psd1 -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| ($_.FullName -notmatch $prune) -and | |
| ($_.FullName -notmatch $excludeSubEsc) | |
| } | |
| } | |
| } | |
| $files = @($files | Sort-Object FullName -Unique) | |
| $count = @($files).Count | |
| $settingsPath = Join-Path $env:GITHUB_WORKSPACE $env:SETTINGS_FILE | |
| $useSettings = (Test-Path $settingsPath) | |
| $settingsArgs = @{} | |
| if ($useSettings) { $settingsArgs['Settings'] = $settingsPath } | |
| $results = @() | |
| try { | |
| if ($count -gt 0) { | |
| $results = Invoke-ScriptAnalyzer -Path $files.FullName -Recurse:$false @settingsArgs | |
| } else { | |
| "No PowerShell files found to analyze." | Set-Content -Encoding UTF8 $sumOut | |
| } | |
| } | |
| catch { | |
| $_ | Out-String | Set-Content -Encoding UTF8 $failOut | |
| $results = @() | |
| } | |
| if (-not $results) { $results = @() } | |
| # Always write reports (even if empty) | |
| $results | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $jsonOut | |
| $results | | |
| Select-Object RuleName, Severity, Message, ScriptName, Line, Column | | |
| Export-Csv -Path $csvOut -NoTypeInformation -Encoding UTF8 | |
| $bySev = $results | Group-Object Severity | Sort-Object Name | |
| $byRule = $results | Group-Object RuleName | Sort-Object Count -Descending | |
| $md = New-Object System.Collections.Generic.List[string] | |
| $md.Add("# PSScriptAnalyzer Report (Report-Only)") | |
| $md.Add("") | |
| $md.Add("**Files scanned:** $count") | |
| $md.Add("**Settings file used:** $useSettings ($($env:SETTINGS_FILE))") | |
| $md.Add("") | |
| $md.Add("## Findings by Severity") | |
| if ($bySev.Count -eq 0) { $md.Add("- None") } | |
| else { foreach ($g in $bySev) { $md.Add("- **$($g.Name)**: $($g.Count)") } } | |
| $md.Add("") | |
| $md.Add("## Top Rules") | |
| $top = $byRule | Select-Object -First 20 | |
| if ($top.Count -eq 0) { $md.Add("- None") } | |
| else { foreach ($g in $top) { $md.Add("- **$($g.Name)**: $($g.Count)") } } | |
| $md.Add("") | |
| $md.Add("> Note: This workflow is report-only. Findings do not fail the job. Artifacts are uploaded when present.") | |
| $md | Set-Content -Encoding UTF8 $mdOut | |
| $summary = New-Object System.Collections.Generic.List[string] | |
| $summary.Add("PSScriptAnalyzer (Report-Only)") | |
| $summary.Add("Files scanned: $count") | |
| $summary.Add("Settings file used: $useSettings ($($env:SETTINGS_FILE))") | |
| $summary.Add("Total findings: $(@($results).Count)") | |
| foreach ($g in $bySev) { $summary.Add(" $($g.Name): $($g.Count)") } | |
| $summary | Set-Content -Encoding UTF8 $sumOut | |
| function Get-SarifLevel([string]$sev) { | |
| switch -Regex ($sev) { | |
| 'Error' { 'error' } | |
| 'Warning' { 'warning' } | |
| default { 'note' } | |
| } | |
| } | |
| # Update SARIF file (baseline exists already) | |
| try { | |
| $sarif = @{ | |
| '$schema' = 'https://json.schemastore.org/sarif-2.1.0.json' | |
| version = '2.1.0' | |
| runs = @( | |
| @{ | |
| tool = @{ | |
| driver = @{ | |
| name = 'PSScriptAnalyzer' | |
| informationUri = 'https://github.com/PowerShell/PSScriptAnalyzer' | |
| version = (Get-Module PSScriptAnalyzer).Version.ToString() | |
| } | |
| } | |
| results = @() | |
| } | |
| ) | |
| } | |
| foreach ($r in $results) { | |
| # Use relative path when possible (better GitHub code scanning display) | |
| $scriptRel = [string]$r.ScriptName | |
| if (-not [string]::IsNullOrWhiteSpace($scriptRel)) { | |
| $scriptRel = $scriptRel.Replace("$env:GITHUB_WORKSPACE\", '').Replace("$env:GITHUB_WORKSPACE/", '') | |
| } | |
| $sarif.runs[0].results += @{ | |
| ruleId = [string]$r.RuleName | |
| level = (Get-SarifLevel ([string]$r.Severity)) | |
| message = @{ text = [string]$r.Message } | |
| locations = @( | |
| @{ | |
| physicalLocation = @{ | |
| artifactLocation = @{ uri = $scriptRel } | |
| region = @{ | |
| startLine = [int]$r.Line | |
| startColumn = [int]$r.Column | |
| } | |
| } | |
| } | |
| ) | |
| } | |
| } | |
| $sarif | ConvertTo-Json -Depth 12 | Set-Content -Encoding UTF8 $sarifOut | |
| } | |
| catch { | |
| $_ | Out-String | Add-Content -Encoding UTF8 $failOut | |
| # Keep the baseline SARIF if update fails | |
| } | |
| exit 0 | |
| - name: Pre-upload diagnostics (ensure SARIF exists) | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $outDir = Join-Path $env:GITHUB_WORKSPACE $env:OUT_DIR | |
| $sarifOut = Join-Path $outDir $env:OUT_SARIF | |
| Write-Host "GITHUB_WORKSPACE=$env:GITHUB_WORKSPACE" | |
| Write-Host "OUT_DIR=$outDir" | |
| if (Test-Path $outDir) { | |
| Get-ChildItem -Recurse -Force $outDir | Format-Table FullName, Length | |
| } else { | |
| Write-Host "Output dir missing; creating." | |
| New-Item -ItemType Directory -Force -Path $outDir | Out-Null | |
| } | |
| if (-not (Test-Path $sarifOut)) { | |
| Write-Host "SARIF missing — creating baseline now." | |
| $baseline = @{ | |
| '$schema' = 'https://json.schemastore.org/sarif-2.1.0.json' | |
| version = '2.1.0' | |
| runs = @(@{ tool = @{ driver = @{ name = 'PSScriptAnalyzer'; version = '0' } }; results = @() }) | |
| } | |
| $baseline | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $sarifOut | |
| } | |
| - name: Upload lint artifacts (never fail if empty) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: powershell-lint-reports | |
| path: ${{ env.OUT_DIR }}/** | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| - name: Upload SARIF to GitHub Code Scanning (report-only) | |
| if: always() && hashFiles(format('{0}/{1}', env.OUT_DIR, env.OUT_SARIF)) != '' | |
| uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: ${{ env.OUT_DIR }}/${{ env.OUT_SARIF }} | |
| category: ${{ env.SARIF_CATEGORY }} |