Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5a23446
Add GitHub Actions workflow for package updates
neilr81 Feb 7, 2026
b7f8e53
Add script to update NuGet package versions
neilr81 Feb 7, 2026
c0d9eb7
Update script paths in package-update.yml
neilr81 Feb 7, 2026
bfb26c8
Apply suggestion from @Copilot
neilr81 Feb 7, 2026
88eb82c
Update package-update workflow to use cron schedule
neilr81 Feb 7, 2026
671dd97
Update .github/scripts/Update-NuGetPackageVersions.ps1
neilr81 Feb 7, 2026
1c56edc
Gate PR creation on commit detection in package-update workflow (#796)
Copilot Feb 7, 2026
4b27d31
Update .github/scripts/Update-NuGetPackageVersions.ps1
neilr81 Feb 7, 2026
8c3190a
Apply suggestion from @Copilot
neilr81 Feb 7, 2026
287fbe0
Apply suggestion from @Copilot
neilr81 Feb 7, 2026
c56db37
Apply suggestion from @Copilot
neilr81 Feb 7, 2026
1a02929
Add pull_request_target trigger to workflow
neilr81 Feb 7, 2026
1cf02c9
Apply suggestion from @Copilot
neilr81 Feb 7, 2026
331b09d
Update package-update.yml
neilr81 Feb 7, 2026
07d2d47
Update package-update.yml
neilr81 Feb 7, 2026
0455764
Rename checkout step for clarity
neilr81 Feb 7, 2026
1cbd892
Remove GitHub CLI installation from package-update.yml
neilr81 Feb 7, 2026
3e92781
Update Update-NuGetPackageVersions.ps1
neilr81 Feb 7, 2026
fc9b6c7
Fix label assignment in package-update workflow
neilr81 Feb 7, 2026
527230e
Remove 'powershell' label from package update PR
neilr81 Feb 7, 2026
34493b9
Add label to automated package update PR
neilr81 Feb 7, 2026
f2afff8
Fix label formatting in package-update.yml
neilr81 Feb 7, 2026
f5431d8
Add token input for create pull request action
neilr81 Feb 7, 2026
cd5081d
Update GH_TOKEN to use secrets for PR creation
neilr81 Feb 7, 2026
852325b
Update package-update.yml
neilr81 Feb 8, 2026
40ba9dd
Merge 852325b05f01c52e46146df66f5ff51cbc6e3dba into 60e237869ccaee744…
neilr81 Feb 8, 2026
379cba4
chore(automation): apply PowerShell updates
github-actions[bot] Feb 8, 2026
55e1c51
Merge 379cba4898327542c90f9628dcad07929844ea34 into 60e237869ccaee744…
neilr81 Feb 8, 2026
5d69300
Merge 55e1c51e667baa7dc0d157db902ce43afc7b7123 into 60e237869ccaee744…
neilr81 Feb 8, 2026
6f35d1d
Merge 5d69300647c7452ac6e3b3275061869bbe4b49d9 into 60e237869ccaee744…
neilr81 Feb 8, 2026
bbc97ca
Merge 6f35d1d7d1fd0933ff64bf60a59f4f35e2ecc4f7 into 60e237869ccaee744…
neilr81 Feb 8, 2026
6137e99
Merge bbc97ca5a35cbd41c5ff03b6bc399b0a05743565 into 60e237869ccaee744…
neilr81 Feb 8, 2026
e4ab9fa
Merge 6137e9954e265f9659000055ffa79a1cde860dcf into 60e237869ccaee744…
neilr81 Feb 8, 2026
929f317
Merge e4ab9fa5b398881982bf4213959a33b35d8915ca into 60e237869ccaee744…
neilr81 Feb 8, 2026
bb4f02f
Merge 929f3177cb4b6aa66a1a7d60352c247bf9cc4406 into 60e237869ccaee744…
neilr81 Feb 8, 2026
9a99f0b
Merge bb4f02fd618d38ae014459480b6845ff7494b1a3 into 60e237869ccaee744…
neilr81 Feb 8, 2026
92be031
Merge 9a99f0b54f056443b45f0f36de40be0ccf332de2 into 60e237869ccaee744…
neilr81 Feb 8, 2026
a821e33
Merge 92be0315b106814bb4a4f714c60d73767dc99c6c into 60e237869ccaee744…
neilr81 Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 350 additions & 0 deletions .github/scripts/Update-NuGetPackageVersions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
<#
.SYNOPSIS
Updates NuGet package versions in Directory.Packages.props file.

.DESCRIPTION
Scans Directory.Packages.props for PackageVersion elements in ItemGroups labeled 'AutoUpdate',
queries the latest available versions using 'dotnet package search', and updates the Version
attributes accordingly. Respects PreserveMajor attribute and pre-release version detection.

.PARAMETER PropsFilePath
Relative path to the Directory.Packages.props file from the source directory.
Default: "Directory.Packages.props"

.PARAMETER SourcesDirectory
The root source directory containing the props file.
Default: $env:BUILD_SOURCESDIRECTORY (Azure Pipelines variable)

.PARAMETER FailOnError
If $true, throws an exception on package lookup failures. If $false, logs warnings and continues.
Default: $false

.NOTES
Verbose logging is automatically enabled when Azure Pipelines System.Debug is set to 'true'.
To enable verbose logging, set the system.debug variable in your pipeline or run with:
variables:
system.debug: true

.EXAMPLE
.\Update-NuGetPackageVersions.ps1

.EXAMPLE
.\Update-NuGetPackageVersions.ps1 -PropsFilePath "Directory.Packages.props" -FailOnError $true

.EXAMPLE
$env:SYSTEM_DEBUG = 'true'; .\Update-NuGetPackageVersions.ps1
#>

[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$PropsFilePath = "Directory.Packages.props",

[Parameter(Mandatory = $false)]
[string]$SourcesDirectory = $(
if ($env:BUILD_SOURCESDIRECTORY) {
$env:BUILD_SOURCESDIRECTORY
}
elseif ($env:GITHUB_WORKSPACE) {
$env:GITHUB_WORKSPACE
}
else {
(Get-Location).Path
}
),

[Parameter(Mandatory = $false)]
[bool]$FailOnError = $false
)

# Normalize and validate SourcesDirectory so it is always a valid root directory
if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) {
# GitHub Actions default
$SourcesDirectory = $env:GITHUB_WORKSPACE
}

if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) {
# Local or generic PowerShell fallback
$SourcesDirectory = (Get-Location).Path
}

if (-not (Test-Path -LiteralPath $SourcesDirectory -PathType Container)) {
throw "SourcesDirectory '$SourcesDirectory' does not exist or is not a directory. Specify a valid -SourcesDirectory path."
}

# Resolve to a fully qualified, normalized path
$SourcesDirectory = (Resolve-Path -LiteralPath $SourcesDirectory).ProviderPath
# Determine whether verbose logging should be enabled:
# - Prefer the standard -Verbose common parameter when explicitly passed
# - Fall back to Azure Pipelines System.Debug variable for backwards compatibility
$isVerboseParameterSet = $PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose']
$EnableVerboseLogging = $isVerboseParameterSet -or ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1')

if ($EnableVerboseLogging) {
$VerbosePreference = 'Continue'
}
$ErrorActionPreference = if ($FailOnError) { "Stop" } else { "Continue" }

# Helper function for version comparison (from VersionUtils.ps1)
function Get-LatestVersionFromString {
param (
[string]$First,
[string]$Second
)

if (-not $First) { return $Second }
if (-not $Second) { return $First }

function Get-VersionFromString {
param ([string]$Value)

$splitIndex = $Value.IndexOf('-')
if ($splitIndex -eq -1) {
$versionString = $Value
$suffix = ''
} else {
$versionString = $Value.Substring(0, $splitIndex)
$suffix = $Value.Substring($splitIndex)
}

$version = $null
if (-not [System.Version]::TryParse($versionString, [ref]$version)) {
$version = $versionString
}

return [PSCustomObject]@{ Version = $version; Suffix = $suffix }
}

$firstVersionObject = Get-VersionFromString $First
$secondVersionObject = Get-VersionFromString $Second

if ($firstVersionObject.Version -eq $secondVersionObject.Version) {
if (-not $firstVersionObject.Suffix) { return $First }
if (-not $secondVersionObject.Suffix) { return $Second }
if ($firstVersionObject.Suffix -lt $secondVersionObject.Suffix) { return $Second }
return $First
}

if ($firstVersionObject.Version -lt $secondVersionObject.Version) { return $Second }
return $First
}

function Test-PreReleaseVersion {
param ([string]$Version)
return $Version.Contains('-')
}

function Get-LatestPackageVersion {
param (
[string]$PackageId,
[bool]$IncludePrerelease,
[string]$MajorVersion = ""
)

try {
$prereleaseFlag = if ($IncludePrerelease) { "--prerelease" } else { "" }

# Prefer NuGet-GitHub.Config (used on GitHub runners) when present, otherwise fall back to NuGet.config
$nugetGithubConfigPath = Join-Path $SourcesDirectory "NuGet-GitHub.Config"
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetGithubConfigPath) {
$configSourceFlag = "--configfile `"$nugetGithubConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet-GitHub.Config from: $nugetGithubConfigPath"
}
}
elseif (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
}
else {
Write-Warning "NuGet-GitHub.Config or NuGet.config not found at: $SourcesDirectory - search may not find required feeds"
}
Comment on lines +147 to +165
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo contains NuGet.Config (capital C), but the script falls back to NuGet.config. On case-sensitive filesystems this won’t be found, causing package searches to run without the intended feeds. Update the fallback path/message to use the correct filename casing (NuGet.Config).

Copilot uses AI. Check for mistakes.

