Skip to content

A dotnet tool that updates packages for all solutions in a directory.

License

Notifications You must be signed in to change notification settings

SimonCropp/PackageUpdate

Repository files navigation

PackageUpdate

Build status NuGet Status

A dotnet tool that updates packages for all solutions in a directory.

See Milestones for release notes.

Requirements/Caveats

NuGet package

https://nuget.org/packages/PackageUpdate/

Installation

Ensure dotnet CLI is installed.

Install PackageUpdate

dotnet tool install -g PackageUpdate

Performance characteristics

73 seconds for the following scenario

  • 50 solutions
  • 2 NuGet sources
  • 384 nuget packages
  • Network (Mbps): 94 up / 35 down. 17ms ping

Usage

packageupdate C:\Code\TargetDirectory

If no directory is passed the current directory will be used.

Arguments

Target Directory

packageupdate C:\Code\TargetDirectory
packageupdate -t C:\Code\TargetDirectory
packageupdate --target-directory C:\Code\TargetDirectory

Package

The package name to update. If not specified, all packages will be updated.

packageupdate -p packageName
packageupdate --package packageName

Build

Build the solution after the update

packageupdate -b
packageupdate --build

Behavior

  • Recursively scan the target directory for all directories containing a .sln file.
  • Perform a dotnet restore on the directory.
  • Recursively scan the directory for *.csproj files.
  • Call dotnet list package to get the list of pending packages.
  • Call dotnet add package with the package and version.

PackageUpdateIgnores

When processing multiple directories, it is sometimes desirable to "always ignore" certain directories. This can be done by adding a PackageUpdateIgnores environment variable:

setx PackageUpdateIgnores "AspNetCore,EntityFrameworkCore"

The value is comma separated.

Add to Windows Explorer

Use context-menu.reg to add PackageUpdate to the Windows Explorer context menu.

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\Shell]
@="none"
[HKEY_CLASSES_ROOT\Directory\shell\packageupdate]
"MUIVerb"="run packageupdate"
"Position"="bottom"
[HKEY_CLASSES_ROOT\Directory\Background\shell\packageupdate]
"MUIVerb"="run packageupdate"
"Position"="bottom"
[HKEY_CLASSES_ROOT\Directory\shell\packageupdate\command]
@="cmd.exe /c packageupdate \"%V\""
[HKEY_CLASSES_ROOT\Directory\Background\shell\packageupdate\command]
@="cmd.exe /c packageupdate \"%V\""

snippet source | anchor

Authenticated feed

To use authenticated feed, add the packageSourceCredentials to the global nuget config:

<packageSourceCredentials>
<feedName>
    <add key="Username" value="username" />
    <add key="ClearTextPassword" value="api key" />
</feedName>
</packageSourceCredentials>

Package Version Pinning

Overview

prevent specific packages from being automatically updated by adding the Pinned="true" attribute to package entries in the Directory.Packages.props file.

Usage

Pin a Single Package

<Project>
  <ItemGroup>
    <PackageVersion Include="System.ValueTuple" Version="4.5.0" Pinned="true" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
  </ItemGroup>
</Project>

In this example:

  • System.ValueTuple will remain at version 4.5.0 and will not be updated
  • Newtonsoft.Json will be updated to the latest version when running the updater

Pin Multiple Packages

<Project>
  <ItemGroup>
    <PackageVersion Include="System.ValueTuple" Version="4.5.0" Pinned="true" />
    <PackageVersion Include="Microsoft.AspNetCore.App" Version="6.0.0" Pinned="true" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
  </ItemGroup>
</Project>

Document Why a Package is Pinned

It's good practice to add comments explaining why a package is pinned:

<Project>
  <ItemGroup>
    <!-- Pinned: v4.6+ breaks compatibility with .NET Framework 4.6.1 -->
    <PackageVersion Include="System.ValueTuple" Version="4.5.0" Pinned="true" />
    
    <!-- Pinned: Newer versions require EF Core migration -->
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="6.0.10" Pinned="true" />
    
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
  </ItemGroup>
</Project>

Behavior

When Running Update All Packages

dotnet run -- update
  • All packages without Pinned="true" will be checked for updates
  • Pinned packages are skipped entirely

When Running Update Specific Package

dotnet run -- update --package System.ValueTuple
  • Even when explicitly targeting a pinned package, it will not be updated
  • The pin is always respected, regardless of how the updater is invoked

Common Use Cases

1. Breaking Changes

Pin packages when newer versions introduce breaking changes you're not ready to handle:

<PackageVersion Include="AutoMapper" Version="10.1.1" Pinned="true" />

2. Framework Constraints

Pin packages that have specific framework version requirements:

<!-- Required for .NET Framework 4.7.2 compatibility -->
<PackageVersion Include="System.Memory" Version="4.5.4" Pinned="true" />

3. Security Fixes

Pin to a specific patched version while waiting for a proper migration:

<!-- Pinned to security patch - v8.x requires major refactoring -->
<PackageVersion Include="IdentityServer4" Version="4.1.2" Pinned="true" />

4. Performance Regressions

Pin when a newer version causes performance issues:

<!-- v6.x has known performance regression in our scenario -->
<PackageVersion Include="Dapper" Version="2.0.123" Pinned="true" />

5. Vendor Dependencies

