M⚠️ ◾ Automated Package Update#821
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 Actions automation to keep NuGet package versions (and future SDK versions) up to date via PowerShell scripts, and applies the latest automated update to central package versions.
Changes:
- Bumps MSTest packages in
Directory.Packages.props(and reformats the file as a side effect of serialization). - Adds a scheduled workflow to run the NuGet package update script and open a PR automatically.
- Introduces 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 8 comments.
| File | Description |
|---|---|
| Directory.Packages.props | Updates MSTest versions; file formatting/encoding also changed. |
| .github/workflows/package-update.yml | New scheduled workflow to run update script and open a PR. |
| .github/scripts/Update-NuGetPackageVersions.ps1 | New script that searches feeds and updates central package versions. |
| .github/scripts/Update-DotNetSdkVersions.ps1 | New script to update global.json SDK and MSBuild SDK versions (not yet wired into workflow). |
| # 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 script looks for NuGet.config, but this repo uses NuGet.Config (capital C). On case-sensitive filesystems this will fail to locate the config and may miss private feeds. Update the filename casing to match the repo root file (NuGet.Config).
| # 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.
Using Invoke-Expression to execute the dotnet package search command is fragile and can be unsafe. Prefer invoking dotnet with & and a proper argument list to avoid quoting issues and reduce injection risk.
| on: | ||
| schedule: | ||
| - cron: "0 6 * * 1" | ||
| pull_request: | ||
| branches: [ "main" ] | ||
|
|
There was a problem hiding this comment.
The workflow is configured to run on pull_request events, but the job creates commits/branches and opens a PR. This can run on every PR targeting main and potentially create recursive/duplicate automation PRs. Restrict this workflow to schedule/workflow_dispatch, or add a job-level condition (e.g., only run when github.event_name == 'schedule') before any git push / PR creation steps.
| # 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 script falls back to NuGet.config, but this repo uses NuGet.Config (capital C) at the repo root. On case-sensitive filesystems (e.g., Linux/macOS), this fallback will never be found, which can make package searches miss required feeds. Update the filename casing to match the repo (NuGet.Config) and keep the warning text consistent.
| $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.
Using Invoke-Expression to run the dotnet package search command is brittle (quoting/escaping) and introduces command-injection risk if any input ever becomes untrusted. Prefer invoking dotnet directly with the call operator (&) and an argument array, and capture stdout for JSON parsing.
|
|
||
| if ($updateCount -gt 0) { | ||
| Write-Host "##[section]Saving $updateCount package updates to $propsFile" | ||
| $xml.Save($propsFile) |
There was a problem hiding this comment.
Saving the XmlDocument via $xml.Save($propsFile) rewrites the file’s formatting/encoding (this PR shows widespread whitespace changes and a BOM added to Directory.Packages.props). To avoid noisy diffs on every run, write with explicit XmlWriter settings (encoding/no BOM, preserve formatting) or update only the Version attribute text in-place without reserializing the whole document.
| $xml.Save($propsFile) | |
| # Save XML with explicit settings to avoid changing encoding/formatting (no BOM, preserve whitespace) | |
| $xmlWriterSettings = New-Object System.Xml.XmlWriterSettings | |
| $xmlWriterSettings.Encoding = New-Object System.Text.UTF8Encoding($false) # UTF-8 without BOM | |
| $xmlWriterSettings.Indent = $false | |
| $xmlWriterSettings.NewLineHandling = [System.Xml.NewLineHandling]::None | |
| $xmlWriter = [System.Xml.XmlWriter]::Create($propsFile, $xmlWriterSettings) | |
| try { | |
| $xml.Save($xmlWriter) | |
| } | |
| finally { | |
| $xmlWriter.Dispose() | |
| } |
| @@ -1,59 +1,59 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
There was a problem hiding this comment.
Directory.Packages.props now starts with a UTF-8 BOM (U+FEFF) and had broad formatting churn. If this is unintended, configure the update script’s save logic to emit UTF-8 without BOM and avoid touching unrelated whitespace so future automated updates produce minimal diffs.
| <?xml version="1.0" encoding="utf-8"?> | |
| <?xml version="1.0" encoding="utf-8"?> |
| [Parameter(Mandatory = $false)] | ||
| [bool]$FailOnError = $false | ||
| ) | ||
|
|
There was a problem hiding this comment.
SourcesDirectory defaults only to $env:BUILD_SOURCESDIRECTORY and isn’t normalized/validated like the NuGet update script. When run outside Azure Pipelines (e.g., GitHub Actions or locally), this can be empty and cause Join-Path/Test-Path failures. Add the same fallback logic ($env:GITHUB_WORKSPACE / current directory) and validate the directory exists before use.
| # Normalize and validate sources directory for use across environments | |
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | |
| $SourcesDirectory = $env:BUILD_SOURCESDIRECTORY | |
| } | |
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | |
| $SourcesDirectory = $env:GITHUB_WORKSPACE | |
| } | |
| if ([string]::IsNullOrWhiteSpace($SourcesDirectory)) { | |
| $SourcesDirectory = (Get-Location).Path | |
| } | |
| if (-not (Test-Path -LiteralPath $SourcesDirectory -PathType Container)) { | |
| throw "Sources directory '$SourcesDirectory' does not exist or is not a directory." | |
| } | |
| $SourcesDirectory = (Resolve-Path -LiteralPath $SourcesDirectory).Path |
This PR was created automatically by the workflow run 21805902413.