$searchCmd = "dotnet package search `"$PackageId`" --exact-match --format json $prereleaseFlag $configSourceFlag"

if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Executing: $searchCmd"
}

Write-Host ("Searching for package: {0} {1}" -f $PackageId, ($MajorVersion ? ('(major version {0}.*)' -f $MajorVersion) : ''))

$output = Invoke-Expression $searchCmd 2>&1 | Out-String

if ($LASTEXITCODE -ne 0) {
$errorMsg = "Failed to search for package '$PackageId': $output"
if ($FailOnError) {
throw $errorMsg
} else {
Write-Warning $errorMsg
return $null
}
}

if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Raw output: $output"
}

$result = $output | ConvertFrom-Json

if (-not $result.searchResult -or $result.searchResult.Count -eq 0) {
$errorMsg = "Package '$PackageId' not found in any configured feed"
if ($FailOnError) {
throw $errorMsg
} else {
Write-Warning $errorMsg
return $null
}
}

# Iterate through all sources and packages to find the latest version
# The JSON structure is: searchResult[].packages[].version
$latestVersion = $null
foreach ($source in $result.searchResult) {
if ($source.packages) {
foreach ($package in $source.packages) {
if ($package.id -eq $PackageId) {
# If major version filtering is needed, skip non-matching versions
if ($MajorVersion -and -not ($package.version -match "^$MajorVersion\.")) {
continue
}
$latestVersion = Get-LatestVersionFromString -First $latestVersion -Second $package.version
}
}
}
}

if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Found latest version: $latestVersion"
}

return $latestVersion
}
catch {
$errorMsg = "Error searching for package '$PackageId': $_"
if ($FailOnError) {
throw $errorMsg
} else {
Write-Warning $errorMsg
return $null
}
}
}

# Main script execution
Write-Host "==============================================================================="
Write-Host "NuGet Package Version Update Script"
Write-Host "==============================================================================="
Write-Host "Props File Path: $PropsFilePath"
Write-Host "Sources Directory: $SourcesDirectory"
Write-Host "Verbose Logging: $EnableVerboseLogging"
Write-Host "Fail On Error: $FailOnError"
Write-Host "==============================================================================="

$propsFile = Join-Path $SourcesDirectory $PropsFilePath
Write-Host "Full path to props file: $propsFile"

if (-not (Test-Path $propsFile)) {
$errorMsg = "Props file not found at: $propsFile"
if ($FailOnError) {
throw $errorMsg
} else {
Write-Warning $errorMsg
exit 1
}
}

Write-Host "Updating NuGet packages in: $propsFile"

# Load XML with whitespace preservation
$xml = New-Object System.Xml.XmlDocument
$xml.PreserveWhitespace = $true
$xml.Load($propsFile)

$updateCount = 0

# Find all ItemGroups with AutoUpdate label
$autoUpdateGroups = $xml.Project.ItemGroup | Where-Object {
$null -ne $_.Label -and $_.Label -match 'AutoUpdate'
}

Write-Host "Found $($autoUpdateGroups.Count) ItemGroups with AutoUpdate label"

foreach ($itemGroup in $autoUpdateGroups) {
$packageVersions = $itemGroup.PackageVersion

if (-not $packageVersions) { continue }

if ($EnableVerboseLogging) {
Write-Host "[VERBOSE] Processing ItemGroup with $(@($packageVersions).Count) packages"
}

foreach ($packageVersion in $packageVersions) {
$packageId = $packageVersion.Include
$currentVersion = $packageVersion.Version
$preserveMajor = $packageVersion.PreserveMajor -eq "true"

if (-not $packageId -or -not $currentVersion) {
Write-Host "Skipping invalid PackageVersion: $($packageVersion.OuterXml)"
continue
}

if ($EnableVerboseLogging) {
Write-Host "[VERBOSE] Processing: $packageId (current: $currentVersion, preserveMajor: $preserveMajor)"
}

# Determine if we should include prerelease
$includePrerelease = Test-PreReleaseVersion -Version $currentVersion

if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Include prerelease: $includePrerelease"
}

# Get major version if needed
$majorVersion = ""
if ($preserveMajor) {
$majorVersion = $currentVersion.Split('.')[0]
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Preserving major version: $majorVersion"
}
}

# Get latest version
$latestVersion = Get-LatestPackageVersion -PackageId $packageId -IncludePrerelease $includePrerelease -MajorVersion $majorVersion

if (-not $latestVersion) {
Write-Host "No update available for '$packageId'"
continue
}

# Compare versions
$selectedVersion = Get-LatestVersionFromString -First $currentVersion -Second $latestVersion

if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Version comparison: current=$currentVersion, latest=$latestVersion, selected=$selectedVersion"
}

if ($selectedVersion -ne $currentVersion) {
Write-Host "##[section]Updating '$packageId' from '$currentVersion' to '$latestVersion'"
$packageVersion.Version = $latestVersion
$updateCount++
} else {
Write-Host "Package '$packageId' already at latest version '$currentVersion'"
}
}
}

if ($updateCount -gt 0) {
Write-Host "##[section]Saving $updateCount package updates to $propsFile"
$xml.Save($propsFile)
Write-Host "Successfully updated $updateCount packages"
} else {
Write-Host "No package updates needed"
}

Write-Host "==============================================================================="
Write-Host "NuGet Package Update Complete"
Write-Host "==============================================================================="
Loading