M⚠️ ◾ Automated Package Update#819
Conversation
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.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Changed the trigger for package updates to a scheduled cron job.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 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>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Removed GitHub CLI installation step from workflow.
Removed the 'powershell' label from the automated package update PR.
This script updates .NET SDK and MSBuild SDK versions in the global.json file by querying the latest releases and modifying the file accordingly.
PR Metrics❌ Try to keep pull requests smaller than 400 lines of new product code by following the Single Responsibility Principle (SRP).
Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs! |
There was a problem hiding this comment.
Pull request overview
Adds GitHub-based automation to periodically update centrally-managed NuGet package versions (and includes a helper script for updating global.json SDK versions), alongside a small package version bump.
Changes:
- Bump MSTest packages in
Directory.Packages.props. - Add a scheduled GitHub Actions workflow to run an update script, commit changes, and open a PR.
- Add PowerShell scripts to update
Directory.Packages.props(NuGet packages) andglobal.json(.NET SDK / MSBuild SDKs).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| Directory.Packages.props | Updates centrally-managed package versions (notably MSTest 4.0.2 → 4.1.0) and formatting churn from XML save. |
| .github/workflows/package-update.yml | New scheduled workflow that runs the updater script and opens an automated PR. |
| .github/scripts/Update-NuGetPackageVersions.ps1 | New script to search latest NuGet versions and update Directory.Packages.props. |
| .github/scripts/Update-DotNetSdkVersions.ps1 | New script to update .NET SDK version and MSBuild SDK package versions in global.json. |
| [Parameter(Mandatory = $false)] | ||
| [bool]$FailOnError = $false | ||
| ) | ||
|
|
There was a problem hiding this comment.
$SourcesDirectory defaults to $env:BUILD_SOURCESDIRECTORY, which won’t be set in GitHub Actions and may be empty locally. Later Join-Path $SourcesDirectory ... will throw when $SourcesDirectory is null/empty.
Add the same normalization/validation fallback used in Update-NuGetPackageVersions.ps1 (e.g., fall back to $env:GITHUB_WORKSPACE and then Get-Location).
| # Normalize and validate SourcesDirectory, with fallbacks for non-Azure environments | |
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | |
| if (-not [string]::IsNullOrWhiteSpace($env:BUILD_SOURCESDIRECTORY)) { | |
| $SourcesDirectory = $env:BUILD_SOURCESDIRECTORY | |
| } elseif (-not [string]::IsNullOrWhiteSpace($env:GITHUB_WORKSPACE)) { | |
| $SourcesDirectory = $env:GITHUB_WORKSPACE | |
| } else { | |
| $SourcesDirectory = (Get-Location).Path | |
| } | |
| } | |
| try { | |
| $SourcesDirectory = (Resolve-Path -LiteralPath $SourcesDirectory).Path | |
| } catch { | |
| # If resolution fails, keep the original value; subsequent operations may handle/throw. | |
| } |
| # 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" |
There was a problem hiding this comment.
The repository config file is NuGet.Config, but the script looks for NuGet.config. On case-sensitive file systems this will fail to find the config and may prevent package search from using the intended feeds.
Use the correct casing (NuGet.Config) consistently in both the file check and the warning/verbose messages.
| # 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 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" |
| $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 | ||
|
|
There was a problem hiding this comment.
Invoke-Expression executes a constructed command string, which is error-prone for escaping and increases the blast radius of unexpected input. Since $PackageId and paths are interpolated into the string, prefer invoking dotnet with the call operator (&) and explicit arguments instead.
This will be safer and more reliable across different package IDs/paths.
| if (git diff --cached --quiet) { | ||
| echo "No changes detected; skipping commit and PR creation." | ||
| echo "has-changes=false" >> $env:GITHUB_OUTPUT | ||
| exit 0 | ||
| } |
There was a problem hiding this comment.
git diff --cached --quiet doesn’t write to stdout, so in PowerShell if (git diff --cached --quiet) { ... } will always evaluate as false regardless of the exit code. This means the workflow will try to commit even when there are no staged changes, causing the step to fail on git commit.
Capture the exit code (e.g., run the command, then check $LASTEXITCODE / $?) to decide whether to skip PR creation.
| on: | ||
| schedule: | ||
| - cron: "0 6 * * 1" | ||
| pull_request: | ||
| branches: [ "main" ] | ||
|
|
There was a problem hiding this comment.
This workflow is configured to run on pull_request events targeting main while also pushing branches and opening PRs. On PR events (especially from forks), repository secrets (e.g., CREATE_PULLREQUEST) aren’t available and the job can fail; additionally it risks PR/branch churn when the automation’s own PR triggers the workflow again.
Consider limiting this workflow to schedule/workflow_dispatch only, or add conditions to skip when the actor is the bot and/or when the ref is already an automation/package-update-* branch.
| # 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" | ||
| } |
There was a problem hiding this comment.
The repo’s NuGet config file is named NuGet.Config (capital “C”), but the script falls back to NuGet.config. On case-sensitive file systems this fallback will never be found, and the script may search without the intended feeds.
Update the fallback filename (and associated log messages) to match NuGet.Config exactly.
| $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 | ||
|
|
There was a problem hiding this comment.
Building a command string and executing it via Invoke-Expression is risky and makes argument escaping brittle. Here the command contains values derived from repo data ($PackageId, paths), so a malformed value could change the executed command.
Prefer invoking dotnet directly with the call operator (&) and a proper argument array (or Start-Process) instead of Invoke-Expression.
This PR was created automatically by the workflow run 21805878640.