Sooner or later you hit a setting that no DSC resource manages. A hosts file entry, a certificate with specific properties, some tweak that only exists as a PowerShell snippet on a wiki page. You know the PowerShell to fix it, but there is no resource to wrap it in.
One way is to reach for Microsoft.DSC.Transitional/RunCommandOnSet. It runs a command during the set operation, and that is all it does. There is no test, so DSC cannot tell you whether the machine is in the desired state. But it is a good fit as a temporary transitional resource while migrating DSCv3 resources.
There is another option: Microsoft.DSC.Transitional/PowerShellScript. It lets you bring your own getScript, testScript and setScript.
Note: If you are new to DSC 3.0 check out my blog series about it:
- Getting started with DSC 3.0 – Part 1: What is DSC and what changed?
- Getting started with DSC 3.0 – Part 2: Managing a Windows service end to end
- Getting started with DSC 3.0 – Part 3: Capturing the current state with dsc config export
- Getting started with DSC 3.0 – Part 4: Configuring IIS with multiple resources and dependsOn
- Getting started with DSC 3.0 – Part 5: Guarding your configuration with Assertion
- Getting started with DSC 3.0 – Part 6: Using the DSC MCP server with your AI coding tools
Why do we need this?
Every DSC resource implements the same operations: get, test and set. Most resources are written once and reused everywhere. With PowerShellScript you write those three operations yourself, inline in your configuration document.
It is a transitional resource, and the name says it all. It is a bridge between the declarative DSC model and the imperative scripts you already have. If you used the Script resource from PSDscResources in the past, the three properties will look familiar.
The three scripts
- getScript: returns the current state of the resource. It runs when you invoke
dsc config get. - testScript: returns
$truewhen the machine is in the desired state and$falsewhen it is not. It runs ondsc config test, and as the first step ofdsc config set. - setScript: makes the change. It only runs when
testScriptreturned$false.
The interesting part is the relation between the last two. testScript is the gatekeeper. If you get it right, setScript only touches the machine when it has to, and running the configuration twice is harmless.
Putting it into action
Let's make sure a hosts file entry exists. Not something you would do in production, but small enough to see all three scripts at work.
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Hosts entry for dsc.local
type: Microsoft.DSC.Transitional/PowerShellScript
properties:
getScript: |
$hosts = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
$entry = Get-Content $hosts | Where-Object { $_ -match '^\s*127\.0\.0\.1\s+dsc\.local\s*$' }
return @{ present = [bool]$entry }
testScript: |
$hosts = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
return [bool](Get-Content $hosts | Where-Object { $_ -match '^\s*127\.0\.0\.1\s+dsc\.local\s*$' })
setScript: |
$hosts = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
Add-Content -Path $hosts -Value '127.0.0.1 dsc.local'
Let's see the three operations in action (from an elevated shell, the hosts file is protected):
# Runs getScript only
dsc config get --file hosts.dsc.yaml
# Runs testScript only, nothing is changed
dsc config test --file hosts.dsc.yaml
# Runs testScript first, and setScript only when the test fails
dsc config set --file hosts.dsc.yaml
# Run it again: testScript now returns true and setScript is skipped
dsc config set --file hosts.dsc.yaml
The second set is the one that matters. Nothing happens, because the machine already looks the way we described. That idempotency is not something DSC gives you for free here. It is the result of a good testScript.
Remark: PowerShellScript runs in PowerShell 7. If your script depends on modules or features that only exist in Windows PowerShell, use Microsoft.DSC.Transitional/WindowsPowerShellScript. Same properties, but it runs in 5.1.
Using the output of setScript
Whatever your setScript returns ends up in the result of the resource. Use --output-format pretty-json and look at afterState.output:
$result = dsc config set --file hosts.dsc.yaml --output-format pretty-json | ConvertFrom-Json -Depth 20
$result.results.result.afterState.output
This is useful when a script creates something you need later, like the thumbprint of a certificate, and you want to hand it to the next step in your pipeline.
When not to use it
If all you need is to run something once during set, and you do not care about get or test, RunCommandOnSet is simpler.
If you find yourself copying the same three scripts into every configuration, that is the signal to build a real resource. Inline scripts are hard to test and hard to reuse. Use them as glue, not as a foundation!