M⚠️ ◾ Automated Package Update#824
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
Automates dependency maintenance by adding a GitHub Actions workflow and PowerShell scripts to update centrally-managed NuGet package versions (and SDK versions), plus applies an automated package version bump in Directory.Packages.props.
Changes:
- Bump MSTest package versions in
Directory.Packages.propsand normalize XML formatting. - Add a scheduled GitHub Actions workflow to run package update automation and open a PR.
- Add PowerShell automation scripts to update NuGet package versions and .NET SDK/MSBuild SDK versions.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| Directory.Packages.props | Updates MSTest versions and reformats PackageVersion entries. |
| .github/workflows/package-update.yml | Adds a scheduled automation workflow that runs update script and opens a PR. |
| .github/scripts/Update-NuGetPackageVersions.ps1 | Adds script to search latest package versions and update Directory.Packages.props AutoUpdate groups. |
| .github/scripts/Update-DotNetSdkVersions.ps1 | Adds script to update global.json SDK/MSBuild SDK versions. |
| 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 and can create/push a new branch + open a PR. This is likely to spam PRs and can recurse when the automation PR itself triggers the workflow. Limit this workflow to schedule (and optionally workflow_dispatch) rather than pull_request.
| # 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 = "" |
There was a problem hiding this comment.
The repo root uses NuGet.Config (capital C), but the script looks for NuGet.config. On case-sensitive filesystems this will never be found, so the intended feeds/config won’t be used. Update this to match the repo’s filename (or check both casings).
| $response = Invoke-WebRequest -Uri $sdkRedirectUrl -Method HEAD -MaximumRedirection 10 -UseBasicParsing | ||
|
|
||
| if ($EnableVerboseLogging) { | ||
| Write-Host " [VERBOSE] Response status: $($response.StatusCode)" | ||
| Write-Host " [VERBOSE] Response headers: $($response.Headers | ConvertTo-Json -Compress)" | ||
| } | ||
|
|
||
| # PowerShell Core: Get final URL from response object | ||
| $finalUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri |
There was a problem hiding this comment.
Invoke-WebRequest result handling is not compatible across Windows PowerShell 5.1 vs PowerShell Core: -UseBasicParsing is legacy, and $response.BaseResponse.RequestMessage.RequestUri is not available on WinPS 5.1. If this script is intended for Azure Pipelines (often WinPS 5.1), use a response URI property that exists there (e.g., BaseResponse.ResponseUri) and avoid -UseBasicParsing.
| $response = Invoke-WebRequest -Uri $sdkRedirectUrl -Method HEAD -MaximumRedirection 10 -UseBasicParsing | |
| if ($EnableVerboseLogging) { | |
| Write-Host " [VERBOSE] Response status: $($response.StatusCode)" | |
| Write-Host " [VERBOSE] Response headers: $($response.Headers | ConvertTo-Json -Compress)" | |
| } | |
| # PowerShell Core: Get final URL from response object | |
| $finalUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri | |
| $response = Invoke-WebRequest -Uri $sdkRedirectUrl -Method HEAD -MaximumRedirection 10 | |
| if ($EnableVerboseLogging) { | |
| Write-Host " [VERBOSE] Response status: $($response.StatusCode)" | |
| Write-Host " [VERBOSE] Response headers: $($response.Headers | ConvertTo-Json -Compress)" | |
| } | |
| # Get final URL from response object (compatible with Windows PowerShell 5.1 and PowerShell Core) | |
| $finalUrl = $response.BaseResponse.ResponseUri.AbsoluteUri |
| 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 |
There was a problem hiding this comment.
Get-LatestVersionFromString compares pre-release suffixes lexicographically (string compare). This gives incorrect ordering for versions like -preview.10 vs -preview.2, which can cause the script to select the wrong "latest" version. Consider using a SemVer/NuGetVersion-aware comparison (or parsing numeric components of the suffix) instead of plain string comparison.
| 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 | ||
| } |
There was a problem hiding this comment.
Get-LatestVersionFromString compares pre-release suffixes lexicographically (string compare), which can order -preview.10 before -preview.2. This can select the wrong latest SDK/MSBuild SDK version when pre-releases are involved. Use a SemVer/NuGetVersion-aware comparison (or parse numeric suffix parts) instead.
| [Parameter(Mandatory = $false)] | ||
| [bool]$FailOnError = $false | ||
| ) | ||
|
|
There was a problem hiding this comment.
$SourcesDirectory defaults to $env:BUILD_SOURCESDIRECTORY but the script doesn’t normalize/validate it like the package update script does. If the env var is unset/empty, Join-Path will fail. Add the same fallback logic (e.g., $env:GITHUB_WORKSPACE then Get-Location) and validate the directory exists.
| # Normalize and validate the sources directory to avoid Join-Path failures | |
| 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 { | |
| # Resolve to a full, normalized path and ensure it exists as a directory | |
| $resolvedSourcesDirectory = Resolve-Path -Path $SourcesDirectory -ErrorAction Stop | |
| if (-not (Test-Path -Path $resolvedSourcesDirectory -PathType Container)) { | |
| throw "Sources directory '$SourcesDirectory' is not a valid directory." | |
| } | |
| $SourcesDirectory = $resolvedSourcesDirectory.ProviderPath | |
| } | |
| catch { | |
| $message = "Failed to resolve sources directory from '$SourcesDirectory'." | |
| if ($FailOnError) { | |
| throw "$message Error: $($_.Exception.Message)" | |
| } else { | |
| Write-Warning "$message Falling back to current location." | |
| $fallback = (Get-Location).Path | |
| $SourcesDirectory = $fallback | |
| } | |
| } |
| # 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 repo root uses NuGet.Config (capital C), but this script looks for NuGet.config. On case-sensitive filesystems the config won’t be found, so package searches may miss required feeds. Update the filename (or check both casings).
| # 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 / NuGet.Config exists in the sources directory | |
| $nugetConfigLower = Join-Path $SourcesDirectory "NuGet.config" | |
| $nugetConfigUpper = Join-Path $SourcesDirectory "NuGet.Config" | |
| $configSourceFlag = "" | |
| $nugetConfigPath = $null | |
| if (Test-Path $nugetConfigLower) { | |
| $nugetConfigPath = $nugetConfigLower | |
| } elseif (Test-Path $nugetConfigUpper) { | |
| $nugetConfigPath = $nugetConfigUpper | |
| } | |
| if ($nugetConfigPath) { | |
| $configSourceFlag = "--configfile `"$nugetConfigPath`"" | |
| if ($EnableVerboseLogging) { | |
| Write-Host " [VERBOSE] Using NuGet config from: $nugetConfigPath" | |
| } | |
| } else { | |
| Write-Warning "NuGet config file not found (checked: $nugetConfigLower, $nugetConfigUpper) - search may not find private feeds" |
This PR was created automatically by the workflow run 21805928402.