Last active
February 9, 2026 19:52
-
-
Save Wind010/db6e883efa7a6c2d824514efeebd3b85 to your computer and use it in GitHub Desktop.
This script updates the versions of specified NuGet packages in a Directory.Packages.props file to a new version, which can be provided or fetched as the latest from NuGet/Artificatory.
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
| <# | |
| .SYNOPSIS | |
| Updates NuGet package versions in a Directory.Packages.props file. | |
| .DESCRIPTION | |
| This script updates the versions of NuGet packages in a Directory.Packages.props file. | |
| It can update all packages or only those matching specified prefixes, with smart version | |
| selection based on whether packages match the PackagePrefixes parameter. | |
| Version Selection Logic: | |
| - Packages matching PackagePrefixes: Updates to latest pre-release version with optional branch filtering | |
| - Other packages: Updates to latest stable (non-prerelease) version | |
| - All packages: Can be excluded via ExcludedPackagePrefixes parameter | |
| .PARAMETER NewVersion | |
| Optional. Specific version to apply to all packages. If not provided, the script will | |
| automatically fetch the latest version from NuGet for each package. | |
| .PARAMETER PackagePrefixes | |
| Optional. Array of package name prefixes to match for pre-release updates. | |
| Default: @("Starbucks.Risk.Common", "Starbucks.Risk.Accertify", "Starbucks.Risk.Iovation", "Starbucks.Risk.Decision.Domain") | |
| Packages matching these prefixes will be updated to the latest pre-release version with branch filtering. | |
| .PARAMETER ExcludedPackagePrefixes | |
| Optional. Array of package name prefixes to exclude from updates. | |
| Default: @() (empty - no exclusions) | |
| Packages matching these prefixes will be skipped entirely (no version lookup performed). | |
| .PARAMETER FilePath | |
| Optional. Path to the Directory.Packages.props file to update. | |
| Default: "Directory.Packages.props" | |
| .PARAMETER UpdateAuthoredPackagesOnly | |
| Optional. If true, only updates packages matching PackagePrefixes. If false, updates all packages. | |
| Default: $true | |
| When false, matching packages get pre-release versions while others get stable versions. | |
| .PARAMETER IsDebug | |
| Optional. Enables detailed debug output including package search results and file contents. | |
| Default: $false | |
| .EXAMPLE | |
| .\update-packages.ps1 | |
| Updates only packages matching the default PackagePrefixes to their latest pre-release versions. | |
| .EXAMPLE | |
| .\update-packages.ps1 -UpdateAuthoredPackagesOnly $false | |
| Updates all packages in Directory.Packages.props - matching prefixes get pre-release, others get stable. | |
| .EXAMPLE | |
| .\update-packages.ps1 -NewVersion "1.2.3-DFP-1234.1" | |
| Sets all matching packages to version 1.2.3-DFP-1234.1. | |
| .EXAMPLE | |
| .\update-packages.ps1 -UpdateAuthoredPackagesOnly $false -ExcludedPackagePrefixes @("Microsoft", "System") | |
| Updates all packages except those starting with "Microsoft" or "System". | |
| .EXAMPLE | |
| .\update-packages.ps1 -PackagePrefixes @("MyCompany") -FilePath "src\Directory.Packages.props" | |
| Updates packages matching "MyCompany" prefix in a custom file location. | |
| .NOTES | |
| Branch Filtering: The script detects the current git branch and filters pre-release versions | |
| to match the branch prefix pattern (e.g., "DFP-1234" from branch "DFP-1234-MyFeature"). | |
| #> | |
| param( | |
| [Parameter(Mandatory=$false)] | |
| [string]$NewVersion = $null, | |
| [Parameter(Mandatory=$false)] | |
| [string[]]$PackagePrefixes = @("Some.Package.One" | |
| , "Some.Package.Two"), | |
| [Parameter(Mandatory=$false)] | |
| [string[]]$ExcludedPackagePrefixes = @(), | |
| [Parameter(Mandatory=$false)] | |
| [string]$FilePath = "Directory.Packages.props", | |
| [Parameter(Mandatory=$false)] | |
| [bool]$UpdateAuthoredPackagesOnly = $true, | |
| [Parameter(Mandatory=$false)] | |
| [bool]$IsDebug = $false | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| if (-not (Test-Path -Path $FilePath)) { | |
| throw "File not found: $FilePath" | |
| } | |
| function Get-BranchPrefix { | |
| Write-Host "Determining current git branch..." -ForegroundColor Cyan | |
| $branchName = git rev-parse --abbrev-ref HEAD 2>$null | |
| if ($null -eq $branchName -or $branchName -eq "") { | |
| Write-Warning "Unable to determine git branch name. No branch filtering will be applied." | |
| return $null | |
| } | |
| Write-Host "Current branch: $branchName" -ForegroundColor Yellow | |
| # Extract prefix (e.g., "DFP-0000" from "DFP-0000-SomeStory") | |
| $pattern = '^([A-Za-z]+-\d+)' | |
| if ($branchName -match $pattern) { | |
| $prefix = $matches[1] | |
| Write-Host "Branch prefix extracted: $prefix" -ForegroundColor Green | |
| return $prefix | |
| } | |
| Write-Warning "Branch name '$branchName' does not match expected pattern (e.g., $pattern). No branch filtering will be applied." | |
| return $null | |
| } | |
| function Get-LatestPackageVersion { | |
| param( | |
| [string]$PackageName, | |
| [string]$BranchPrefix = $null, | |
| [bool]$IncludePrerelease = $true | |
| ) | |
| $releaseType = if ($IncludePrerelease) { "pre-release" } else { "stable" } | |
| Write-Host "Determining latest $releaseType version of $PackageName from NuGet..." -ForegroundColor Cyan | |
| # The 1st result is the most accurate. The returned results is a list of packages matching the search | |
| # and is the only the latest version. | |
| $searchArgs = @("package", "search", $PackageName, "--take", "1", "--format", "json") | |
| if ($IncludePrerelease) { | |
| $searchArgs += "--prerelease" | |
| } | |
| $packageInfo = & dotnet $searchArgs | ConvertFrom-Json | |
| if ($null -eq $packageInfo -or $null -eq $packageInfo.searchResult.packages) { | |
| Write-Error "Failed to retrieve packages for $PackageName from NuGet." | |
| return $null | |
| } | |
| $packages = $packageInfo.searchResult.packages | |
| if ($IsDebug) { | |
| Write-Host "Debug: Found $($packages.Count) package(s)" -ForegroundColor Magenta | |
| foreach ($pkg in $packages) { | |
| Write-Host " - $($pkg.id) : $($pkg.latestVersion)" -ForegroundColor Magenta | |
| } | |
| } | |
| # Find the package that contains name | |
| $targetPackage = $packages | Where-Object { $_.id -like "*$PackageName*" } | Select-Object -First 1 | |
| if ($null -eq $targetPackage) { | |
| Write-Error "Package $PackageName not found in search results." | |
| return $null | |
| } | |
| # If we have a branch prefix, filter versions (only applies to pre-release packages) | |
| if (![string]::IsNullOrEmpty($BranchPrefix) -and $IncludePrerelease) { | |
| Write-Host "Filtering for versions starting with '$BranchPrefix'..." -ForegroundColor Cyan | |
| # Get all versions for this package | |
| $showArgs = @("package", "show", $PackageName, "--format", "json", "--prerelease") | |
| $allVersionsJson = & dotnet $showArgs 2>$null | |
| if ($null -eq $allVersionsJson -or $allVersionsJson -eq "") { | |
| Write-Warning "Could not retrieve version list for $PackageName. Using latest version without filtering." | |
| $latestVersion = $targetPackage.latestVersion | |
| } else { | |
| $packageInfo = $allVersionsJson | ConvertFrom-Json | |
| if ($null -ne $packageInfo.versions -and $packageInfo.versions.Count -gt 0) { | |
| # Filter versions that start with the branch prefix | |
| $filteredVersions = $packageInfo.versions | Where-Object { $_ -like "$BranchPrefix*" } | |
| if ($IsDebug -and $null -ne $filteredVersions) { | |
| Write-Host "Debug: Filtered versions:" -ForegroundColor Magenta | |
| $filteredVersions | ForEach-Object { Write-Host " - $_" -ForegroundColor Magenta } | |
| } | |
| if ($null -ne $filteredVersions -and $filteredVersions.Count -gt 0) { | |
| # Get the latest filtered version (versions are typically returned in descending order) | |
| $latestVersion = $filteredVersions | Select-Object -First 1 | |
| Write-Host "Found branch-specific version: $latestVersion" -ForegroundColor Green | |
| } else { | |
| Write-Warning "No versions found starting with '$BranchPrefix'. Using overall latest version." | |
| $latestVersion = $targetPackage.latestVersion | |
| } | |
| } else { | |
| Write-Warning "Could not parse versions for $PackageName. Using latest version without filtering." | |
| $latestVersion = $targetPackage.latestVersion | |
| } | |
| } | |
| } else { | |
| $latestVersion = $targetPackage.latestVersion | |
| } | |
| if ($null -eq $latestVersion -or $latestVersion -eq "") { | |
| Write-Error "Failed to determine latest version of $PackageName from NuGet." | |
| return $null | |
| } | |
| Write-Host "Latest version of $PackageName is $latestVersion" -ForegroundColor Green | |
| return $latestVersion | |
| } | |
| function Get-RegexPattern { | |
| param( | |
| [string]$PackageName | |
| ) | |
| # Escape special regex characters in package name | |
| $escapedPackage = [regex]::Escape($PackageName) | |
| # Build the full regex pattern for a single package | |
| return '<PackageVersion\s+Include="(' + $escapedPackage + '[^"]*)"\s+Version="([^"]*)"' | |
| } | |
| function Get-MatchingPackages { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$FilePath, | |
| [Parameter(Mandatory = $true)] | |
| [string[]]$PackagePrefixes | |
| ) | |
| $content = Get-Content -Path $FilePath -Raw | |
| [xml]$xml = $content | |
| $matchingPackages = @() | |
| foreach ($packagePrefix in $PackagePrefixes) { | |
| $matchingPackage = $xml.Project.ItemGroup.PackageVersion | | |
| Where-Object { $_.Include -like "*$packagePrefix*" } | | |
| Select-Object -ExpandProperty Include | |
| $matchingPackages += $matchingPackage | |
| } | |
| return $matchingPackages | |
| } | |
| function Get-AllPackages { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$FilePath | |
| ) | |
| $content = Get-Content -Path $FilePath -Raw | |
| [xml]$xml = $content | |
| $allPackages = $xml.Project.ItemGroup.PackageVersion | | |
| Select-Object -ExpandProperty Include | |
| return $allPackages | |
| } | |
| function Test-PackageMatchesPrefix { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$PackageName, | |
| [Parameter(Mandatory = $true)] | |
| [string[]]$PackagePrefixes | |
| ) | |
| foreach ($prefix in $PackagePrefixes) { | |
| if ($PackageName -like "*$prefix*") { | |
| return $true | |
| } | |
| } | |
| return $false | |
| } | |
| # Get branch prefix once for all packages | |
| $branchPrefix = Get-BranchPrefix | |
| # Read the file content once | |
| $content = Get-Content -Path $FilePath -Raw | |
| $totalUpdated = 0 | |
| # Get packages to update based on UpdateAuthoredPackagesOnly parameter | |
| if ($UpdateAuthoredPackagesOnly) { | |
| $packageNamesToUpdate = Get-MatchingPackages -FilePath $FilePath -PackagePrefixes $PackagePrefixes | |
| Write-Host ("`nFound {0} matching package(s) in {1}" -f $packageNamesToUpdate.Count, $FilePath) -ForegroundColor Cyan | |
| } else { | |
| $packageNamesToUpdate = Get-AllPackages -FilePath $FilePath | |
| Write-Host ("`nFound {0} total package(s) in {1}" -f $packageNamesToUpdate.Count, $FilePath) -ForegroundColor Cyan | |
| } | |
| # Process each package name individually | |
| foreach ($packageName in $packageNamesToUpdate) { | |
| Write-Host "`n======================================================" -ForegroundColor Cyan | |
| Write-Host "Processing package: $packageName" -ForegroundColor Cyan | |
| Write-Host "=======================================================" -ForegroundColor Cyan | |
| # Check if this package should be excluded | |
| if ($ExcludedPackagePrefixes.Count -gt 0) { | |
| $isExcluded = Test-PackageMatchesPrefix -PackageName $packageName -PackagePrefixes $ExcludedPackagePrefixes | |
| if ($isExcluded) { | |
| Write-Host "Package matches excluded prefix - skipping" -ForegroundColor Yellow | |
| continue | |
| } | |
| } | |
| # Check if this package matches any of the prefixes | |
| $matchesPrefix = Test-PackageMatchesPrefix -PackageName $packageName -PackagePrefixes $PackagePrefixes | |
| # Generate regex pattern for this specific package | |
| $pattern = Get-RegexPattern -PackageName $packageName | |
| Write-Host "Generated regex pattern: $pattern" -ForegroundColor Yellow | |
| # Determine version for this specific package | |
| $versionToUse = $null | |
| if ([string]::IsNullOrEmpty($NewVersion)) { | |
| if ($matchesPrefix) { | |
| Write-Host "Package matches prefix - fetching latest pre-release version with branch filtering" -ForegroundColor Yellow | |
| $versionToUse = Get-LatestPackageVersion -PackageName $packageName -BranchPrefix $branchPrefix -IncludePrerelease $true | |
| } else { | |
| Write-Host "Package does not match prefix - fetching latest stable version" -ForegroundColor Yellow | |
| $versionToUse = Get-LatestPackageVersion -PackageName $packageName -BranchPrefix $null -IncludePrerelease $false | |
| } | |
| if ($null -eq $versionToUse) { | |
| Write-Warning "Skipping $packageName due to version lookup failure" | |
| continue | |
| } | |
| } else { | |
| $versionToUse = $NewVersion | |
| Write-Host "Using provided version: $versionToUse" -ForegroundColor Green | |
| } | |
| # Find matches for this package | |
| $matches = [regex]::Matches($content, $pattern) | |
| $count = $matches.Count | |
| Write-Host "`nFound $count matching package(s) for ${packageName}:" -ForegroundColor Cyan | |
| foreach ($match in $matches) { | |
| $fullPackageName = $match.Groups[1].Value | |
| $currentVersion = $match.Groups[2].Value | |
| Write-Host " - $fullPackageName : $currentVersion" -ForegroundColor Yellow | |
| } | |
| if ($count -eq 0) { | |
| Write-Warning "No packages matching '$packageName' found in $FilePath" | |
| continue | |
| } | |
| # Perform replacement for this specific package | |
| $content = [regex]::Replace($content, $pattern, { | |
| param($match) | |
| $fullPackageName = $match.Groups[1].Value | |
| $oldVersion = $match.Groups[2].Value | |
| Write-Host " Updating $fullPackageName from $oldVersion to $versionToUse" -ForegroundColor Gray | |
| return "<PackageVersion Include=`"$fullPackageName`" Version=`"$versionToUse`"" | |
| }) | |
| $totalUpdated += $count | |
| } | |
| # Write the updated content back to the file | |
| if ($totalUpdated -gt 0) { | |
| Set-Content -Path $FilePath -Value $content -NoNewline | |
| Write-Host "`n========================================" -ForegroundColor Green | |
| Write-Host "Successfully updated $totalUpdated package(s) total" -ForegroundColor Green | |
| Write-Host "========================================" -ForegroundColor Green | |
| } else { | |
| Write-Warning "`nNo packages were updated" | |
| if ($IsDebug) { | |
| Write-Host "`nDebug: First few lines of file:" -ForegroundColor Red | |
| (Get-Content -Path $FilePath | Select-Object -First 50) | ForEach-Object { Write-Host $_ } | |
| } | |
| exit 0 | |
| } | |
| # Verify the updates | |
| Write-Host "`nVerifying updates..." -ForegroundColor Cyan | |
| $verifyContent = Get-Content -Path $FilePath -Raw | |
| foreach ($packageName in $PackagePrefixes) { | |
| $pattern = Get-RegexPattern -PackageName $packageName | |
| $verifyMatches = [regex]::Matches($verifyContent, $pattern) | |
| foreach ($match in $verifyMatches) { | |
| $fullPackageName = $match.Groups[1].Value | |
| $version = $match.Groups[2].Value | |
| Write-Host " ✓ $fullPackageName = $version" -ForegroundColor Green | |
| } | |
| } | |
| if ($IsDebug) { | |
| Write-Host "`nDebug: File contents after update:" -ForegroundColor Red | |
| Write-Host (Get-Content -Path $FilePath -Raw) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment