Skip to content

Conversation

@neilr81
Copy link
Contributor

@neilr81 neilr81 commented Feb 7, 2026

This pull request introduces a new automated workflow for keeping NuGet package versions up to date. It adds a PowerShell script to scan and update package versions in the Directory.Packages.props file and a GitHub Actions workflow to run this script weekly and on pull requests to the main branch. The workflow commits any changes and opens a pull request automatically if updates are found.

Automation for NuGet package updates:

  • Added .github/scripts/Update-NuGetPackageVersions.ps1, a PowerShell script that:
    • Scans Directory.Packages.props for PackageVersion entries in ItemGroups labeled AutoUpdate
    • Checks for the latest available versions using dotnet package search
    • Updates versions, respecting the PreserveMajor attribute and pre-release versions
    • Supports verbose logging and error handling suitable for CI environments

GitHub Actions workflow for scheduled and PR-based updates:

  • Added .github/workflows/package-update.yml to:
    • Run weekly (every Monday at 6:00 UTC) and on pull requests to main
    • Temporarily for checkout code to test the new workflow, install dependencies, run the update script, and create a new branch/commit if changes are detected
    • Open a pull request automatically with the updates and assign it to the triggering user

Copilot AI review requested due to automatic review settings February 7, 2026 14:08
@github-actions
Copy link

github-actions bot commented Feb 7, 2026

PR Metrics

Try to keep pull requests smaller than 400 lines of new product code by following the Single Responsibility Principle (SRP).
⚠️ Consider adding additional tests.

Lines
Product Code 737
Test Code -
Subtotal 737
Ignored Code 72
Total 809

Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!

@github-actions github-actions bot changed the title Add GitHub Actions workflow for package updates XS✔ ◾ Add GitHub Actions workflow for package updates Feb 7, 2026
This script updates NuGet package versions in the Directory.Packages.props file by querying the latest available versions and updating the Version attributes accordingly, while respecting the PreserveMajor attribute and pre-release version detection.
@github-actions github-actions bot changed the title XS✔ ◾ Add GitHub Actions workflow for package updates S⚠️ ◾ Add GitHub Actions workflow for package updates Feb 7, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new GitHub Actions workflow intended to run a PowerShell-based update script and automatically open a PR with the resulting changes.

Changes:

  • Introduces .github/workflows/package-update.yml with a workflow_dispatch + push trigger.
  • Runs a PowerShell script, commits any changes to a new branch, pushes it, and opens a PR via gh.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 7, 2026 14:12
Changed the trigger for package updates to a scheduled cron job.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

Comment on lines 38 to 52
git checkout -b $branch
git add -A

if (git diff --cached --quiet) {
echo "No changes detected; exiting."
exit 0
}

git commit -m "chore(automation): apply PowerShell updates"
git push --set-upstream origin $branch

- name: Open PR with gh
if: success()
env:
GH_TOKEN: ${{ github.token }}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Branch names are based only on github.run_id. Re-runs of the same workflow and/or concurrent runs can collide with an existing branch name and cause the push/PR creation to fail. Include github.run_attempt (and/or add a concurrency group) to ensure uniqueness and avoid duplicate PRs.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +226 to +238
$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 }
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Directory.Packages.props uses a default XML namespace (xmlns="http://schemas.microsoft.com/developer/msbuild/2003"), so $xml.Project.ItemGroup won’t reliably return ItemGroups. Use SelectNodes/SelectSingleNode with an XmlNamespaceManager (or a local-name() XPath) to find ItemGroup and PackageVersion elements; otherwise the script may report 0 AutoUpdate groups and do nothing.

Suggested change
$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 }
# Prepare XML namespace manager for MSBuild default namespace
$namespaceManager = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$namespaceManager.AddNamespace('msb', $xml.DocumentElement.NamespaceURI)
$updateCount = 0
# Find all ItemGroups with AutoUpdate label (namespace-aware)
$autoUpdateGroups = $xml.SelectNodes("/msb:Project/msb:ItemGroup[@Label and contains(@Label, 'AutoUpdate')]", $namespaceManager)
Write-Host "Found $($autoUpdateGroups.Count) ItemGroups with AutoUpdate label"
foreach ($itemGroup in $autoUpdateGroups) {
$packageVersions = $itemGroup.SelectNodes("msb:PackageVersion", $namespaceManager)
if (-not $packageVersions -or $packageVersions.Count -eq 0) { continue }

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Feb 7, 2026

@neilr81 I've opened a new pull request, #796, to work on those changes. Once the pull request is ready, I'll request review from you.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 7, 2026 14:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment on lines 21 to 30
- name: Install GitHub CLI
uses: cli/cli-action@v2
with:
version: 'latest'

- name: Run PowerShell script
shell: pwsh
run: |
./.github/scripts/Update-NuGetPackageVersions.ps1 -Verbose

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This workflow runs dotnet package search via the PowerShell script but does not install/pin a .NET SDK. To make runs deterministic and aligned with global.json, add an actions/setup-dotnet step (e.g., using global-json-file) before invoking the script.

Copilot uses AI. Check for mistakes.
Copilot AI and others added 2 commits February 7, 2026 14:28
* Initial plan

* Gate PR creation on changes detection flag

Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 7, 2026 14:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

Comment on lines 21 to 30
- name: Install GitHub CLI
uses: cli/cli-action@v2
with:
version: 'latest'

- name: Run PowerShell script
shell: pwsh
run: |
./.github/scripts/Update-NuGetPackageVersions.ps1 -Verbose

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

This workflow invokes dotnet (via the update script) but never installs the repo’s pinned .NET SDK. The repo uses global.json / other workflows install .NET 10 explicitly; without actions/setup-dotnet, this job can start failing when the runner image changes or if dotnet package search isn’t available. Add a setup-dotnet step aligned with the repo’s build workflow.

Copilot uses AI. Check for mistakes.
Comment on lines 137 to 147
# Check if NuGet.config exists in the sources directory
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
} else {
Write-Warning "NuGet.config not found at: $SourcesDirectory - search may not find private feeds"
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The script looks for NuGet.config and then passes it to dotnet package search --configfile. In this repo the files are NuGet.Config (internal Office feed) and NuGet-GitHub.Config (nuget.org). For GitHub Actions, using NuGet.Config is likely to fail due to missing auth to the internal feed; prefer NuGet-GitHub.Config when running on GitHub Actions (and use the correct filename/casing so it also works on non-Windows runners).

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +114
$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 }
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Prerelease comparison uses a plain string comparison on the suffix (e.g., -rc.10 vs -rc.2), which doesn’t follow NuGet/SemVer precedence rules and can select the wrong “latest” prerelease. Consider using NuGet’s version parsing/ordering (e.g., NuGet.Versioning’s NuGetVersion) or implement proper SemVer prerelease token comparison.

Suggested change
$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 }
function Compare-PreReleaseSuffix {
param (
[string]$FirstSuffix,
[string]$SecondSuffix
)
if ($FirstSuffix -eq $SecondSuffix) { return 0 }
# Trim leading '-' if present
if ($FirstSuffix.StartsWith('-')) { $FirstSuffix = $FirstSuffix.Substring(1) }
if ($SecondSuffix.StartsWith('-')) { $SecondSuffix = $SecondSuffix.Substring(1) }
$firstIdentifiers = $FirstSuffix -split '\.'
$secondIdentifiers = $SecondSuffix -split '\.'
$maxLength = [Math]::Max($firstIdentifiers.Length, $secondIdentifiers.Length)
for ($i = 0; $i -lt $maxLength; $i++) {
if ($i -ge $firstIdentifiers.Length) { return -1 } # fewer identifiers => lower precedence
if ($i -ge $secondIdentifiers.Length) { return 1 } # more identifiers => higher precedence
$firstId = $firstIdentifiers[$i]
$secondId = $secondIdentifiers[$i]
if ($firstId -eq $secondId) { continue }
$firstNum = 0
$secondNum = 0
$firstIsNumeric = [int]::TryParse($firstId, [ref]$firstNum)
$secondIsNumeric = [int]::TryParse($secondId, [ref]$secondNum)
if ($firstIsNumeric -and $secondIsNumeric) {
if ($firstNum -lt $secondNum) { return -1 }
if ($firstNum -gt $secondNum) { return 1 }
continue
}
if ($firstIsNumeric -and -not $secondIsNumeric) { return -1 } # numeric < non-numeric
if (-not $firstIsNumeric -and $secondIsNumeric) { return 1 } # non-numeric > numeric
$stringCompare = [string]::CompareOrdinal($firstId, $secondId)
if ($stringCompare -lt 0) { return -1 }
if ($stringCompare -gt 0) { return 1 }
}
return 0
}
$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 }
$suffixComparison = Compare-PreReleaseSuffix -FirstSuffix $firstVersionObject.Suffix -SecondSuffix $secondVersionObject.Suffix
if ($suffixComparison -lt 0) { return $Second }

Copilot uses AI. Check for mistakes.
Comment on lines +249 to +264
$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"
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The repo’s Directory.Packages.props uses the MSBuild XML default namespace (xmlns="http://schemas.microsoft.com/developer/msbuild/2003"). Accessing nodes via $xml.Project.ItemGroup will not find elements under that namespace, so no AutoUpdate ItemGroups will be processed. Update the XML traversal to use an XmlNamespaceManager + SelectNodes (or otherwise handle the default namespace) when locating ItemGroups/PackageVersion elements.

Suggested change
$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"
# Create namespace manager for MSBuild default namespace
$namespaceManager = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
if ($xml.DocumentElement -and $xml.DocumentElement.NamespaceURI) {
$namespaceManager.AddNamespace("msb", $xml.DocumentElement.NamespaceURI)
}
$updateCount = 0
# Find all ItemGroups with AutoUpdate label
if ($namespaceManager.HasNamespace("msb")) {
$autoUpdateGroups = $xml.SelectNodes("//msb:ItemGroup[contains(@Label, 'AutoUpdate')]", $namespaceManager)
} else {
# Fallback for documents without a default namespace
$autoUpdateGroups = $xml.SelectNodes("//ItemGroup[contains(@Label, 'AutoUpdate')]")
}
Write-Host "Found $($autoUpdateGroups.Count) ItemGroups with AutoUpdate label"
foreach ($itemGroup in $autoUpdateGroups) {
if ($namespaceManager.HasNamespace("msb")) {
$packageVersions = $itemGroup.SelectNodes("msb:PackageVersion", $namespaceManager)
} else {
# Fallback for documents without a default namespace
$packageVersions = $itemGroup.SelectNodes("PackageVersion")
}
if (-not $packageVersions -or $packageVersions.Count -eq 0) { continue }
if ($EnableVerboseLogging) {
Write-Host "[VERBOSE] Processing ItemGroup with $($packageVersions.Count) packages"

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 7, 2026 19:29
neilr81 and others added 2 commits February 7, 2026 19:30
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment on lines +267 to +282
$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"
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Directory.Packages.props uses the MSBuild default XML namespace (<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">). With that default namespace, $xml.Project.ItemGroup will not select any nodes, so $autoUpdateGroups will always be empty and the script won’t update anything. Use an XmlNamespaceManager + SelectNodes/SelectSingleNode (or an XPath using local-name()) to find ItemGroup[@Label[contains(.,'AutoUpdate')]]/PackageVersion reliably.

Suggested change
$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"
# Prepare namespace manager for default MSBuild XML namespace (if present)
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
if ($xml.DocumentElement -and $xml.DocumentElement.NamespaceURI) {
$nsMgr.AddNamespace("msb", $xml.DocumentElement.NamespaceURI)
}
$updateCount = 0
# Find all ItemGroups with AutoUpdate label (handle default MSBuild XML namespace)
if ($nsMgr -and $nsMgr.HasNamespace("msb")) {
$autoUpdateGroups = $xml.SelectNodes("//msb:ItemGroup[contains(@Label,'AutoUpdate')]", $nsMgr)
} else {
# Fallback for files without a default namespace
$autoUpdateGroups = $xml.SelectNodes("//ItemGroup[contains(@Label,'AutoUpdate')]")
}
Write-Host "Found $($autoUpdateGroups.Count) ItemGroups with AutoUpdate label"
foreach ($itemGroup in $autoUpdateGroups) {
if ($nsMgr -and $nsMgr.HasNamespace("msb")) {
$packageVersions = $itemGroup.SelectNodes("msb:PackageVersion", $nsMgr)
} else {
$packageVersions = $itemGroup.SelectNodes("PackageVersion")
}
if (-not $packageVersions -or $packageVersions.Count -eq 0) { continue }
if ($EnableVerboseLogging) {
Write-Host "[VERBOSE] Processing ItemGroup with $($packageVersions.Count) packages"

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +165
# 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"
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The script looks for NuGet.config (lowercase c), but the repo file is NuGet.Config (capital C). This will fail on case-sensitive filesystems (and also misses the internal feed config locally), causing package search to run without the intended sources. Update the script to check for the actual NuGet.Config name (or probe both casings).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 7, 2026 23:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

.github/workflows/package-update.yml:66

  • The final with: block is invalid YAML for a run: step and will cause the workflow to fail to load. If you intend to use a different token, set it via env (e.g., GH_TOKEN) or switch the step to a reusable action that supports with: inputs; otherwise remove these lines.

Comment on lines +148 to +165
$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"
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

The repo’s file is NuGet.Config (capital C). On case-sensitive filesystems this Join-Path ... "NuGet.config" check will fail and the script will incorrectly warn that no NuGet config exists. Update the filename to match (NuGet.Config) or check both casings.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 8, 2026 21:26
This script updates .NET SDK and MSBuild SDK versions in the global.json file by querying the latest releases and modifying the file accordingly.
@github-actions github-actions bot changed the title S⚠️ ◾ Add GitHub Actions workflow for package updates M⚠️ ◾ Add GitHub Actions workflow for package updates Feb 8, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +55 to +57
git push --set-upstream origin $branch
echo "has-changes=true" >> $env:GITHUB_OUTPUT

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.

gh pr create is authenticated with secrets.CREATE_PULLREQUEST, but the repo already uses the built-in secrets.GITHUB_TOKEN in other workflows. If possible, prefer GITHUB_TOKEN (with the existing workflow permissions) to avoid managing an extra secret and to make scheduled runs work out of the box.

Copilot uses AI. Check for mistakes.
throw $errorMsg
} else {
Write-Warning $errorMsg
exit 1
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.

When the props file is missing, the script exits with code 1 even when -FailOnError is $false. This contradicts the parameter description (“logs warnings and continues”) and will fail CI runs unexpectedly. Either treat missing props as a hard error regardless of FailOnError, or honor FailOnError here and continue/return cleanly when it’s $false.

Suggested change
exit 1
return

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 8, 2026 21:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

Comment on lines +179 to +188
$searchCmd = "dotnet package search `"$PackageId`" --exact-match --format json $prereleaseFlag $configSourceFlag"

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

Write-Host "Searching for MSBuild SDK: $PackageId"

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

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.

Avoid Invoke-Expression for running the dotnet command; it’s susceptible to argument injection/quoting bugs and makes error handling harder. Prefer invoking dotnet directly with an argument array and capture stdout/stderr explicitly.

Copilot uses AI. Check for mistakes.
- name: Open PR with gh
if: steps.commit.outputs.has-changes == 'true'
env:
GH_TOKEN: ${{ secrets.CREATE_PULLREQUEST }}
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.

This workflow requires a custom secret (secrets.CREATE_PULLREQUEST) to authenticate gh. If possible, prefer the built-in GITHUB_TOKEN (with the pull-requests: write permission already granted above) to avoid managing a PAT/secret and reduce risk of workflow failure when the secret is missing/rotated.

Suggested change
GH_TOKEN: ${{ secrets.CREATE_PULLREQUEST }}
GH_TOKEN: ${{ github.token }}

Copilot uses AI. Check for mistakes.
Comment on lines 49 to 54
[Parameter(Mandatory = $false)]
[string]$SourcesDirectory = $env:BUILD_SOURCESDIRECTORY,

[Parameter(Mandatory = $false)]
[bool]$FailOnError = $false
)
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.

$SourcesDirectory defaults to $env:BUILD_SOURCESDIRECTORY, which is usually unset on GitHub Actions. In this workflow the script is invoked without -SourcesDirectory, so Join-Path $SourcesDirectory ... will throw. Add the same fallback logic as Update-NuGetPackageVersions.ps1 (use $env:GITHUB_WORKSPACE / current directory) or change the default to prefer $env:GITHUB_WORKSPACE.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +56 to +58
# Auto-detect verbose logging from Azure Pipelines System.Debug variable
$EnableVerboseLogging = ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1')

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 script gates its own "[VERBOSE]" logging on SYSTEM_DEBUG, but the workflow calls it with -Verbose. As written, -Verbose won’t enable these messages (since the script uses Write-Host rather than Write-Verbose). Consider aligning behavior by detecting the -Verbose common parameter (like the NuGet script does) and/or switching verbose output to Write-Verbose.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +6
on:
schedule:
- cron: "0 6 * * 1"

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 workflow file only defines a scheduled trigger, but the PR description says it also runs on pull requests to main. Either add a pull_request trigger (and ensure permissions/push behavior works for PRs) or update the PR description to match the actual triggers.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +34
- name: Run PowerShell script
shell: pwsh
run: |
./.github/scripts/Update-NuGetPackageVersions.ps1 -Verbose

- name: Run PowerShell script
shell: pwsh
run: |
./.github/scripts/Update-DotNetSdkVersions.ps1 -Verbose

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.

Both steps are named "Run PowerShell script", which makes workflow logs harder to read and diagnose. Rename these steps to reflect which script is being executed (NuGet packages vs .NET SDK).

Copilot uses AI. Check for mistakes.
Comment on lines +167 to +177
# Check if NuGet.config exists in the sources directory
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
} else {
Write-Warning "NuGet.config not found at: $nugetConfigPath - search may not find private feeds"
}
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 config file is NuGet.Config (capital C), but this script looks for NuGet.config. On case-sensitive filesystems this will fail to find the config and may cause dotnet package search to miss required feeds. Update the filename to match the repo's NuGet.Config casing (and consider checking for NuGet-GitHub.Config first, like the other script).

Suggested change
# Check if NuGet.config exists in the sources directory
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
} else {
Write-Warning "NuGet.config not found at: $nugetConfigPath - search may not find private feeds"
}
# Check for NuGet configuration files in the sources directory
$configSourceFlag = ""
# Prefer a GitHub-specific config if present, fall back to the repo-wide NuGet.Config
$nugetGitHubConfigPath = Join-Path $SourcesDirectory "NuGet-GitHub.Config"
if (Test-Path $nugetGitHubConfigPath) {
$configSourceFlag = "--configfile `"$nugetGitHubConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet-GitHub.Config from: $nugetGitHubConfigPath"
}
}
else {
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.Config"
if (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 under: $SourcesDirectory - search may not find private feeds"
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +162
# 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"
}
}
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 config file is NuGet.Config (capital C), but this script falls back to NuGet.config. On case-sensitive filesystems that fallback won't be found. Change the fallback to NuGet.Config to match the repo root file name.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Feb 9, 2026

@neilr81 I've opened a new pull request, #825, to work on those changes. Once the pull request is ready, I'll request review from you.

* Initial plan

* Add run_attempt to branch name and concurrency group to prevent collisions

Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>

* Enable cancellation of in-progress package updates

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Feb 10, 2026

@neilr81 I've opened a new pull request, #826, to work on those changes. Once the pull request is ready, I'll request review from you.

…DotNetSdkVersions.ps1 (#826)

* Initial plan

* Add fallback logic for SourcesDirectory in Update-DotNetSdkVersions.ps1

Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: neilr81 <49037171+neilr81@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 10, 2026 17:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

Write-Host "##[section]Saving $updateCount SDK updates to $globalJsonFile"

# Save with consistent JSON formatting (2-space indent)
$globalJson | ConvertTo-Json -Depth 10 | Set-Content $globalJsonFile -NoNewline
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

Using Set-Content with -NoNewline will remove the trailing newline from global.json, which differs from the current file format (which has a trailing newline at line 7). This can cause unnecessary git diffs and may not follow POSIX conventions. Consider adding a newline after the JSON content, or use Out-File with explicit encoding to maintain the original format. For example: '$globalJson | ConvertTo-Json -Depth 10 | Set-Content $globalJsonFile' (without -NoNewline) or append "`n" to ensure a trailing newline.

Suggested change
$globalJson | ConvertTo-Json -Depth 10 | Set-Content $globalJsonFile -NoNewline
$globalJson | ConvertTo-Json -Depth 10 | Set-Content $globalJsonFile

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +131
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
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The function Get-LatestVersionFromString (lines 90-131) is duplicated identically in Update-NuGetPackageVersions.ps1 (lines 89-130). Similarly, Test-PreReleaseVersion is duplicated in both scripts. Consider extracting these shared utility functions into a common PowerShell module (e.g., .github/scripts/VersionUtils.psm1) and dot-sourcing or importing it in both scripts. This reduces maintenance burden and ensures consistent behavior. The comment at line 88 of Update-NuGetPackageVersions.ps1 already suggests this was intended ('from VersionUtils.ps1').

Copilot uses AI. Check for mistakes.
- name: Open PR with gh
if: steps.commit.outputs.has-changes == 'true'
env:
GH_TOKEN: ${{ secrets.CREATE_PULLREQUEST }}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The workflow uses a custom secret 'CREATE_PULLREQUEST' for the GH_TOKEN, but the workflow already declares 'contents: write' and 'pull-requests: write' permissions at the top level (lines 7-9), which should be sufficient for the 'gh pr create' command. Using the built-in GITHUB_TOKEN (via 'secrets.GITHUB_TOKEN') would be more secure and eliminate the need for managing a separate custom secret, unless there's a specific requirement for the PR to trigger other workflows (which the default GITHUB_TOKEN intentionally prevents to avoid recursive workflow triggers). If recursive triggers are needed, this should be documented; otherwise, use 'secrets.GITHUB_TOKEN'.

Suggested change
GH_TOKEN: ${{ secrets.CREATE_PULLREQUEST }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +72
--label automation `
--assignee "${{ github.actor }}"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

For scheduled workflow runs (triggered by cron), github.actor may not represent a meaningful user to assign the PR to - it will typically be the repository owner or the last person who edited the workflow file. Consider removing the --assignee flag for scheduled runs, or use a specific team/user designation. Alternatively, conditionally set the assignee only when the workflow is triggered by a user action (not by schedule).

Suggested change
--label automation `
--assignee "${{ github.actor }}"
--label automation

Copilot uses AI. Check for mistakes.
exit 0
}

git commit -m "chore(automation): apply PowerShell updates"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The commit message 'chore(automation): apply PowerShell updates' is misleading - the scripts are updating NuGet packages and .NET SDK versions, not updating PowerShell itself. Consider a more accurate message like 'chore(automation): update NuGet packages and .NET SDK versions' or 'chore(deps): update package and SDK versions'.

Suggested change
git commit -m "chore(automation): apply PowerShell updates"
git commit -m "chore(automation): update NuGet packages and .NET SDK versions"

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +158
# PowerShell Core: Get final URL from response object
$finalUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The code assumes PowerShell Core/7+ by accessing $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri, which may not work correctly in Windows PowerShell 5.1. For PowerShell 5.1 compatibility, you should check $PSVersionTable.PSVersion and handle the redirect URL extraction differently (using $response.Headers.Location or ResponseUri). However, since the workflow uses 'shell: pwsh' (PowerShell Core), and the script is intended for CI/CD environments, this may be acceptable if documented. Consider adding a comment noting the PowerShell Core requirement, or add version detection for broader compatibility.

Suggested change
# PowerShell Core: Get final URL from response object
$finalUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
# Determine final URL from response object in a way that works in both
# PowerShell Core and Windows PowerShell 5.1.
$finalUrl = $null
# Preferred (PowerShell Core): RequestMessage.RequestUri
if ($response.BaseResponse -and
$response.BaseResponse.RequestMessage -and
$response.BaseResponse.RequestMessage.RequestUri) {
$finalUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
}
# Fallback: Location header (some environments expose final URI here)
if (-not $finalUrl -and $response.Headers -and $response.Headers.Location) {
$finalUrl = $response.Headers.Location.ToString()
}
# Fallback (Windows PowerShell 5.1): BaseResponse.ResponseUri
if (-not $finalUrl -and
$response.BaseResponse -and
$response.BaseResponse.ResponseUri) {
$finalUrl = $response.BaseResponse.ResponseUri.AbsoluteUri
}

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +85
# Auto-detect verbose logging from Azure Pipelines System.Debug variable
$EnableVerboseLogging = ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1')
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The verbose logging configuration is inconsistent with Update-NuGetPackageVersions.ps1 and does not respect the -Verbose parameter from CmdletBinding. The script should check if the -Verbose parameter was explicitly passed (using $PSBoundParameters.ContainsKey('Verbose')) before falling back to SYSTEM_DEBUG, similar to how Update-NuGetPackageVersions.ps1 handles it at lines 80-85. This means verbose logging won't work when -Verbose is passed to the script, as shown in the workflow at line 37.

Suggested change
# Auto-detect verbose logging from Azure Pipelines System.Debug variable
$EnableVerboseLogging = ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1')
# Determine verbose logging:
# 1. Prefer explicit -Verbose parameter (from CmdletBinding/common parameters)
# 2. Fall back to Azure Pipelines System.Debug variable
if ($PSBoundParameters.ContainsKey('Verbose')) {
$EnableVerboseLogging = [bool]$PSBoundParameters['Verbose']
}
else {
$EnableVerboseLogging = ($env:SYSTEM_DEBUG -eq 'true') -or ($env:SYSTEM_DEBUG -eq '1')
}

Copilot uses AI. Check for mistakes.
Comment on lines +195 to +205
# Check if NuGet.config exists in the sources directory
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
} else {
Write-Warning "NuGet.config not found at: $nugetConfigPath - search may not find private feeds"
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The NuGet configuration file name check uses incorrect casing. The repository has files named 'NuGet.Config' and 'NuGet-GitHub.Config' (capital 'C'), not 'NuGet.config'. On case-sensitive file systems (Linux, macOS), this Test-Path check will fail, causing the script to skip the config file and potentially fail to find packages in private feeds. The script should check for 'NuGet-GitHub.Config' first (as preferred on GitHub runners), then fall back to 'NuGet.Config', matching the pattern used in Update-NuGetPackageVersions.ps1 at lines 148-165.

Suggested change
# Check if NuGet.config exists in the sources directory
$nugetConfigPath = Join-Path $SourcesDirectory "NuGet.config"
$configSourceFlag = ""
if (Test-Path $nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet.config from: $nugetConfigPath"
}
} else {
Write-Warning "NuGet.config not found at: $nugetConfigPath - search may not find private feeds"
}
# Check for NuGet configuration in the sources directory
$nugetConfigPathGitHub = Join-Path $SourcesDirectory "NuGet-GitHub.Config"
$nugetConfigPathDefault = Join-Path $SourcesDirectory "NuGet.Config"
$nugetConfigPath = $null
if (Test-Path $nugetConfigPathGitHub) {
$nugetConfigPath = $nugetConfigPathGitHub
}
elseif (Test-Path $nugetConfigPathDefault) {
$nugetConfigPath = $nugetConfigPathDefault
}
$configSourceFlag = ""
if ($nugetConfigPath) {
$configSourceFlag = "--configfile `"$nugetConfigPath`""
if ($EnableVerboseLogging) {
Write-Host " [VERBOSE] Using NuGet configuration from: $nugetConfigPath"
}
}
else {
Write-Warning "NuGet configuration file not found (expected NuGet-GitHub.Config or NuGet.Config in $SourcesDirectory) - search may not find private feeds"
}

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +5
on:
schedule:
- cron: "0 6 * * 1"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The PR description states the workflow should run "on pull requests to the main branch", but the workflow only has a schedule trigger (weekly on Mondays at 6:00 UTC). If the intent is to also test the workflow on pull requests, add a pull_request trigger with appropriate path filters to avoid unnecessary runs. If PR-based triggers are not needed, the PR description should be updated to reflect the actual behavior.

Copilot uses AI. Check for mistakes.
throw $errorMsg
} else {
Write-Warning $errorMsg
exit 1
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

When FailOnError is false, the script should not exit with a non-zero exit code. The current logic writes a warning but still exits with code 1, which will cause the GitHub Actions workflow to fail even when FailOnError is set to false. Either remove the 'exit 1' statement when FailOnError is false, or exit with code 0. This same issue exists in Update-NuGetPackageVersions.ps1 at line 256.

Suggested change
exit 1
exit 0

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants