How Microsoft's DSC engine is shaping the new adapter model

How Microsoft's DSC engine is shaping the new adapter model

What if you could ship a new DSC resource without writing a single line of code?

From the early days, Microsoft DSC bridged the gap between the new engine and everything that isn't a native command-based DSC resource. Think about the PowerShell DSC resources that have existed in the DSC community for a long time or WMI classes (the Microsoft.Windows/WMI adapter).

They made the ecosystem usable and, on some level, understandable to each other. But as we learned over time, they carried a lot of internal logic to:

  • Discover resources from the subsystem.
  • Get the descriptions from each of them.
  • Validate the resources that got surfaced.

Yet, with time, a new PR quietly redefined what that bridge is. On the surface, it adds a new registry adapter and a new Microsoft.Windows/Personalization resource. But underneath, it moves the authority over an adapted resource away from the adapter and into the resource's own manifest. And the better part? DSC's engine now does the heavy lifting.

This article looks at what actually changed in more detail, and why it matters for anyone who wants to author resources.

The adapter model until now

To understand the shift, we've to take a step back and look at how the classic adapters work. An adapter is a command resource with kind: adapter in its resource manifest. Its dominant feature is the list operation: when DSC needs to know which adapted resources exist, it runs the adapter, and the adapter enumerates a subsystem at runtime.

Figure 1: DSC's engine discovering subsystems (WMI, PowerShell, ...)

For example, PowerShell's adapter walks the $env:PSModulePath looking for modules with DSC resources, whereas the WMI adapter just queries for the available classes on the system.

So, here's the thing. Because historically, adapted resources did not have manifests of their own (they were discovered through the list operation), the adapter is the single source of truth for everything about them. Think about the DSC resource name (and the module), their capabilities (get, set, test, ...), their property schemas, and whether a given configuration is actually even valid.

Validation sort of happens through the adapter, if the validate capability does a thing.

[!NOTE]
The validate capability was never officially implemented in PowerShell's adapter, nor in the WMI one. validate is a deprecated feature.

As you can see, this design works, but it has a real cost. Discovery means starting that strange runtime just to find what exists. So some of the adapters grew caching layers and lookup tables to stay fast. Validation errors became tough, as they're surfaced directly through the adapter rather than the engine. So they vary in quality from one adapter to the next. And there's no way to know a resource's shape without invoking its adapter first.

The adapter, in short, is a discovery engine. It's powerful, but heavy.

What has been introduced

The earlier-mentioned pull request builds on a newer idea in the DSC codebase: the adapted resource manifest. Instead of only existing once an adapter discovers it at runtime, an adapted resource is now a standalone YAML or JSON file with kind: resource, its own type, version, and capabilities, plus arequireAdapter property naming the adapter that executes it. DSC discovers these manifests through its normal discovery pipeline, exactly like native command resources.

{
  "$schema": "https://aka.ms/dsc/schemas/v3/bundled/adaptedresource/manifest.json",
  "type": "DatabricksDsc/AdaptedResource",
  "kind": "resource",
  "version": "1.0.0",
  "capabilities": ["get", "set"],
  "requireAdapter": "Microsoft.Windows/WindowsPowerShell",
  "content": {
    "exampleSetting": "data the adapter knows how to interpret"
  },
  "schema": {
    "embedded": {
      "type": "object",
      "properties": {
        "exampleSetting": { "type": "string" }
      }
    }
  }
}

The three engine changes in the PR make this model click into place:

  1. The engine owns validation: DSC now validates an instance against the JSON Schema embedded in the adapted resource's manifest, instead of calling the adapter's validate capability.
  2. Manifests can carry inline content: Alongside the existing path property, an adapted resource manifest can now define content. This is an arbitrary block of structured data that the adapter interprets, meaning that the manifest no longer just points at the resource - it can be the resource.
  3. The adapter receives that content at invocation time: A new adaptedContentArg argument type lets an adapter's manifest declare how the engine should pass that content along for the specific adapter. More on this later.

Putting those 3 bullet points in simple words: the adapter stops being a discovery engine and becomes a translation engine.

Figure 2: From chaos to order with adapted resource manifests

Registry adapter in practice

