/ .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