CPMigrate
CPMigrate / .NET platform tooling · v3.65.0

One command to Central Package Management.

every version lands in Directory.Packages.props

A .NET global tool that migrates solutions to CPM, scores dependency health across 11 analyzers, and updates packages with test verification — rolling back the instant dotnet test goes red.

⌘K command palette history Tab complete
11 analyzers 4+1 output formats .sln + .slnx SDK 8.0+ MIT
cpmigrate — MySolution.sln — zsh interactive
this is a real shell simulation · help analyze doctor migrate bisect tree clear
01
analysis

Everything that silently rots a dependency graph

Eleven analyzers run against your real project files and the resolved NuGet graph. Every finding carries a stable rule ID — hover a row for the command that explains it.

CriticalSecurityVulnerabilityKnown CVEs in direct and transitive dependencies, straight from the NuGet audit source.SecurityVulnerability
HighInlineVersionUnderCpmCPM drift: inline versions overriding the central pin, missing pins, orphaned entries, CPM switched off.InlineVersionUnderCpm
ModerateVersionInconsistencySame package pinned at different versions across projects — assembly-binding surprises waiting to happen.VersionInconsistency
ModerateTransitiveConflictDivergent transitive graphs; the fixer pins the winner into Directory.Packages.props.TransitiveConflict
HighLicenseRiskCopyleft (GPL/AGPL) and proprietary licenses flagged before legal finds them for you.LicenseRisk
LowDuplicatePackageCasingNewtonsoft.Json vs newtonsoft.json — dedup that the casing difference hid.DuplicatePackageCasing
LowOutdatedPackageVersions behind the feed, with the latest available alongside — fuel for --update-packages.OutdatedPackage
0/100GOOD
Analyzer scoreboard · MySolution.sln
! Version Inconsistencies03 FOUND
Security Vulnerabilities01 FOUND
Duplicate Packages0PASS
Transitive Conflicts0PASS
Redundant References0PASS
02
migration

See exactly what CPM changes

Inline Version attributes vanish from every project; one central PackageVersion per package appears instead. Drag the divider to compare.

before · messy
<!-- Api.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Serilog" Version="3.1.1" />
    <PackageReference Include="Polly" Version="8.4.1" />
  </ItemGroup>
</Project>

<!-- Worker.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Serilog" Version="4.0.2" />
    <PackageReference Include="Polly" Version="8.6.4" />
  </ItemGroup>
</Project>
after · CPM
<!-- Api.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Serilog" />
    <PackageReference Include="Polly" />
  </ItemGroup>
</Project>

<!-- Directory.Packages.props -->
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</…>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Serilog" Version="4.2.0" />
    <PackageVersion Include="Polly"    Version="8.6.4" />
  </ItemGroup>
</Project>
03
safe updates

Bisect, don't revert

All-or-nothing rollback throws away 37 good updates because of one bad one. --bisect banks the green half each round and probes the rest — keeping ~36/38 in about 2·log₂(n) test runs.

round 1 · probe all 8✖ red
SerilogAutoMapperPollyDapperMoqMediatRFluentVal.Newtonsoft
round 2 · bank the green half4 banked
SerilogAutoMapperPollyDapperMoqMediatRFluentVal.Newtonsoft
round 3 · split the red, bank green✖ red
SerilogAutoMapperPollyDapperMoqMediatRFluentVal.Newtonsoft
final · isolate the culprit✔ green
SerilogAutoMapperPollyDapperMoqMediatRFluentVal.Newtonsoft
probing banked green held back

Kept 36 / 38 with tests green

The two it couldn't clear stay at their old version and get named, so you investigate exactly two packages — not thirty-eight.

BISECT RESULT
HELD    Serilog: 3.1.1 → 4.2.0 (left at 3.1.1)
HELD    AutoMapper: 12.0.1 → 14.0.0
APPLIED Polly 8.4.1→8.6.4 · Dapper · Moq · …34 more
Kept 36/38 updates with tests green (9 runs)
→ cpmigrate --update-packages --only Serilog,AutoMapper
Safe update flow →
04
30-second path

From version soup to CPM in five commands

01

Check the environment

SDK, NuGet reachability, workspace, git — one diagnostic table.

cpmigrate --doctor
02

Score the graph

CI-safe exit codes: 0 clean · 5 findings · 8 incomplete scan.

cpmigrate --analyze --audit --outdated --deprecated
03

Preview as a diff

--diff renders the exact Directory.Packages.props change before anything is written.

cpmigrate -s ./MySolution.sln --dry-run --diff
04

Migrate, and prove it

Generates the props file, strips inline versions, keeps a timestamped backup — then --verify restores before and after and diffs the resolved graph. Exit 9 and an automatic rollback on drift nothing accounts for.

cpmigrate -s ./MySolution.sln --verify
05

Update without fear

Tests fail? Everything rolls back. With --bisect the largest green subset stays.

cpmigrate --update-packages --bisect
05
the whole toolbox

Built around the moments that break

Resolved-graph proof