Pin packages that must match versions used by third-party SDKs:

<!-- Must match version used by Acme.ThirdPartySDK -->
<PackageVersion Include="Newtonsoft.Json" Version="12.0.3" Pinned="true" />

Unpinning a Package

To allow a package to be updated again, remove the Pinned="true" attribute:

<!-- Before -->
<PackageVersion Include="System.ValueTuple" Version="4.5.0" Pinned="true" />

<!-- After -->
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />

The next time you run the updater, it will update to the latest version.

Technical Details

  • The Pinned attribute is a custom attribute used by this updater tool
  • It has no effect on NuGet's normal package resolution
  • The attribute follows MSBuild conventions (similar to how Pinned works in project files)
  • Comments and formatting around pinned packages are preserved during updates

Automatic Package Migration

Overview

PackageUpdate automatically detects and migrates deprecated NuGet packages to their recommended alternatives. When a package is marked as deprecated on NuGet.org with an alternative package specified, the tool will automatically replace it during updates.

How It Works

When updating packages, PackageUpdate:

  1. Checks if the current version of each package is marked as deprecated
  2. If an alternative package is specified in the deprecation metadata:
    • Verifies the alternative package exists in configured NuGet sources
    • Checks that the alternative doesn't already exist in Directory.Packages.props
    • Replaces the package reference with the alternative
    • Sets the version to the latest available version of the alternative
  3. Logs the migration with the deprecation reason

Example Migration

Before:

<Project>
  <ItemGroup>
    <PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
  </ItemGroup>
</Project>

After running packageupdate:

<Project>
  <ItemGroup>
    <PackageVersion Include="Azure.Storage.Common" Version="12.26.0" />
    <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>
</Project>

Console output:

Migrated WindowsAzure.Storage -> Azure.Storage.Common (Version: 12.26.0) [Deprecated: Legacy]
Updated Newtonsoft.Json: 13.0.1 -> 13.0.3

Migration Behavior

Automatic by Default

Migrations happen automatically without requiring any flags or configuration. The tool detects deprecated packages and migrates them seamlessly.

Pinned Packages Are Never Migrated

If a package is pinned, it will not be migrated even if it's deprecated:

<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" Pinned="true" />

This package will remain unchanged.

When Alternative Already Exists

If the alternative package already exists in Directory.Packages.props, the migration is skipped:

<Project>
  <ItemGroup>
    <PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" />
    <PackageVersion Include="Azure.Storage.Common" Version="12.0.0" />
  </ItemGroup>
</Project>

Output:

Package WindowsAzure.Storage is deprecated with alternative Azure.Storage.Common, but alternative already exists

Both packages remain in the file, and only Azure.Storage.Common gets updated to the latest version.

When No Alternative is Available

If a package is deprecated but has no alternative specified, the tool logs a warning and continues with normal version update:

Package SomeDeprecatedPackage is deprecated but has no alternative. Reasons: Legacy

Current Version Check

The tool only migrates if the current version you're using is deprecated. If you're on an older, non-deprecated version, and only newer versions are deprecated, no migration occurs. This prevents unnecessary migrations when you're deliberately staying on an older version.

Specific Package Flag

The migration feature works with the --package flag:

packageupdate --package WindowsAzure.Storage

If WindowsAzure.Storage is deprecated with an alternative, it will be migrated automatically.

Common Scenarios

Scenario 1: Microsoft Azure SDK Packages

Many older Azure SDK packages have been deprecated in favor of the new Azure SDK:

  • WindowsAzure.StorageAzure.Storage.Common or Azure.Storage.Blobs
  • Microsoft.Azure.Storage.BlobAzure.Storage.Blobs
  • Microsoft.Azure.DocumentDBMicrosoft.Azure.Cosmos

These migrations happen automatically when you run packageupdate.

Scenario 2: Preventing Migration

If you want to prevent migration of a deprecated package (e.g., you're not ready to migrate yet), pin the package:

<!-- Pinned: Not ready to migrate to Azure.Storage.Blobs yet -->
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" Pinned="true" />

Scenario 3: Manual Review After Migration

After automatic migration, you may want to:

  1. Review the changes in Directory.Packages.props
  2. Update your code to use the new package's API (if breaking changes exist)
  3. Test thoroughly before committing

The migration updates the package reference but doesn't modify your source code.

Logging

Successful Migration

Migrated WindowsAzure.Storage -> Azure.Storage.Common (Version: 12.26.0) [Deprecated: Legacy]

Migration Skipped (Alternative Exists)

Package WindowsAzure.Storage is deprecated with alternative Azure.Storage.Common, but alternative already exists

Migration Skipped (No Alternative)

Package MyOldPackage is deprecated but has no alternative. Reasons: Legacy, Critical Bugs

Migration Skipped (Alternative Not Found)

Package OldPackage is deprecated with alternative NewPackage, but alternative not found in sources

Technical Details

  • Migration uses NuGet's official deprecation metadata API (PackageDeprecationMetadata)
  • The AlternatePackage information comes directly from package authors via NuGet.org
  • File formatting, comments, and XML structure are preserved during migration
  • Migrations are logged distinctly from version updates for clarity

Icon

Update by Andy Miranda from The Noun Project.

About

A dotnet tool that updates packages for all solutions in a directory.

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

 

Contributors 5

Languages