/ .pipelines / verifyNoticeMdAgainstNugetPackages.ps1
verifyNoticeMdAgainstNugetPackages.ps1
1 [CmdletBinding()] 2 Param( 3 [Parameter(Mandatory=$True,Position=1)] 4 [string]$path 5 ) 6 7 $noticeFile = Get-Content -Raw "NOTICE.md" 8 9 Write-Host $noticeFile 10 11 Write-Host "Verifying NuGet packages" 12 13 $projFiles = Get-ChildItem $path -Filter *.csproj -force -Recurse 14 $projFiles.Count 15 16 Write-Host "Going through all csproj files" 17 18 $totalList = $projFiles | ForEach-Object -Parallel { 19 $csproj = $_ 20 $nugetTemp = @(); 21 22 #Workaround for preventing exit code from dotnet process from reflecting exit code in PowerShell 23 $procInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{ 24 FileName = "dotnet.exe"; 25 Arguments = "list $csproj package"; 26 RedirectStandardOutput = $true; 27 RedirectStandardError = $true; 28 } 29 30 $proc = [System.Diagnostics.Process]::Start($procInfo); 31 32 while (!$proc.StandardOutput.EndOfStream) { 33 $nugetTemp += $proc.StandardOutput.ReadLine(); 34 } 35 36 $proc = $null; 37 $procInfo = $null; 38 39 if($nugetTemp -is [array] -and $nugetTemp.count -gt 3) 40 { 41 # Need to debug this script? Uncomment this line. 42 # Write-Host $csproj "`r`n" $nugetTemp "`r`n" 43 $temp = New-Object System.Collections.ArrayList 44 $temp.AddRange($nugetTemp) 45 $temp.RemoveRange(0, 3) 46 47 foreach($p in $temp) 48 { 49 # ignore "Auto-referenced" string in the output 50 if ($p -match "Auto-referenced") { 51 continue 52 } 53 54 # breaking item down to usable array and getting 1 and 2, see below of a sample output 55 # > PACKAGE VERSION VERSION 56 # if a package is Auto-referenced, "(A)" will appear in position 1 instead of a version number. 57 58 $p = -split $p 59 $p = $p[1, 2] 60 $tempString = $p[0] 61 62 if([string]::IsNullOrWhiteSpace($tempString)) 63 { 64 Continue 65 } 66 67 if($tempString.StartsWith("Microsoft.") -Or $tempString.StartsWith("System.")) 68 { 69 Continue 70 } 71 72 echo "- $tempString" 73 } 74 $csproj = $null; 75 } 76 } -ThrottleLimit 4 | Sort-Object 77 78 $returnList = [System.Collections.Generic.HashSet[string]]($totalList) -join "`r`n" 79 80 Write-Host $returnList 81 82 # Extract the current package list from NOTICE.md 83 $noticePattern = "## NuGet Packages used by PowerToys\s*((?:\r?\n- .+)+)" 84 $noticeMatch = [regex]::Match($noticeFile, $noticePattern) 85 86 if ($noticeMatch.Success) { 87 $currentNoticePackageList = $noticeMatch.Groups[1].Value.Trim() 88 } else { 89 Write-Warning "Warning: Could not find 'NuGet Packages used by PowerToys' section in NOTICE.md" 90 $currentNoticePackageList = "" 91 } 92 93 # Test-only packages that are allowed to be in NOTICE.md but not in the build 94 # (e.g., when BuildTests=false, these packages won't appear in the NuGet list) 95 $allowedExtraPackages = @( 96 "- Moq" 97 ) 98 99 if (!$noticeFile.Trim().EndsWith($returnList.Trim())) 100 { 101 Write-Host -ForegroundColor Yellow "Notice.md does not exactly match NuGet list. Analyzing differences..." 102 103 # Show detailed differences 104 $generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object 105 $noticePackages = $currentNoticePackageList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() } | Sort-Object 106 107 Write-Host "" 108 Write-Host -ForegroundColor Cyan "=== DETAILED DIFFERENCE ANALYSIS ===" 109 Write-Host "" 110 111 # Find packages in proj file list but not in NOTICE.md 112 $missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ } 113 if ($missingFromNotice.Count -gt 0) { 114 Write-Host -ForegroundColor Red "MissingFromNotice (ERROR - these must be added to NOTICE.md):" 115 foreach ($pkg in $missingFromNotice) { 116 Write-Host -ForegroundColor Red " $pkg" 117 } 118 Write-Host "" 119 } 120 121 # Find packages in NOTICE.md but not in proj file list 122 $extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ } 123 124 # Filter out allowed extra packages (test-only dependencies) 125 $unexpectedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -notcontains $_ } 126 $allowedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -contains $_ } 127 128 if ($allowedExtra.Count -gt 0) { 129 Write-Host -ForegroundColor Green "ExtraInNotice (OK - allowed test-only packages):" 130 foreach ($pkg in $allowedExtra) { 131 Write-Host -ForegroundColor Green " $pkg" 132 } 133 Write-Host "" 134 } 135 136 if ($unexpectedExtra.Count -gt 0) { 137 Write-Host -ForegroundColor Red "ExtraInNotice (ERROR - unexpected packages in NOTICE.md):" 138 foreach ($pkg in $unexpectedExtra) { 139 Write-Host -ForegroundColor Red " $pkg" 140 } 141 Write-Host "" 142 } 143 144 # Show counts for summary 145 Write-Host -ForegroundColor Cyan "Summary:" 146 Write-Host " Proj file list has $($generatedPackages.Count) packages" 147 Write-Host " NOTICE.md has $($noticePackages.Count) packages" 148 Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages" 149 Write-Host " ExtraInNotice (allowed): $($allowedExtra.Count) packages" 150 Write-Host " ExtraInNotice (unexpected): $($unexpectedExtra.Count) packages" 151 Write-Host "" 152 153 # Fail if there are missing packages OR unexpected extra packages 154 if ($missingFromNotice.Count -gt 0 -or $unexpectedExtra.Count -gt 0) { 155 Write-Host -ForegroundColor Red "FAILED: NOTICE.md mismatch detected." 156 exit 1 157 } else { 158 Write-Host -ForegroundColor Green "PASSED: NOTICE.md matches (with allowed test-only packages)." 159 } 160 } 161 162 exit 0