Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
Verify-MarkdownLinks.ps1318 linesDownload Raw Back to markdownlinks
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3 4#Requires -Version 7.05 6<#7.SYNOPSIS8    Verify all links in markdown files.9 10.DESCRIPTION11    This script parses markdown files to extract links and verifies their accessibility.12    It supports HTTP/HTTPS links and local file references.13 14.PARAMETER Path15    Path to the directory containing markdown files. Defaults to current directory.16 17.PARAMETER File18    Array of specific markdown files to verify. If provided, Path parameter is ignored.19 20.PARAMETER TimeoutSec21    Timeout in seconds for HTTP requests. Defaults to 30.22 23.PARAMETER MaximumRetryCount24    Maximum number of retries for failed requests. Defaults to 2.25 26.PARAMETER RetryIntervalSec27    Interval in seconds between retry attempts. Defaults to 2.28 29.EXAMPLE30    .\Verify-MarkdownLinks.ps1 -Path ./CHANGELOG31 32.EXAMPLE33    .\Verify-MarkdownLinks.ps1 -Path ./docs -FailOnError34 35.EXAMPLE36    .\Verify-MarkdownLinks.ps1 -File @('CHANGELOG/7.5.md', 'README.md')37#>38 39param(40    [Parameter(ParameterSetName = 'ByPath', Mandatory)]41    [string]$Path = "Q:\src\git\powershell\docs\git",42    [Parameter(ParameterSetName = 'ByFile', Mandatory)]43    [string[]]$File = @(),44    [int]$TimeoutSec = 30,45    [int]$MaximumRetryCount = 2,46    [int]$RetryIntervalSec = 247)48 49$ErrorActionPreference = 'Stop'50 51# Get the script directory52$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path53 54# Determine what to process: specific files or directory55if ($File.Count -gt 0) {56    Write-Host "Extracting links from $($File.Count) specified markdown file(s)" -ForegroundColor Cyan57 58    # Process each file individually59    $allLinks = @()60    $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1"61 62    foreach ($filePath in $File) {63        if (Test-Path $filePath) {64            Write-Verbose "Processing: $filePath"65            $fileLinks = & $parseScriptPath -ChangelogPath $filePath66            $allLinks += $fileLinks67        }68        else {69            Write-Warning "File not found: $filePath"70        }71    }72}73else {74    Write-Host "Extracting links from markdown files in: $Path" -ForegroundColor Cyan75 76    # Get all links from markdown files using the Parse-ChangelogLinks script77    $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1"78    $allLinks = & $parseScriptPath -ChangelogPath $Path79}80 81if ($allLinks.Count -eq 0) {82    Write-Host "No links found in markdown files." -ForegroundColor Yellow83    exit 084}85 86Write-Host "Found $($allLinks.Count) links to verify" -ForegroundColor Green87 88# Group links by URL to avoid duplicate checks89$uniqueLinks = $allLinks | Group-Object -Property Url90 91Write-Host "Unique URLs to verify: $($uniqueLinks.Count)" -ForegroundColor Cyan92 93$results = @{94    Total = $uniqueLinks.Count95    Passed = 096    Failed = 097    Skipped = 098    Errors = [System.Collections.ArrayList]::new()99}100 101function Test-HttpLink {102    param(103        [string]$Url104    )105 106    try {107        # Try HEAD request first (faster, doesn't download content)108        $response = Invoke-WebRequest -Uri $Url `109            -Method Head `110            -TimeoutSec $TimeoutSec `111            -MaximumRetryCount $MaximumRetryCount `112            -RetryIntervalSec $RetryIntervalSec `113            -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com/PowerShell/PowerShell)" `114            -SkipHttpErrorCheck115 116        # If HEAD fails with 404 or 405, retry with GET (some servers don't support HEAD)117        if ($response.StatusCode -eq 404 -or $response.StatusCode -eq 405) {118            Write-Verbose "HEAD request failed with $($response.StatusCode), retrying with GET for: $Url"119            $response = Invoke-WebRequest -Uri $Url `120                -Method Get `121                -TimeoutSec $TimeoutSec `122                -MaximumRetryCount $MaximumRetryCount `123                -RetryIntervalSec $RetryIntervalSec `124                -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com)" `125                -SkipHttpErrorCheck126        }127 128        if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) {129            return @{ Success = $true; StatusCode = $response.StatusCode }130        }131        else {132            return @{ Success = $false; StatusCode = $response.StatusCode; Error = "HTTP $($response.StatusCode)" }133        }134    }135    catch {136        return @{ Success = $false; StatusCode = 0; Error = $_.Exception.Message }137    }138}139 140function Test-LocalLink {141    param(142        [string]$Url,143        [string]$BasePath144    )145 146    # Strip query parameters (e.g., ?sanitize=true) and anchors (e.g., #section)147    $cleanUrl = $Url -replace '\?.*$', '' -replace '#.*$', ''148 149    # Handle relative paths150    $targetPath = Join-Path $BasePath $cleanUrl151 152    if (Test-Path $targetPath) {153        return @{ Success = $true }154    }155    else {156        return @{ Success = $false; Error = "File not found: $targetPath" }157    }158}159 160# Verify each unique link161$progressCount = 0162foreach ($linkGroup in $uniqueLinks) {163    $progressCount++164    $url = $linkGroup.Name165    $occurrences = $linkGroup.Group166    Write-Verbose -Verbose "[$progressCount/$($uniqueLinks.Count)] Checking: $url"167 168    # Determine link type and verify169        $verifyResult = $null170        if ($url -match '^https?://') {171            $verifyResult = Test-HttpLink -Url $url172        }173        elseif ($url -match '^#') {174            Write-Verbose -Verbose "Skipping anchor link: $url"175            $results.Skipped++176            continue177        }178        elseif ($url -match '^mailto:') {179            Write-Verbose -Verbose "Skipping mailto link: $url"180            $results.Skipped++181            continue182        }183        else {184            $basePath = Split-Path -Parent $occurrences[0].Path185            $verifyResult = Test-LocalLink -Url $url -BasePath $basePath186        }187        if ($verifyResult.Success) {188            Write-Host "✓ OK: $url" -ForegroundColor Green189            $results.Passed++190        }191        else {192            $errorMsg = if ($verifyResult.StatusCode) {193                "HTTP $($verifyResult.StatusCode)"194            }195            else {196                $verifyResult.Error197            }198 199            # Determine if this status code should be ignored or treated as failure200            # Ignore: 401 (Unauthorized), 403 (Forbidden), 429 (Too Many Requests - already retried)201            # Fail: 404 (Not Found), 410 (Gone), 406 (Not Acceptable) - these indicate broken links202            $shouldIgnore = $false203            $ignoreReason = ""204 205            switch ($verifyResult.StatusCode) {206                401 {207                    $shouldIgnore = $true208                    $ignoreReason = "authentication required"209                }210                403 {211                    $shouldIgnore = $true212                    $ignoreReason = "access forbidden"213                }214                429 {215                    $shouldIgnore = $true216                    $ignoreReason = "rate limited (already retried)"217                }218            }219 220            if ($shouldIgnore) {221                Write-Host "⊘ IGNORED: $url - $errorMsg ($ignoreReason)" -ForegroundColor Yellow222                Write-Verbose -Verbose "Ignored error details for $url - Status: $($verifyResult.StatusCode) - $ignoreReason"223                foreach ($occurrence in $occurrences) {224                    Write-Verbose -Verbose "    Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)"225                }226                $results.Skipped++227            }228            else {229                Write-Host "✗ FAILED: $url - $errorMsg" -ForegroundColor Red230                foreach ($occurrence in $occurrences) {231                    Write-Host "    Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" -ForegroundColor DarkGray232                }233                $results.Failed++234                [void]$results.Errors.Add(@{235                    Url = $url236                    Error = $errorMsg237                    Occurrences = $occurrences238                })239            }240        }241    }242 243# Print summary244Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan245Write-Host "Link Verification Summary" -ForegroundColor Cyan246Write-Host ("=" * 60) -ForegroundColor Cyan247Write-Host "Total URLs checked: $($results.Total)" -ForegroundColor White248Write-Host "Passed: $($results.Passed)" -ForegroundColor Green249Write-Host "Failed: $($results.Failed)" -ForegroundColor $(if ($results.Failed -gt 0) { "Red" } else { "Green" })250Write-Host "Skipped: $($results.Skipped)" -ForegroundColor Gray251 252if ($results.Failed -gt 0) {253    Write-Host "`nFailed Links:" -ForegroundColor Red254    foreach ($failedLink in $results.Errors) {255        Write-Host "  • $($failedLink.Url)" -ForegroundColor Red256        Write-Host "    Error: $($failedLink.Error)" -ForegroundColor DarkGray257        Write-Host "    Occurrences: $($failedLink.Occurrences.Count)" -ForegroundColor DarkGray258    }259 260    Write-Host "`n❌ Link verification failed!" -ForegroundColor Red261    exit 1262}263else {264    Write-Host "`n✅ All links verified successfully!" -ForegroundColor Green265}266 267# Write to GitHub Actions step summary if running in a workflow268if ($env:GITHUB_STEP_SUMMARY) {269    $summaryContent = @"270 271# Markdown Link Verification Results272 273## Summary274- **Total URLs checked:** $($results.Total)275- **Passed:** ✅ $($results.Passed)276- **Failed:** $(if ($results.Failed -gt 0) { "❌" } else { "✅" }) $($results.Failed)277- **Skipped:** $($results.Skipped)278 279"@280 281    if ($results.Failed -gt 0) {282        $summaryContent += @"283 284## Failed Links285 286| URL | Error | Occurrences |287|-----|-------|-------------|288 289"@290        foreach ($failedLink in $results.Errors) {291            $summaryContent += "| $($failedLink.Url) | $($failedLink.Error) | $($failedLink.Occurrences.Count) |`n"292        }293 294        $summaryContent += @"295 296<details>297<summary>Click to see all failed link locations</summary>298 299"@300        foreach ($failedLink in $results.Errors) {301            $summaryContent += "`n### $($failedLink.Url)`n"302            $summaryContent += "**Error:** $($failedLink.Error)`n`n"303            foreach ($occurrence in $failedLink.Occurrences) {304                $summaryContent += "- `$($occurrence.Path):$($occurrence.Line):$($occurrence.Column)`n"305            }306        }307        $summaryContent += "`n</details>`n"308    }309    else {310        $summaryContent += "`n## ✅ All links verified successfully!`n"311    }312 313    Write-Verbose -Verbose "Writing `n $summaryContent `n to ${env:GITHUB_STEP_SUMMARY}"314    $summaryContent | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append315    Write-Verbose -Verbose "Summary written to GitHub Actions step summary"316}317 318