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 the Get-TargetResource, Set-TargetResource, and Test-TargetResource function 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-DscResource in 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.

Figure 1: The gap between the two adapter views is your Path C backlog.

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:

  1. $legacy is the classic view with every resource Windows PowerShell can find on $env:PSModulePath.
  2. $adapted and $adaptedPs7 are the two adapter views, requested as JSON so they can be queried rather than read.
  3. The loop builds the Module/Resource type 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-DscResource in Windows PowerShell enumerates from $env:PSModulePath as seen by 5.1, while dsc resource list goes 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:

  1. What is it? Artifact type and implementation detail.
  2. Who owns it? A team name, not "operations."
  3. Is it still used? Last applied, last compiled, last modified.
  4. 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