Migrate to NuGet Central Package Management | Analyze dependency health | Update packages safely | Auto-fix issues
Managing NuGet dependencies across large .NET solutions is painful. Version drift, duplicated references, transitive conflicts, and security vulnerabilities accumulate silently until they break your build or compromise your supply chain.
CPMigrate is a .NET global tool that automates the migration to NuGet Central Package Management (CPM), analyzes your entire dependency graph, auto-fixes common issues, and keeps packages up to date — with automatic rollback when tests fail.
- Migrates any .NET solution to CPM by generating
Directory.Packages.propsand cleaning.csprojfiles - Analyzes dependency health: version inconsistencies, duplicates, redundant references, transitive conflicts, framework alignment, and security vulnerabilities
- Auto-fixes detected issues with a single command
- Updates NuGet packages to latest versions, runs
dotnet test, and rolls back automatically on failure - Unifies repeated project properties into
Directory.Build.props - Batch processes monorepos with dozens of solutions in parallel
- Supports
.slnand the new.slnxformat (Visual Studio 17.10+)
- Installation
- Quick Start
- 60-Second Quickstart
- Features
- CLI Reference
- Exit Codes
- CI/CD Integration
- Examples & Benchmarks
- Release Cadence
- Telemetry (Opt-in)
- Community Growth
- Gallery
- Contributing
- License
Requires .NET SDK 8.0 or later. Targets .NET 10 with LatestMajor roll-forward.
dotnet tool install --global CPMigrateUpdate to the latest version:
cpmigrate --updateOr via the .NET CLI:
dotnet tool update --global CPMigrateNote: NuGet indexing may take up to 15 minutes after a new release. Clear your HTTP cache if updates aren't found immediately:
dotnet nuget locals http-cache --clear
git clone https://github.com/georgepwall1991/CPMigrate.git
cd CPMigrate
dotnet buildcpmigrateLaunches the Mission Control dashboard — a step-by-step wizard that guides you through migration, analysis, package updates, rollback, and more. The wizard adapts to your environment: when CPM is already enabled, it offers Update NuGet Packages as a quick action with prompts for transitive dependencies, pre-release versions, and dry-run mode.
cpmigrate -s ./MySolution.slncpmigrate -s ./MySolution.sln --dry-runcpmigrate --analyzecpmigrate --update-packages
# Include transitive dependencies
cpmigrate --update-packages --transitive# 1) Scan for issues (CI-safe exit codes)
cpmigrate --analyze --audit --outdated --deprecated --output Json --quiet > analysis.json
# 2) Preview migration
cpmigrate -s ./MySolution.sln --dry-run
# 3) Migrate to CPM
cpmigrate -s ./MySolution.sln
# 4) Safely update packages with rollback protection
cpmigrate --update-packages --dry-runScans your .sln or .slnx file, extracts all <PackageReference> entries from .csproj / .fsproj / .vbproj files, resolves version conflicts, and generates a centralized Directory.Packages.props.
# Standard migration
cpmigrate -s ./MySolution.sln
# Merge into an existing Directory.Packages.props
cpmigrate -s ./MySolution.sln --merge
# Fail if any version conflicts exist (strict mode)
cpmigrate -s ./MySolution.sln --conflict-strategy Fail
# Prompt for each conflict interactively
cpmigrate -s ./MySolution.sln --interactive-conflictsConflict resolution strategies:
| Strategy | Behavior |
|---|---|
Highest |
Use the highest version found across projects (default) |
Lowest |
Use the lowest version found |
Fail |
Exit with error if any package has conflicting versions |
Run 7 built-in analyzers without modifying any files:
cpmigrate --analyze| Analyzer | What it detects |
|---|---|
| Version Inconsistencies | Same package with different versions across projects |
| Duplicate Packages | Same package referenced with different casing (e.g., Newtonsoft.Json vs newtonsoft.json) |
| Redundant References | Same package referenced multiple times within a single project |
| Transitive Conflicts | Transitive dependencies with divergent versions across projects |
| Framework Alignment | Projects targeting different TargetFramework values |
| Redundant Direct References | Explicit references already provided transitively (lifting candidates) |
| Security Vulnerabilities | Known CVEs in direct and transitive dependencies (requires --audit) |
# Include transitive dependencies in analysis
cpmigrate --analyze --transitive
# Include security vulnerability scanning
cpmigrate --analyze --audit
# Full analysis with all checks
cpmigrate --analyze --transitive --audit
# Include outdated + deprecated checks
cpmigrate --analyze --outdated --deprecatedAutomatically fix detected issues:
# Fix all auto-fixable issues
cpmigrate --analyze --fix
# Preview what fixes would be applied
cpmigrate --analyze --fix-dry-runAvailable fixers:
| Fixer | What it fixes |
|---|---|
| Version Inconsistency Fixer | Standardizes package versions across projects using the configured conflict strategy |
| Duplicate Package Casing Fixer | Normalizes package name casing to the most common variant |
| Redundant Reference Fixer | Removes duplicate <PackageReference> entries within the same project |
| Transitive Conflict Pinner | Pins divergent transitive dependencies in Directory.Packages.props |
New in v3.0. Update all NuGet packages to their latest versions with automatic test verification and rollback. v3.2 adds full support in the interactive wizard — run cpmigrate -i and select "Update NuGet packages" from the maintenance menu.
# Preview available updates
cpmigrate --update-packages --dry-run
# Update packages, run tests, rollback on failure
cpmigrate --update-packages
# Include pre-release versions
cpmigrate --update-packages --include-prerelease
# Or use the interactive wizard (v3.2+)
cpmigrate -iNew in v3.1. Add --transitive to also scan and pin transitive (indirect) dependencies:
# Preview direct + transitive updates
cpmigrate --update-packages --transitive --dry-run
# Update direct packages and pin transitive deps
cpmigrate --update-packages --transitive
# With pre-release versions
cpmigrate --update-packages --transitive --include-prereleaseWhen --transitive is enabled, CPMigrate:
- Scans all projects via
dotnet list package --include-transitive - Deduplicates across projects (picks the highest resolved version)
- Excludes transitive deps already managed as direct dependencies
- Queries NuGet for the latest version of each transitive dep
- Shows separate DIRECT UPDATES and TRANSITIVE UPDATES sections
- Pins accepted transitive updates as new
<PackageVersion>entries inDirectory.Packages.props
Per-project scan failures are logged and skipped gracefully. If all scans fail, the tool continues with direct-only updates.
How it works:
- Reads current versions from
Directory.Packages.props - Queries the NuGet API for latest versions (8 concurrent lookups)
- Optionally scans transitive dependencies (
--transitive) - Shows a table of available updates (separate sections for direct and transitive)
- For major version bumps, prompts you interactively: accept or skip
- Minor/patch updates are auto-accepted
- Creates a backup of
Directory.Packages.props - Applies version updates and transitive pins atomically
- Runs
dotnet restorethendotnet test - Tests pass — keeps updates, cleans up backup
- Tests fail — rolls back all changes (including transitive pins) automatically
Requires CPM to be enabled. If
Directory.Packages.propsdoesn't exist, runcpmigratefirst to migrate. Transitive scanning requiresdotnet restoreto have been run beforehand.
Promote repeated properties and items from individual project files into a shared Directory.Build.props:
# Preview what would be unified
cpmigrate --unify-props --dry-run
# Apply unification
cpmigrate --unify-props
# Skip confirmation prompt
cpmigrate --unify-props --forceIdentifies properties and items present in at least 60% of projects with the same value (e.g., TargetFramework, ImplicitUsings, Nullable, Authors) and migrates them to the root-level file. Individual project files are cleaned up automatically.
Process multiple solutions in a monorepo:
# Sequential processing
cpmigrate --batch /path/to/repo
# Parallel processing (uses all CPU cores)
cpmigrate --batch /path/to/repo --batch-parallel
# Continue on failure
cpmigrate --batch /path/to/repo --batch-parallel --batch-continueRecursively discovers .sln and .slnx files, excluding common non-project directories (node_modules, bin, obj, .git, etc.). Each solution gets an isolated backup directory to prevent collisions.
Every migration creates a timestamped backup. Roll back at any time:
# Rollback to previous state
cpmigrate --rollback
# List all backups
cpmigrate --list-backups
# Prune old backups, keeping the last 3
cpmigrate --prune-backups --retention 3
# Delete all backups
cpmigrate --prune-allBackups are stored in .cpmigrate_backup/ with a JSON manifest for reliable restoration. Use --add-gitignore to automatically add the backup directory to .gitignore.
Create a .cpmigrate.json in your repository root to set default options:
{
"$schema": "https://raw.githubusercontent.com/georgepwall1991/CPMigrate/main/schemas/cpmigrate.schema.json",
"ConflictStrategy": "Highest",
"Backup": true,
"BackupDir": ".",
"AddGitignore": true,
"MergeExisting": false,
"OutputFormat": "Terminal",
"Retention": {
"Enabled": true,
"MaxBackups": 5
}
}The config file is discovered by walking up from the current directory. CLI arguments always take precedence over config file values.
| Option | Short | Default | Description |
|---|---|---|---|
--solution |
-s |
. |
Path to .sln / .slnx file or directory |
--project |
-p |
Path to a specific project file | |
--output-dir |
-o |
. |
Output directory for Directory.Packages.props |
--dry-run |
-d |
false |
Preview changes without modifying files |
--merge |
false |
Merge into existing Directory.Packages.props |
|
--conflict-strategy |
Highest |
Version conflict resolution: Highest, Lowest, Fail |
|
--interactive-conflicts |
false |
Prompt for each version conflict | |
--keep-attrs |
-k |
false |
Keep Version attributes in project files |
--interactive |
-i |
false |
Launch the interactive Mission Control wizard (migration, analysis, package updates, rollback, batch, backups) |
| Option | Short | Default | Description |
|---|---|---|---|
--analyze |
-a |
false |
Run dependency health analysis |
--transitive |
false |
Include transitive dependencies | |
--audit |
false |
Include security vulnerability scanning | |
--outdated |
false |
Include outdated package checks | |
--deprecated |
false |
Include deprecated package checks | |
--fix |
false |
Apply auto-fixes (requires --analyze) |
|
--fix-dry-run |
false |
Preview auto-fixes without applying |
| Option | Default | Description |
|---|---|---|
--update-packages |
false |
Update all packages to latest, run tests, rollback on failure |
--transitive |
false |
Also scan and pin transitive dependencies (v3.1) |
--include-prerelease |
false |
Include pre-release versions when updating |
| Option | Default | Description |
|---|---|---|
--unify-props |
false |
Migrate common properties to Directory.Build.props |
--force |
false |
Skip confirmation prompts |
| Option | Default | Description |
|---|---|---|
--batch |
Directory to scan recursively for solutions | |
--batch-parallel |
false |
Process solutions in parallel |
--batch-continue |
false |
Continue even if a solution fails |
| Option | Short | Default | Description |
|---|---|---|---|
--rollback |
-r |
false |
Restore from most recent backup |
--no-backup |
-n |
false |
Disable backup creation |
--backup-dir |
. |
Backup directory location | |
--list-backups |
false |
List all available backups | |
--prune-backups |
false |
Delete old backups based on --retention |
|
--prune-all |
false |
Delete all backups | |
--retention |
5 |
Number of backups to keep when pruning | |
--add-gitignore |
false |
Add backup directory to .gitignore |
| Option | Short | Default | Description |
|---|---|---|---|
--output |
Terminal |
Output format: Terminal or Json |
|
--output-file |
Write JSON output to a file | ||
--quiet |
-q |
false |
Suppress non-essential output |
--verbose |
-v |
false |
Enable diagnostic logging to cpmigrate.log |
| Option | Default | Description |
|---|---|---|
--update |
false |
Check for and install the latest version of CPMigrate |
| Code | Name | Meaning |
|---|---|---|
0 |
Success | Operation completed successfully |
1 |
ValidationError | Invalid command-line options |
2 |
FileOperationError | File I/O or permission failure |
3 |
VersionConflict | Unresolvable version conflict (with --conflict-strategy Fail) |
4 |
NoProjectsFound | No .csproj / .fsproj / .vbproj files discovered |
5 |
AnalysisIssuesFound | Analysis detected issues (useful for CI gates) |
6 |
UnexpectedError | Unhandled exception |
7 |
TestFailure | Tests failed after package update (rollback performed) |
Use --output Json --quiet to guarantee JSON-only stdout (no banners/preamble), which is ideal for CI parsing.
# analyze
cpmigrate --analyze --audit --outdated --deprecated --output Json --quiet > analyze.json
# migrate
cpmigrate -s ./MySolution.sln --dry-run --output Json --quiet > migrate.json
# rollback
cpmigrate --rollback --backup-dir . --output Json --quiet > rollback.json
# update-packages
cpmigrate --update-packages --dry-run --output Json --quiet > update-packages.json- name: Install CPMigrate
run: dotnet tool install --global CPMigrate
- name: Check dependency health
run: cpmigrate --analyze --audit --output Json --output-file analysis.json --quiet
- name: Fail on issues
run: |
EXIT_CODE=$(cpmigrate --analyze --audit --quiet; echo $?)
if [ $EXIT_CODE -eq 5 ]; then
echo "::error::Dependency issues detected"
exit 1
fiUse --output Json to produce machine-readable output for CI/CD pipelines:
cpmigrate --analyze --output Json --output-file report.json- Starter example:
examples/small-solution/ - Monorepo example:
examples/monorepo/ - Benchmark table:
docs/benchmarks.md
These sample repositories are designed for onboarding, CI templates, and reproducible before/after conversion demos.
- Stable releases: weekly, versioned and changeloged
- Release candidates (RC): published for fast feedback before stable promotion
- Change log source of truth:
CHANGELOG.md - Policy details:
docs/release-cadence.md
CPMigrate supports privacy-first telemetry that is disabled by default.
- Enable by setting
CPMIGRATE_TELEMETRY_OPT_IN=true - Captures only command-level metrics (operation, duration, exit code category, high-level flags)
- Captures no project paths, package names, file contents, or source code
- Stores local events at
~/.cpmigrate/telemetry/events.ndjson
- Enable GitHub Discussions and pin:
Start here(first-run flow + CI JSON mode)Success stories(before/after migration outcomes)
- Use Discussions as the primary intake for usage feedback and roadmap voting.
The interactive wizard assessing migration risk and guiding you through each step.
Previewing changes safely before committing.
Scanning for vulnerabilities and redundant dependencies.
Contributions are welcome. To get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Ensure all 487+ tests pass (
dotnet test) - Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
George Wall — @georgepwall1991