To put the proof in the pudding, we can take a look at the Microsoft.Windows/Personalization resource. This resource speaks the words already based on the naming: it's a resource for Windows personalization settings.

It contains light/dark mode, transparency, and accent color. The new Microsoft.Windows.Adapter/Registry is built on top of it, which contains roughly 260 lines of YAML and contains no code at all. Each property in its content block maps one JSON property to one registry value:

type: Microsoft.Windows/Personalization
kind: resource
requireAdapter: Microsoft.Windows.Adapter/Registry
content:
  appsUseLightTheme:
    keyPath: HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize
    valueName: AppsUseLightTheme
    valueType: REG_DWORD
    jsonType: boolean
    defaultValueIfNotFound: 0
    mapJsonToRegistry:
      'false': 0
      'true': 1
schema:
  embedded:
    # JSON Schema defining appsUseLightTheme as a boolean

The mapping vocabulary is small but expressive:

Property What it does
keyPath Locates the registry key that holds the value
valueName Names the specific registry value under that key
valueType Declares the registry data type, such as REG_DWORD
jsonType Declares the user-facing type, such as boolean or stringArray
mapJsonToRegistry Translates each JSON value to its registry equivalent
defaultValueIfNotFound Provides a fallback when the key or value is absent, which often has a well-known meaning in the registry

That last value, defaultValueIfNotFound matters because an absent registry value has a well-known meaning of its own. Of course, the above scales beyond booleans. For example, the manifest maps the Start menu's folder list, stored as REG_BINARY GUIDs, onto a friendly string array of names like Documents and Downloads.

Here's the nice part about such a model. Users get a typed, discoverable API over raw registry plumbing:

resources:
- name: Dark mode everywhere
  type: Microsoft.Windows/Personalization
  properties:
    appsUseLightTheme: false
    systemUsesLightTheme: false

You declare appsUseLightTheme: false; the adapter writes a REG_DWORD of 0 to the right key. Nobody has to remember that path, and the embedded schema rejects a typo'd property before anything touches the registry.

Comparing the two models

With both models mentioned in this article, it's best to see the differences side by side. The short version: work that used to happen inside the adapter at runtime now happens in the engine, or doesn't need to happen at all.

Classic adapters (PowerShell, WMI) New model (registry adapter)
Resource discovery Runtime enumeration via list Static manifest, engine discovery
Schema & validation Adapter's validate function JSON Schema in the manifest, enforced by the engine
Resource definition Code (PowerShell module, WMI class) Declarative YAML (content mapping)
Invocation Nested resources array, adapter fans out Per instance, content passed via --adapted-resource
Performance profile Caches plus foreign runtime startup No cache needed; a single binary call
Who can author one Developers Anyone comfortable writing YAML

Knowing about the two models, two things are worth stating plainly. Firstly, the classic adapters aren't going anywhere. They remain the right design for surfacing existing subsystems, where the resources live elsewhere (and must be discovered). The new model shines when you're authoring new resources over a generic engine like the registry.

Secondly, the two models share one contract: from a configuration author's point of view, an adapted resource looks and behaves like any other resource. But the change in itself is in who does the work underneath.

Okay, there's a third one on an earlier promise mentioned about adaptedContentArg. Instead of receiving a batch of nested instances to fan out itself, the adapter declares an argument in its manifest, --adapted-resource in the registry adapter's case, and the engine uses it to hand over the manifest's content block together with the instance's desired state, one instance at a time.

Figure 3: Process flow of adapted resource with Registry adapter

Closing thoughts

The registry adapter is deliberately minimal. It implements only the type conversions the Personalization resource needs, with more promised as real use cases appear. But the pattern generalizes, and that's where it gets interesting:

  • The same shape can apply to INI, XML, files, to WMI, or to any settings stored with a regular structure.
  • One adapter binary can carry an open-ended family of zero-code resources, defined by the community as shareable manifests.
  • Because each manifest embeds its schema, tooling such as editors, linters, and documentation generators can understand these resources without executing anything.

If you want to give it a try, grab a recent DSC release and run dsc resource get -r Microsoft.WIndows/Personalization, then read through the Personalization manifest in the repo. It's the best documentation of the model today.