--verify answers the only question a migration PR raises: does this change what we ship? Every moved version, attributed to the decision that caused it.

cpmigrate -s ./Sln.sln --verify

Environment doctor

One command verifies SDK, feed, workspace and git before you touch anything.

cpmigrate --doctor

Config scaffolding

--init writes a .cpmigrate.json of team defaults — interactive or CI-safe.

cpmigrate --init

Workspace status

A repository-context dashboard without entering the wizard.

cpmigrate --status

Dependency tree

Direct + transitive packages per project, rendered as ASCII.

cpmigrate --tree --transitive

License radar

Copyleft and proprietary licenses flagged from the project file alone.

cpmigrate --analyze --licenses

Spreadsheet export

--output Csv dumps one row per finding for trend tracking.

cpmigrate --analyze --output Csv

Batch / monorepo

Recursive solution discovery, parallel, continue-on-failure, isolated backups.

cpmigrate --batch ./repo --batch-parallel

Unify props

Promote repeated properties into Directory.Build.props.

cpmigrate --unify-props
06
shipping cadence

Twenty releases in one pass

--doctorEnvironment diagnostics
--initScaffold .cpmigrate.json
--statusWorkspace health dashboard
--treeASCII dependency tree
--diffUnified-diff dry-run
--licensesLicense radar
--output CsvSpreadsheet export
scoreboardHealth gauge + STATUS
07
alternatives

Against doing it by hand

ApproachGood fitWhere it falls short
Manual CPM migrationOne-off experiments, tiny solutionsSlow, easy to miss references, impossible to repeat
Ad hoc scriptsRepo-specific automationBrittle logic, no analysis, no rollback you'd trust at 2 a.m.
Raw dotnet package listInventory and vulnerability dataNo migration, no fixers, no central props generation
CPMigrateTeams adopting CPM across real solutionsPurpose-built for exactly this path
08
contract

Exit codes, like a man page

The numbers a CI gate is written against — and the one thing a script can't discover by trying.

CPMIGRATE(1)CPMigrate exit codes
Exit status
0Success — operation completed successfully.
1ValidationError — invalid command-line options.
2FileOperationError — file I/O or permission failure.
3VersionConflict — unresolvable conflict with --conflict-strategy Fail.
4NoProjectsFound — no project files discovered.
5AnalysisIssuesFound — analysis detected issues (the CI gate).
6UnexpectedError — unhandled exception.
7TestFailure — tests failed after update; rollback performed.
8IncompleteAnalysis — a scan didn't finish; treat as re-run, never as clean.
9GraphDrift — verify found the resolved graph moved unexplained, or couldn't prove it hadn't.
10RemediationIncomplete — remediate ran but an advisory is still there.
09
see it work

Twenty seconds of the real thing

10
faq

Asked before you ask

How do I migrate a .NET solution to Central Package Management?

Run cpmigrate -s ./MySolution.sln --dry-run (add --diff for a unified diff), then cpmigrate -s ./MySolution.sln to apply. CPMigrate extracts every <PackageReference>, resolves conflicts, generates Directory.Packages.props, and strips inline Version attributes — with a backup you can roll back to.

Does migrating to CPM change what my code builds against?

It can — which is what --verify is for. Moving a version into Directory.Packages.props is a no-op, but when two projects disagree about a package the migration must pick one, and --conflict-strategy Highest silently upgrades the loser. git diff cannot show you that. --verify restores before and after, diffs the fully-resolved graph per project and target framework, and reports every version that moved with the decision that caused it. Anything unaccounted for exits 9 and rolls the migration back.

What is Directory.Packages.props?

The file NuGet Central Package Management reads versions from, so every project shares one version per package. CPMigrate generates and maintains it. Microsoft's CPM docs cover the format.

Can CPMigrate roll back a bad package update?

Two ways. --update-packages runs dotnet test and rolls back on failure. --update-packages --bisect keeps the largest subset that stays green and names the held-back packages. Migrations get timestamped backups restorable with cpmigrate --rollback.

Does CPMigrate work in CI/CD?

Built for it. --output Json --quiet for strict JSON against a published schema; --output Sarif for PR annotations; --output Markdown for the step summary; --output Csv for spreadsheets. Exit code 8 means the scan didn't complete; 9 means a --verify migration moved the resolved graph in a way nothing accounts for.

Does it support .slnx and monorepos?

Yes — classic .sln and VS 17.10+ .slnx, and --batch recursively discovers every solution, optionally in parallel and continue-on-failure, with an isolated backup per solution.

Can it gate on vulnerabilities without failing on existing debt?

Yes. --audit scans direct and transitive CVEs; --fail-on High narrows the gate while still reporting everything; --write-baseline records today's findings once so CI fails only on new debt.

install

Start with one command

dotnet tool install --global CPMigrate --version 3.65.0
cpmigrate --doctor
brew tap georgepwall1991/cpmigrate
brew install cpmigrate
winget install GeorgeWall.CPMigrate

Available after Microsoft indexes the package; use the Windows portable release meanwhile.

copied ✔