Inventory what you have
You can't plan a migration without knowing what you're migrating. "We should move to Microsoft DSC someday" is not a plan. But a spreadsheet with so many rows and a decision in each one is.
A PowerShell DSC estate typically contains four kinds of artifacts, and each one migrates differently.
The four artifact types
| Artifact | What it looks like | Migration concern |
|---|---|---|
| Configuration scripts | .ps1 files using the Configuration keyword |
Rewrite as configuration documents (or import MOF) |
| Compiled MOF files | localhost.mof, webserver01.mof on pull servers |
Import extension translates them |
| Script-based resources | Modules with Get/Set/Test-TargetResource functions |
Run via the 5.1 adapter, or rewrite as class-based |
| Class-based resources | PowerShell classes with Get(), Set(), Test() |
Run via either adapter, mostly as-is |
Start with the resources, because everything else depends on them. A configuration you can't run is a configuration whose resources didn't migrate.
Enumerate what PowerShell DSC sees
On a machine with your modules installed, the classic enumeration is:
Get-DscResource | Select-Object Name, ModuleName, Version, ImplementationDetail
The ImplementationDetail property is the important one:
ScriptBased— the module implements theGet-TargetResource,Set-TargetResource, andTest-TargetResourcefunction trio alongside a schema MOF file.ClassBased— it's a PowerShell class decorated with[DscResource()].- Composite resources — configurations packaged as resources. They show up too, and they need special handling covered in Module 2.
[!NOTE]
Run this in Windows PowerShell 5.1, not PowerShell 7.Get-DscResourcein PowerShell 7 comes from a different module version and enumerates a different set. For inventory purposes you want the 5.1 view, because that's the view the LCM has been using.
Ask the same question from the other side
Now find out what Microsoft DSC can already see. The adapters perform this discovery for you:
dsc resource list --adapter Microsoft.Adapter/WindowsPowerShell
dsc resource list --adapter Microsoft.Adapter/PowerShell
The first command lists everything the Windows PowerShell 5.1 adapter can find — script-based and class-based resources. The second lists what the PowerShell 7+ adapter can find, which is class-based resources only.
That difference is the entire inventory in one line:
- The 5.1 adapter maintains full backwards compatibility with script-based resources.
- The PowerShell 7+ adapter deliberately supports only class-based resources.
If a resource shows up in the first list but not the second, you've found a migration decision point.

Make the inventory systematic
Comparing two console outputs by eye works for ten resources and fails for a hundred. The following script joins both views into a single migration-oriented report. Save it as Get-DscMigrationInventory.ps1.
# Get-DscMigrationInventory.ps1
[CmdletBinding()]
param()
$legacy = Get-DscResource -ErrorAction SilentlyContinue
$adapted = dsc resource list --adapter Microsoft.Adapter/WindowsPowerShell --output-format json |
ConvertFrom-Json
$adaptedPs7 = dsc resource list --adapter Microsoft.Adapter/PowerShell --output-format json |
ConvertFrom-Json
foreach ($resource in $legacy) {
$moduleName = if ($resource.ModuleName) { $resource.ModuleName } else { '(binary)' }
$adaptedType = '{0}/{1}' -f $moduleName, $resource.Name
[PSCustomObject]@{
Resource = $adaptedType
Implementation = $resource.ImplementationDetail
VisibleTo51 = ($adapted.type -contains $adaptedType)
VisibleToPs7 = ($adaptedPs7.type -contains $adaptedType)
SuggestedPath = if ($adaptedPs7.type -contains $adaptedType) {
'Adapt (PowerShell 7+)'
} elseif ($adapted.type -contains $adaptedType) {
'Adapt (5.1), consider class-based rewrite'
} else {
'Investigate: not discovered by any adapter'
}
}
}
[!NOTE]
This is the same inventory script used in The Microsoft DSC Handbook, Chapter 11. If you own the book, you'll recognize it; if you don't, the script above is complete and you don't need anything else.
Read the script in three parts:
$legacyis the classic view with every resource Windows PowerShell can find on$env:PSModulePath.$adaptedand$adaptedPs7are the two adapter views, requested as JSON so they can be queried rather than read.- The loop builds the
Module/Resourcetype string that a configuration document would use, then checks it against both adapter views.
Run it and you get a table that classifies every resource on the machine:
Resource Implementation VisibleTo51 VisibleToPs7 SuggestedPath
-------- -------------- ----------- ------------ -------------
BinderyOps/AppEnvironment ScriptBased True False Adapt (5.1), consider class-based rewrite
PSDesiredStateConfiguration/File Binary True False Adapt (5.1), consider class-based rewrite
BinderyOps/AppRegistration ClassBased True True Adapt (PowerShell 7+)
The SuggestedPath column is a starting point, not a verdict. A resource that the PowerShell 7+ adapter can see might still deserve a native rewrite; a resource stuck on 5.1 might be scheduled for deletion instead. But the column turns a vague intention into a concrete list you can walk through in a planning meeting.
[!WARNING]
Get-DscResourcein Windows PowerShell enumerates from$env:PSModulePathas seen by 5.1, whiledsc resource listgoes through the adapter's own discovery. If the two disagree on what exists, check which module paths each host sees before assuming a resource is broken. This is by far the most common source of "it works in PowerShell but DSC can't find it" reports.
Inventory the rest of the estate
Resources are the hard part, but three more sweeps complete the picture:
Configuration scripts. Search your repositories for the Configuration keyword:
Get-ChildItem -Path .\ -Filter *.ps1 -Recurse |
Select-String -Pattern '^\s*Configuration\s+\w+' |
Select-Object Path, Line
For each hit, record which resources it imports and whether the script is still the source of truth for a compiled MOF somewhere.
Compiled MOFs. On a pull server, the configuration store is the authoritative list. Locally, they're wherever your build wrote them:
Get-ChildItem -Path .\ -Filter *.mof -Recurse |
Select-Object FullName, LastWriteTime, Length
The LastWriteTime matters more than you'd expect. A MOF compiled four years ago whose source script no longer exists is a Path B candidate. A MOF regenerated by last night's build is a Path A candidate and plan to migrate the script instead.
Hidden cross-node dependencies. These bite late, so find them early:
Get-ChildItem -Path .\ -Filter *.ps1 -Recurse |
Select-String -Pattern 'WaitFor(All|Any|Some)'
Every hit is a piece of orchestration logic that has no direct equivalent in Microsoft DSC. Module 3 explains what to do with them. For now, just know they exist.
What good looks like
A finished inventory answers four questions for every row:
- What is it? Artifact type and implementation detail.
- Who owns it? A team name, not "operations."
- Is it still used? Last applied, last compiled, last modified.
- Which path? One of the four you'll meet in unit 5.
The fourth column is the one this course exists to fill. The first three are yours, and they're worth more than they look: a surprising share of every real migration is deleting things nobody had the authority to delete before.
> EOF