Skip to content

Instantly share code, notes, and snippets.

@Wind010
Last active February 9, 2026 19:52
Show Gist options
  • Select an option

  • Save Wind010/db6e883efa7a6c2d824514efeebd3b85 to your computer and use it in GitHub Desktop.

Select an option

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