From 013e808c31dc442c654e30c8536827d5f8daba48 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Sun, 10 May 2026 16:37:21 +0200 Subject: [PATCH 1/3] docs: Update PSScript examples --- .../examples/configure-with-script.md | 310 ++++++ .../examples/invoke-with-input-data.md | 469 +++++++++ .../examples/invoke-with-messaging.md | 518 ++++++++++ .../examples/invoke-with-output-data.md | 887 ++++++++++++++++++ .../powershell-script-with-input-output.md | 0 .../examples/psscript.config.dsc.yaml | 105 +++ .../run-a-simple-powershell-script.md | 0 .../Transitional/PowerShellScript/index.md | 366 +++++++- ...e-windows-service-state-with-powershell.md | 183 ++++ .../WindowsPowerShellScript/index.md | 2 +- 10 files changed, 2792 insertions(+), 48 deletions(-) create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/configure-with-script.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-output-data.md delete mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/powershell-script-with-input-output.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/psscript.config.dsc.yaml delete mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/run-a-simple-powershell-script.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/configure-with-script.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/configure-with-script.md new file mode 100644 index 000000000..a8b6e7085 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/configure-with-script.md @@ -0,0 +1,310 @@ +--- +description: > + Example showing how to use the PowerShellScript resource in a DSC configuration + document. +ms.date: 05/10/2026 +ms.topic: reference +title: Configure a system with the PowerShellScript resource +--- + + + +# Configure a system with the PowerShellScript resource + +This example shows how you can use the [`Microsoft.DSC.Transitional/PowerShellScript`][01] resource +in a configuration both to invoke non-idempotent scripts and to idempotently manage a message of +the day file that doesn't have a specific DSC resource. + +## Definition + +The configuration document for this example defines two instances of the resource: + +1. The first instance, `Report processor info`, returns the number of processor cores and the + processor architecture from both `getScript` and `setScript`. This instance is informational + only - it doesn't modify the system. +1. The second instance, `Message of the Day`, idempotently manages a message of the day file. It + uses `input` to define the contents of the file and pulls the value for the input from the + `parameters` definition. It defines all three script properties: `getScript` to return the + actual state, `testScript` to determine if the instance is in the desired state, and `setScript` + to enforce the desired state. + + The `getScript` and `setScript` definitions return the same structured output representing the + state of the MOTD file to make monitoring how the instance modifies the system easier. All three + script definitions use the `Write-Verbose` cmdlet to emit informational messages about what the + instance is doing. In particular the messages from `testScript` describe whether and how the + file isn't in the desired state to address the limited information the script can surface in its + output. + +:::code language="yaml" source="psscript.config.dsc.yaml"::: + +Copy the configuration document and save it as `psscript.config.dsc.yaml`. + +## Get the current state + +To retrieve the current state of the system, use the [dsc config get][02] command on the +configuration document. + +```powershell +dsc --trace-level info config get --file ./psscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT1.2985379S + metadata: + Microsoft.DSC: + duration: PT1.2985379S + name: Report processor info + type: Microsoft.DSC.Transitional/PowerShellScript + result: + actualState: + output: + - processorCount: 8 + processorArchitecture: X64 +- executionInformation: + duration: PT0.9556133S + metadata: + Microsoft.DSC: + duration: PT0.9556133S + name: Message of the Day + type: Microsoft.DSC.Transitional/PowerShellScript + result: + actualState: + output: + - filePath: Temp:/example.motd + exists: false +messages: [] +hadErrors: false +``` + +The command emitted messages to stderr and the result to stdout. The messages include informational +messages from `getScript` for the message of the day instance indicating that the script looked for +but did not find the MOTD file. + +The result includes structured output from both instances: + +- The processor report instance shows that the system has `8` cores and is an `X64` architecture. +- The message of the day instance shows that the expected MOTD file doesn't exist at + `Temp:/example.motd`. + +## Enforce the desired state + +To update the system to the desired state, use the [dsc config set][03] command on the +configuration document. + +```powershell +dsc --trace-level info config set --file ./psscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd', creating new file + INFO PID : MOTD file created at 'Temp:/example.motd', setting content + INFO diff: key 'motd' missing + INFO diff: key 'lastUpdated' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT2.1871641S + metadata: + Microsoft.DSC: + duration: PT2.1871641S + name: Report processor info + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] +- executionInformation: + duration: PT1.708226S + metadata: + Microsoft.DSC: + duration: PT1.708226S + name: Message of the Day + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - exists: false + filePath: Temp:/example.motd + afterState: + output: + - exists: true + motd: Hello, friend! + filePath: Temp:/example.motd + lastUpdated: 2026-06-02T18:05:16.8811712-05:00 + changedProperties: + - output +messages: [] +hadErrors: false +``` + +As before, the message of the day instance surfaces informational messages. The messages show that +the MOTD file wasn't found and then the `setScript` reports that it is creating the file and +setting the content. + +It's easier to review the result data for each instance separately: + +- ```yaml + name: Report processor info + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] + ``` + + The processor info report shows the same state for the system before and after the **Set** + operation. If the instance didn't define `setScript` then `afterState` would be an empty object + (`{}`) and the `changedProperties` field would report that `output` was modified. Providing + identical output for the `setScript` ensures that the result doesn't imply any system changes. + +- ```yaml + name: Message of the Day + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - exists: false + filePath: Temp:/example.motd + afterState: + output: + - exists: true + motd: Hello, friend! + filePath: Temp:/example.motd + lastUpdated: 2026-06-02T18:05:16.8811712-05:00 + changedProperties: + - output + ``` + + The result for the message of the day instance shows that `exists` changed from `false` to `true`. + The `afterState` also includes the `motd` property showing the newly-set MOTD and reports the + last updated time for the file. + +If you invoke the **Set** operation for the configuration again you should see that neither instance +modifies the system: + +```powershell +dsc --trace-level info config set --file ./psscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file found at 'Temp:/example.motd', retrieving content and last updated time + INFO PID : MOTD file found at 'Temp:/example.motd', checking content + INFO PID : MOTD content matches desired value, no update needed +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT3.8028321S + metadata: + Microsoft.DSC: + duration: PT3.8028321S + name: Report processor info + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] +- executionInformation: + duration: PT2.6216447S + metadata: + Microsoft.DSC: + duration: PT2.6216447S + name: Message of the Day + type: Microsoft.DSC.Transitional/PowerShellScript + result: + beforeState: + output: + - filePath: Temp:/example.motd + motd: Hello, friend! + exists: true + lastUpdated: 2026-06-03T08:46:38.0491245-05:00 + afterState: + output: + - motd: Hello, friend! + exists: true + lastUpdated: 2026-06-03T08:46:38.0491245-05:00 + filePath: Temp:/example.motd + changedProperties: [] +messages: [] +hadErrors: false +``` + +## Cleanup + +To return your system to its original state, invoke the following PowerShell command to remove the +MOTD file from the `Temp:/` folder: + +```powershell +Remove-Item -Path 'Temp:/example.motd' -Verbose +``` + + +[01]: ../index.md +[02]: ../../../../../../cli/config/get.md +[03]: ../../../../../../cli/config/set.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md new file mode 100644 index 000000000..3422c0fa3 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md @@ -0,0 +1,469 @@ +--- +description: > + Example showing how to pass input data to a PowerShellScript resource and access properties, + array elements, and nested values inside the script. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the PowerShellScript resource with input data +--- + + + +# Invoke the PowerShellScript resource with input data + +These examples show how you can pass input data to the +[`Microsoft.DSC.Transitional/PowerShellScript` resource][01] and how to bind that data to your +script with a [`param()` statement][02]. + +## Input data types + +The following examples show how data input is bound to the parameters for a defined scriptblock +when the parameter isn't defined with a specific type. + +The data that the resource passes to a script is first converted from the JSON input that DSC sends +with the [`ConvertFrom-Json` cmdlet][03]. + +### Passing string input data + +When you define `input` as a string value, the parameter for the script is a `[string]` object. + +```powershell +$instance = @' +input: hello world +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.String]' + boundDataValue: hello world +``` + +### Passing integer input data + +When you define `input` as an integer value, the parameter for the script is an `[Int64]` value. + +```powershell +$instance = @' +input: 10 +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.Int64]' + boundDataValue: 10 +``` + +### Passing boolean input data + +When you define `input` as a boolean value, the parameter for the script is a `[Boolean]` value. + +```powershell +$instance = @' +input: true +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.Boolean]' + boundDataValue: true +``` + +### Passing array input data + +When you define `input` as a string value, the parameter for the script is a `[Object[]]` array. +The items in the array are data types as emitted by the [`ConvertFrom-Json` cmdlet][03]. + +```powershell +$instance = @' +input: +- hello world +- 10 +- 1.23 +- true +- null +- nested: object +- - nested + - array +getScript: |- + param($inputData) + + $inputData | ForEach-Object -Begin { $i = 0 } -Process { + [ordered]@{ + boundDataItemIndex = $i + boundDataItemType = if ($null -eq $_) { + '$null' + } else { + "[$($_.GetType().FullName)]" + } + boundDataItemValue = $_ + } + $i++ + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataItemIndex: 0 + boundDataItemType: '[System.String]' + boundDataItemValue: hello world + - boundDataItemIndex: 1 + boundDataItemType: '[System.Int64]' + boundDataItemValue: 10 + - boundDataItemIndex: 2 + boundDataItemType: '[System.Double]' + boundDataItemValue: 1.23 + - boundDataItemIndex: 3 + boundDataItemType: '[System.Boolean]' + boundDataItemValue: true + - boundDataItemIndex: 4 + boundDataItemType: $null + boundDataItemValue: null + - boundDataItemIndex: 5 + boundDataItemType: '[System.Management.Automation.PSCustomObject]' + boundDataItemValue: + nested: object + - boundDataItemIndex: 6 + boundDataItemType: '[System.Object[]]' + boundDataItemValue: + - nested + - array +``` + +### Passing object input data + +When you define `input` as an object value, the parameter for the script is a `[pscustomobject]`. +The values for each property of the object are data types as emitted by the +[`ConvertFrom-Json` cmdlet][03]. + +```powershell +$instance = @' +input: + string: hello world + integer: 10 + number: 1.23 + boolean: true + "null": null + nestedObject: + foo: bar + nestedArray: + - nested + - array +getScript: |- + param($inputData) + + $inputData.psobject.Properties | ForEach-Object -Process { + [ordered]@{ + boundDataPropertyName = $_.Name + boundDataPropertyType = "[$($_.TypeNameOfValue)]" + boundDataPropertyValue = $_.Value + } + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataPropertyName: string + boundDataPropertyType: '[System.String]' + boundDataPropertyValue: hello world + - boundDataPropertyName: integer + boundDataPropertyType: '[System.Int64]' + boundDataPropertyValue: 10 + - boundDataPropertyName: number + boundDataPropertyType: '[System.Double]' + boundDataPropertyValue: 1.23 + - boundDataPropertyName: boolean + boundDataPropertyType: '[System.Boolean]' + boundDataPropertyValue: true + - boundDataPropertyName: 'null' + boundDataPropertyType: '[System.Object]' + boundDataPropertyValue: null + - boundDataPropertyName: nestedObject + boundDataPropertyType: '[System.Management.Automation.PSCustomObject]' + boundDataPropertyValue: + foo: bar + - boundDataPropertyName: nestedArray + boundDataPropertyType: '[System.Object[]]' + boundDataPropertyValue: + - nested + - array +``` + +## Casting input data + +When you define the parameters for a scriptblock, you can specify a type for the input data. The +script uses PowerShell's [parameter type conversion][04] to try to convert the input +data. If the type conversion is impossible for the input data, PowerShell raises an error and the +operation fails. + +The following example shows how you can convert the input data to a given type. In this case, it +converts every item in the input data into a `[datetime]` object. + +```powershell +$instance = @' +input: + - 2026-01-02 + - 01/20/2026 +getScript: |- + param([datetime[]]$inputData) + + $inputData | ForEach-Object { + [ordered]@{ + InputDate = $_ + NextDate = $_.AddDays(1) + } + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - InputDate: 2026-01-02T00:00:00 + NextDate: 2026-01-03T00:00:00 + - InputDate: 2026-01-20T00:00:00 + NextDate: 2026-01-21T00:00:00 +``` + +## Input related errors + +Passing input to a script has several requirements: + +1. The script property for the resource must use the `param()` statement to define exactly one + parameter. +1. The `input` property for the resource must be defined with a non-null value. +1. If the `param()` statement defines a type for the input data, the value for the `input` property + of the instance must be convertible to that type. + +The resource raises an error and prevents the script from executing when any of these requirements +aren't met by the resource instance definition. + +### Error: input provided but script has no parameters + +If you provide a value for `input` but the script does not define a `param()` statement, the +resource exits with code `2` and emits the following error message: + +```plaintext +Input was provided but script does not have a parameter to accept input. +``` + +```powershell +$instance = @' +getScript: | + "Script without parameters" +input: oops +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Input was provided but script does not have a parameter to accept input. + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines a parameter but no input provided + +If the script defines a `param()` statement but no `input` is specified for the instance, the +resource exits with code `2` and emits the following error message: + +```plaintext +Script has a parameter '' but no input was provided. +``` + +```powershell +$instance = @' +getScript: | + param($inputObj) + "This will not run" +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Script has a parameter 'inputObj' but no input was provided. + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines more than one parameter + +If the script defines a `param()` statement with two or more parameters, the resource exits with +code `1` and emits the following error message: + +```plaintext +Script must have exactly one parameter. +``` + +```powershell +$instance = @' +input: +- first +- second +getScript: |- + param($a, $b) + + [ordered]@{ + a = $a + b = $b + } +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID 23764: Script must have exactly one parameter. + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines a typed parameter but input is invalid + +If the script defines the `param()` statement with a parameter that has a defined type that the +input data can't convert into, the resource raises an error message about an argument +transformation failure. + +```powershell +$instance = @' +input: foo +getScript: |- + param([int]$inputData) + + $inputData +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "Cannot process argument transformation on parameter 'inputData'. Cannot convert value "foo" to type "System.Int32". Error: "The input string 'foo' was not in a correct format."" + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +## Using input in a configuration document + +You can pass input to a `PowerShellScript` instance inside a DSC configuration document, including +values from configuration parameters. The following configuration uses the [dsc config get][05] +command to pass a port number into the script: + +```yaml +# check-port.dsc.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + port: + type: int + defaultValue: 8080 +resources: + - name: checkPort + type: Microsoft.DSC.Transitional/PowerShellScript + properties: + getScript: | + param($inputObj) + Write-Information "Checking port $($inputObj.port)..." + Test-NetConnection -ComputerName localhost -Port $inputObj.port | + Select-Object -ExpandProperty TcpTestSucceeded + input: + port: "[parameters('port')]" +``` + +```powershell +dsc config get --file check-port.dsc.yaml +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + # Elided for brevity +results: +- executionInformation: + duration: PT12.3218732S + metadata: + Microsoft.DSC: + duration: PT12.3218732S + name: checkPort + type: Microsoft.DSC.Transitional/PowerShellScript + result: + actualState: + output: + - false +messages: [] +hadErrors: false +``` + + +[01]: ../index.md +[02]: https://learn.microsoft.com\powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#parameter-declaration +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/convertfrom-json +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#type-conversion-of-parameter-values +[05]: ../../../../../../cli/config/get.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md new file mode 100644 index 000000000..ed66a48c2 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md @@ -0,0 +1,518 @@ +--- +description: > + Example showing how to emit trace messages from a PowerShellScript resource. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the PowerShellScript resource with trace messaging +--- + + + +# Invoke the PowerShellScript resource with trace messaging + +These examples show how you can emit messages from the +[`Microsoft.DSC.Transitional/PowerShellScript` resource][01]. + +## Emitting errors + +By default, any errors raised during script execution cause the execution to emit the error message +and immediately halt script execution. The following example snippets show how you can provide +error details for the user when a script fails. + +### Emitting an error from a failed cmdlet + +In this example, the script depends on the `tstoy` command being available on the system. When the +command isn't available, the script fails and reports the error. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy -CommandType Application | + Select-Object -ExpandProperty Path + + & $tstoyCmd version --full --format json | ConvertFrom-Json +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The term 'tstoy' is not recognized as a name of a cmdlet, function, script file, or executable program. +Check the spelling of the name, or if a path was included, verify that the path is correct and try again." + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +The first error in the output indicates that the script execution was stopped by the error from the +`Get-Command` invocation, which showed that `tstoy` wasn't available on the system. + +### Emitting errors with `Write-Error` + +Instead of raising the default error from a failed command, you can use the [`Write-Error`][02] +cmdlet to emit a specific error message. In this example, the script depends on the `tstoy` command +being available on the system. When the command isn't available, the script fails and reports the +error. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy* -CommandType Application | + Where-Object {$_.Name -match 'tstoy(\.exe)?' } | + Select-Object -ExpandProperty Path + if ([string]::IsNullOrEmpty($tstoyCmd)) { + Write-Error "command 'tstoy' not found; unable to report version for 'tstoy'" + } + + & $tstoyCmd version --full --format json | ConvertFrom-Json +'@ + + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: command 'tstoy' not found; unable to report version for 'tstoy'" + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +The first error in the output indicates that the script execution was stopped and includes the +message emitted from the `Write-Error` command. + +### Throwing an error from a `catch` block + +In the previous error examples, the emitted error includes information about execution stopping +because of the error action preference being set to stop. You can make the error message clearer +by rethrowing the underlying exception from a the `catch` block in a [`try`/`catch` statement][03]. + +```powershell +$instance = @' +getScript: |- + try { + $tstoyCmd = Get-Command -Name tstoy -CommandType Application | + Select-Object -ExpandProperty Path + + & $tstoyCmd version --full --format json | ConvertFrom-Json + } catch { + throw $_.Exception + } +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The term 'tstoy' is not recognized as a name of a cmdlet, function, script file, or executable program. +Check the spelling of the name, or if a path was included, verify that the path is correct and try again." + ERROR Failed to run process 'pwsh': Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'pwsh' [exit code 1] manifest description: PowerShell script execution failed +``` + +## Emitting warning messages + +You can emit warning messages from a script with the [`Write-Warning`][04] cmdlet. + +This example shows how you can emit a warning from a script without halting execution. The script +looks for the `tstoy` command and returns the version information for that command if it exists. If +the command isn't available, the script raises a warning and returns no output data. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy* -CommandType Application | + Where-Object {$_.Name -match 'tstoy(\.exe)?' } | + Select-Object -ExpandProperty Path + + if ([string]::IsNullOrEmpty($tstoyCmd)) { + Write-Warning "command 'tstoy' not found; unable to report version for 'tstoy'" + } else { + & $tstoyCmd version --full --format json | ConvertFrom-Json + } +'@ + + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```Output + WARN PID : command 'tstoy' not found; unable to report version for 'tstoy' +actualState: + output: [] +``` + +## Emitting info messages + +You can emit `info` level messages for DSC with the [`Write-Verbose`][05] and [`Write-Host`][06] +cmdlets. When a cmdlet used in your script emits verbose or information messages, you can use the +[`-Verbose` common parameter][07] or specify the [`-InformationAction` common parameter][08] as +`Continue` to have those messages emitted for DSC. + +### Emitting verbose messages from cmdlets + +The following snippet creates a temporary file. It uses commands that emit verbose messages, like +`New-Item`. The example shows how you can specify the `-Verbose` parameter on cmdlets to surface +their verbose messaging in DSC as `info` level trace messages. + +```powershell +$instance = [ordered]@{ + input = 'create' + getScript = { + param( + [ValidateSet('create', 'delete')] + [string] $fileOperation + ) + + $tempFolder = "Temp:/dsc/examples/PowerShellScript/messaging" + $tempFile = Join-Path $tempFolder 'info.txt' + + if (Test-Path $tempFile) { + $fileInfo = Get-Item -Path $tempFile + + [ordered]@{ + path = $fileInfo.FullName + exists = $true + creationTimeUtc = $fileInfo.CreationTimeUtc + lastWriteTimeUtc = $fileInfo.LastWriteTimeUtc + attributes = $fileInfo.Attributes.ToString() + } + } else { + [ordered]@{ + path = $fileInfo.FullName + exists = $false + } + } + }.ToString() + setScript = { + param( + [ValidateSet('create', 'delete')] + [string] $fileOperation + ) + + $tempFolder = "Temp:\dsc\examples\PowerShellScript\messaging" + $tempFile = Join-Path $tempFolder 'info.txt' + + switch ($fileOperation) { + 'create' { + if (-not (Test-Path $tempFolder)) { + $null = New-Item -Path $tempFolder -ItemType Directory -Force -Verbose + } + if (-not (Test-Path $tempFile)) { + $null = New-Item -Path $tempFile -ItemType File -Verbose + } + + $fileInfo = Get-Item -Path $tempFile + + [ordered]@{ + path = $fileInfo.FullName + exists = $true + creationTimeUtc = $fileInfo.CreationTimeUtc + lastWriteTimeUtc = $fileInfo.LastWriteTimeUtc + attributes = $fileInfo.Attributes + } + } + 'delete' { + if (Test-Path $tempFile) { + Remove-Item -Path $tempFile -Force -Verbose + } + + [ordered]@{ + path = $fileInfo.FullName + exists = $false + } + } + } + }.ToString() +} + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + $instance | ConvertTo-Json -Compress +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Performing the operation "Create Directory" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\PowerShellScript". + INFO PID : Performing the operation "Create Directory" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\PowerShellScript\messaging". + INFO PID : Performing the operation "Create File" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\PowerShellScript\messaging\info.txt". + INFO diff: key 'creationTimeUtc' missing + INFO diff: key 'lastWriteTimeUtc' missing + INFO diff: key 'attributes' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - path: null + exists: false +afterState: + output: + - path: C:\Users\\AppData\Local\Temp\dsc\examples\PowerShellScript\messaging\info.txt + exists: true + creationTimeUtc: 2026-05-21T17:58:31.2115007Z + lastWriteTimeUtc: 2026-05-21T17:58:31.2115007Z + attributes: 32 +changedProperties: +- output +``` + +The info messages emitted by DSC include the verbose messages from creating the temporary directory +and file. + +Invoke the resource again but with the `input` set to `delete` to remove the temporary file: + +```powershell +$instance.input = 'delete' + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + $instance | ConvertTo-Json -Compress +) +``` + +### Emitting verbose messages with `Write-Verbose` + +You can surface custom `info` level messages from scripts with the [`Write-Verbose`][05] cmdlet. + +The following snippet shows how messages from `Write-Verbose` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Verbose "Setting things up" + Write-Verbose "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource get @arguments +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Setting things up + INFO PID : Retrieving data +actualState: + output: [] +``` + +### Emitting verbose messages with `Write-Host` + +You can surface custom `info` level messages from scripts with the [`Write-Host`][06] cmdlet. + +The following snippet shows how messages from `Write-Host` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Host "Setting things up" + Write-Host "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource get @arguments +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Setting things up + INFO PID : Retrieving data +actualState: + output: [] +``` + +## Emitting debug messages + +You can emit `debug` level messages for DSC with the [`Write-Debug`][09] cmdlet. When a cmdlet used +in your script emits debug messages, you can use the [`-Debug` common parameter][07] to have those +messages emitted for DSC. + +### Emitting debug messages from cmdlets + +The following snippet shows how debug messages from commands are captured by the resource. It +defines a function that emits debug messages and then invokes that function. + +```powershell +$instance = @' +getScript: |- + function Get-Data { + [CmdletBinding()] + param() + + Write-Debug "Starting process..." + Write-Debug "Doing things..." + Write-Debug "Done." + } + + Get-Data +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Starting process... + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Doing things... + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Done. + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'pwsh' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' +actualState: + output: [] +``` + +The output shows `debug` level messages emitted by the invoked function in the script. + +### Emitting debug messages with `Write-Debug` + +You can surface custom `debug` level messages from scripts with the [`Write-Debug`][05] cmdlet. + +The following snippet shows how messages from `Write-Debug` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Debug "Setting things up" + Write-Debug "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Setting things up + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Retrieving data + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'pwsh' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' +actualState: + output: [] +``` + +## Emitting trace messages + +You can emit `trace` level messages for DSC with the [`Write-Information`][10] cmdlet. When a +cmdlet used in your script emits debug messages, you can specify the +[`-InformationAction` common parameter][11] as `Continue` to have those messages emitted for DSC. + +### Emitting trace messages from cmdlets + +The following snippet shows how information messages from commands are captured by the resource as +trace messages. It defines a function that emits information messages and then invokes that +function with `-InformationAction` as `Continue`. + +```powershell +$instance = @' +getScript: |- + function Get-Data { + [CmdletBinding()] + param() + + Write-Information "Starting process..." + Write-Information "Doing things..." + Write-Information "Done." + } + + Get-Data -InformationAction Continue +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level trace resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + TRACE dsc_lib::dscresources::command_resource: 898: Invoking command 'pwsh' with args Some(["-NoLogo", "-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "$input | ./psscript.ps1", "get"]) + TRACE dsc_lib::dscresources::command_resource: 900: Current working directory: C:\code\dsc\dsc-pr-review\bin\debug + TRACE dsc_lib::dscresources::command_resource: 806: Writing to command STDIN: {"getScript":"function Get-Data {\n [CmdletBinding()]\n param()\n\n Write-Information \"Starting process...\"\n Write-Information \"Doing things...\"\n Write-Information \"Done.\"\n}\n\nGet-Data -InformationAction Continue"} + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Starting process... + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Doing things... + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Done. + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'pwsh' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + TRACE dsc_lib::dscresources::command_resource: 1083: Verify JSON for 'Microsoft.DSC.Transitional/PowerShellScript': {"output":[]} + +actualState: + output: [] +``` + +The output shows `trace` level messages emitted by the invoked function in the script. + +### Emitting trace messages with `Write-Information` + +You can surface custom `trace` level messages from scripts with the [`Write-Information`][10] cmdlet. + +The following snippet shows how messages from `Write-Information` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Information "Setting things up" + Write-Information "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level trace resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + TRACE dsc_lib::dscresources::command_resource: 898: Invoking command 'pwsh' with args Some(["-NoLogo", "-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "$input | ./psscript.ps1", "get"]) + TRACE dsc_lib::dscresources::command_resource: 900: Current working directory: C:\code\dsc\dsc-pr-review\bin\debug + TRACE dsc_lib::dscresources::command_resource: 806: Writing to command STDIN: {"getScript":"Write-Information \"Setting things up\"\nWrite-Information \"Retrieving data\""} + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Setting things up + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Retrieving data + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'pwsh' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + TRACE dsc_lib::dscresources::command_resource: 1083: Verify JSON for 'Microsoft.DSC.Transitional/PowerShellScript': {"output":[]} + +actualState: + output: [] +``` + + +[01]: ../index.md +[02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-error +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-warning +[05]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-verbose +[06]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-host +[07]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-verbose +[08]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-informationaction +[09]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-debug +[10]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-information +[11]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-informationaction diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-output-data.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-output-data.md new file mode 100644 index 000000000..cea9fbd53 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-output-data.md @@ -0,0 +1,887 @@ +--- +description: > + Example showing how to return output data from a PowerShellScript resource. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the PowerShellScript resource with output data +--- + + + +# Invoke the PowerShellScript resource with output data + +These examples show how you can return output from the +[`Microsoft.DSC.Transitional/PowerShellScript` resource][01]. + +## Output data types + +All output that a script emits for this resource is inserted into the `output` array for the +resource instance. The resource uses the `ConvertTo-Json` cmdlet for every item emitted to the +[Success stream][02]. The converted representation is what the resource inserts into the `output` +array. + +When the resource serializes the output data as JSON it retains up to `9` levels of depth. This can +make the output for typical PowerShell objects a script may return very large and difficult to +parse in the result for an operation. + +### Outputting scalar values + +The following snippet shows how scalar values (not objects or arrays) are handled by the resource +when emitted by a script. Scalar values include strings, integers, floats, booleans, and `$null`. + +```powershell +$instance = @' +getScript: |- + $true # boolean scalar value + 1 # integer scalar value + 1.2 # float scalar value + $null # null scalar value + 'apple' # string scalar value +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - true + - 1 + - 1.2 + - null + - apple +``` + +### Outputting objects + +When a script emits objects that aren't scalar values, the conversion to JSON representation +includes up to `9` levels of depth. Objects often have properties that are _also_ objects with +sub-properties or arrays of nested objects. + +When the object output is particularly large and complex it can cause the resource operation to +fail when DSC needs to validate the output data. The following snippet shows how emitting a +`[FileInfo]` object directly can cause the resource to fail. + +The script creates a new temporary file, which emits the `[FileInfo]` object for the new file as +output. + +```powershell +$instance = @' +getScript: |- + $filePath = 'Temp:/dsc/examples/PowerShellScript/output.txt' + + New-Item -Path $filePath -Force +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'pwsh' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + ERROR dsc::resource_command: 67: JSON: expected value at line 1 column 1 +``` + +We can demonstrate the failure independently of DSC. When you invoke the following snippet, +PowerShell hangs. A `[FileInfo]` object can be extremely large as the object contains references to +its parent folder, which references that object's parent folder, and so on. + +```powershell +$fileInfo = Get-Item -Path 'Temp:/dsc/examples/PowerShellScript/output.txt' +$fileJson = ConvertTo-Json -Depth 9 -InputObject $fileInfo +# The following commands never run because the session hangs +$outputSize = [System.Text.Encoding]::UTF8.GetByteCount($fileJson) / 1MB +"The output JSON is {0} MB" -f [Math]::Round($outputSize, 2) +``` + + +You can cancel the command by pressing Ctrl+C in your console. + +If you update the depth to `5` and invoke the command again, you can see that the size of the JSON +object is _substantial_. + +```powershell +$fileInfo = Get-Item -Path 'Temp:/dsc/examples/PowerShellScript/output.txt' +$fileJson = ConvertTo-Json -Depth 5 -InputObject $fileInfo +# The following commands never run because the session hangs +$outputSize = [System.Text.Encoding]::UTF8.GetByteCount($fileJson) / 1MB +"The output JSON is {0} MB" -f [Math]::Round($outputSize, 2) +``` + +```Output +WARNING: Resulting JSON is truncated as serialization has exceeded the set depth of 5. +The output JSON is 58.37 MB +``` + +Instead of emitting complex objects directly, consider constructing your output objects +intentionally. For a comprehensive example of emitting structured output, see the +["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +section of this article. + +### Outputting arrays + +By default, when a script emits an array as output, each item in the array is captured as a +separate item in the `output` property for the resource. + +The following snippet shows the default behavior. + +```powershell +$instance = @' +getScript: |- + @('a', 'b', 'c') + @(1, 2, 3) +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - a + - b + - c + - 1 + - 2 + - 3 +``` + +In the previous snippet, the script emitted two arrays: + +1. An array containing three strings +1. An array containing three integers + +The `output` for the resource included six separate items representing each of the items in the +emitted arrays in the order that the script emitted them. + +The following snippet shows how you can use the [`Write-Object` cmdlet][03] with the +[`-NoEnumerate`][04] parameter to emit arrays from the script and keep them as arrays. + +```powershell +$instance = @' +getScript: |- + Write-Output -NoEnumerate @('a', 'b', 'c') + Write-Output -NoEnumerate @(1, 2, 3) +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - - a + - b + - c + - - 1 + - 2 + - 3 +``` + +Now the output from the script shows two items in the `output` array. Each item is an array +containing three items. + +## Discarding unwanted output + +Every item emitted to the success stream is included in the `output` for the resource. To avoid +including unwanted data in the output you need to discard that data. To discard data from a +statement that would otherwise emit unwanted output, you can: + +- Assign the statement to `$null`. +- Redirect the statement to `$null`. +- Cast the statement to `[void]`. +- Pipe the statement to `Out-Null`. + +The first three options have nearly identical performance. Piping to `Out-Null` can be much slower +when looping over a large set of data. + +The following snippet shows examples for discarding unwanted output in a script. + +```powershell +$instance = @' +getScript: |- + $filePath = 'Temp:/dsc/examples/PowerShellScript/output.txt' + # Assign to `$null` + $null = New-Item -Path $filePath -Force + # Redirect to `$null` + New-Item -Path $filePath -Force > $null + # Cast to `[void]` + [void](New-Item -Path $filePath -Force) + # Pipe to `Out-Null` + New-Item -Path $filePath -Force | Out-Null + + 'this is the only output' +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - this is the only output +``` + +## Output for `getScript` + +For `getScript` you can emit any data to the PowerShell success stream that you want to surface to +the user. The emitted data is returned in the `actualState.output` field for the **Get** operation +result and the `beforeState.output` field for the **Set** operation result. + +If you're defining the resource instance to idempotently manage the state of one or more system +components, ensure that the output you emit from `getScript` uses the same structure as the output +from `setScript` to make the results readable for the user. + +Otherwise, return any data that you want to surface to the user. If you want to give the user more +information, you can [emit messages][05]. For comprehensive examples of emitting messages from your +script see [Invoke the PowerShellScript resource with trace messaging][06]. + +The following example shows how you can emit items from `getScript` to inform the user. For a +comprehensive example of structured output for an instance that idempotently manages system state, +see ["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +in this article. + +```powershell +$instance = @' +getScript: |- + "Current context is interactive: {0}" -f [Environment]::UserInteractive + "Current context is privileged: {0}" -f [Environment]::IsPrivilegedProcess +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```yaml +actualState: + output: + - 'Current context is interactive: True' + - 'Current context is privileged: False' +``` + +## Output for `testScript` + +The `testScript` definition _must_ return a single boolean value - `$true` to indicate that the +system is in the desired state or `$false` otherwise. + +Any of the following will cause the resource to raise an error when invoking the `testScript`: + +- Not emitting any output at all to the success stream. +- Emitting any non-boolean data to the success stream. +- Emitting more than one boolean value to the success stream. + +You can [emit messages][05] To indicate to the user how and why the +system isn't in the desired state. For detailed examples of emitting messages from your script +see [Invoke the PowerShellScript resource with trace messaging][06]. + +The following example shows how you can define `testScript` to check whether a file exists and +isn't empty. It emits info messages to clarify whether and how the instance is in the desired +state. + +```powershell +$instance = @' +testScript: |- + $filePath = 'Temp:/dsc/examples/PowerShellScript/output.txt' + + if (-not (Test-Path $filePath)) { + Write-Verbose "The file '$filePath' doesn't exist" + return $false + } + + if ([string]::IsNullOrEmpty((Get-Content -Raw -Path $filePath))) { + Write-Verbose "The file '$filePath' is empty" + return $false + } + + Write-Verbose "The file '$filePath' exists and contains content" + $true +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/PowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource test @arguments +``` + +```console + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking test on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : The file 'Temp:/dsc/examples/PowerShellScript/output.txt' is empty + INFO diff: key 'testScript' missing +desiredState: + testScript: |- + $filePath = 'Temp:/dsc/examples/PowerShellScript/output.txt' + + if (-not (Test-Path $filePath)) { + Write-Verbose "The file '$filePath' doesn't exist" + return $false + } + + if ([string]::IsNullOrEmpty((Get-Content -Raw -Path $filePath))) { + Write-Verbose "The file '$filePath' is empty" + return $false + } + + Write-Verbose "The file '$filePath' exists and contains content" + $true +actualState: + _inDesiredState: false +inDesiredState: false +differingProperties: +- testScript +``` + +For a more comprehensive example that idempotently manages system state see the +["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +section of this article. + +## Output for `setScript` + +For `setScript` you can emit any data to the PowerShell success stream that you want to surface to +the user. The emitted data is returned in the `afterState.output` field for the **Set** operation +result. + +If you're defining the resource instance to idempotently manage the state of one or more system +components, ensure that the output you emit from `setScript` uses the same structure as the output +from `getScript` to make the results readable for the user. + +Otherwise, return any data that you want to surface to the user. If you want to give the user more +information, you can [emit messages][05]. For comprehensive examples of +emitting messages from your script see +[Invoke the PowerShellScript resource with trace messaging][06]. + +The following example shows how you can emit items from `setScript` to inform the user about how +the script is modifying the system. + +```powershell +$instance = @' +setScript: |- + $filePath = 'Temp:/dsc/examples/PowerShellScript/output.txt' + $content = 'Hello world' + if (-not (Test-Path $filePath)) { + "File '$filePath' doesn't exist - creating it" + $null = New-Item -Path $filePath -Force + } + + $currentContent = Get-Content -Raw -Path $filePath + if ([string]::IsNullOrEmpty($currentContent)) { + "File '$filePath' is empty - adding content" + $content | Set-Content -Path $filePath -NoNewline + } elseif ($currentContent -ne $content) { + "File '$filePath' contains invalid content - overriding content" + $content | Set-Content -Path $filePath -NoNewline + } else { + "File '$filePath' contains desired content'" + } + + [ordered]@{ + initialContent = $currentContent + finalContent = $content + } +'@ + +dsc resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input $instance +``` + +```yaml +beforeState: {} +afterState: + output: + - File 'Temp:/dsc/examples/PowerShellScript/output.txt' is empty - adding conent + - initialContent: null + finalContent: Hello world +changedProperties: +- output +``` + +In this example, `beforeState` is an empty object because the instance doesn't define `getScript`. +The output from `setScript` includes two items. The first is a message indicating that the file +exists but is empty. The second item is an object showing both the initial content and final +content of the file. + +For a comprehensive example of structured output for an instance that idempotently manages system +state, including defining `getScript` to populate the `beforeState` in the **Set** result, see the +[Structure output for an idempotent instance](#structure-output-for-an-idempotent-instance) section +of this article. + +## Structure output for an idempotent instance + +To return output that is readable for the user, consider returning only objects. Use property names +to orient the user when reviewing the output. Limit the depth of the object to no more than three +levels when possible. + +The following example shows how you can return information about a JSON configuration file that +isn't managed by a specific DSC resource. It follows best practices by: + +1. Implementing scripts for all three operations. +1. Returning a single structured object from `getScript`. +1. Returning a boolean for `testScript` and emitting trace messages to indicate _how_ the instance + is out of the desired state. +1. Returning the same structured object from `setScript` as `getScript`. +1. Emitting trace messages to indicate which settings the `setScript` is modifying. + +> [!NOTE] +> This example uses an ordered dictionary to represent the instance because the script properties +> are much longer and more detailed than earlier examples in this article. Defining the scripts +> this way makes it easier to review the script code than defining it all together in a YAML +> snippet. +> +> The `getScript` and `setScript` snippets define the output object as an ordered dictionary with +> the `[ordered]` type accelerator. This ensures that the emitted object always keeps the key-value +> pairs in the defined order. Defining the output object as a normal hashtable causes the ordering +> of the output object properties to be nondeterministic, which can make comparing results more +> difficult. +> +> You could also define the output object as a `[pscustomobject]` and use the `Add-Member` function +> to add more properties to the initial object. + +First, define an ordered dictionary to represent the instance. Define the `input` for the scripts +the instance will use. In this example, the input data includes both the path to the file and the +settings to manage in that file. + +```powershell +$instance = [ordered]@{ + input = [ordered]@{ + filePath = 'Temp:/dsc/examples/PowerShellScript/output.json' + settings = [ordered]@{ + updateAutomatically = $true + updateFrequency = 30 + } + } +} +``` + +Next, define `getScript` to retrieve the actual state of the configuration file. The script must +define a `param()` statement to accept the input data. + +The script returns an object that always includes the `filePath` and `exists` properties. +`filePath` is identical to `input.filePath` for the instance. `exists` indicates whether the file +actually exists on the system. + +If the file doesn't exist, that's all the information the instance can provide. The script returns +that data and stops processing. + +If the file does exist, the output object also includes the `settings` and `lastWriteTime` +properties. `settings` is the contents of the file converted from JSON. `lastWriteTime` is the +actual last write time for the file itself. + +```powershell +$instance.getScript = { + param($inputData) + + $result = [ordered]@{ + filePath = $inputData.filePath + exists = Test-Path -Path $inputData.filePath + } + + if (-not $result.exists) { + Write-Verbose "Config file doesn't exist" + return $result + } + Write-Verbose "Retrieving settings and last write time from config file" + $fileInfo = Get-Item -Path $inputData.filePath + $settings = Get-Content -Raw -Path $inputData.filePath | ConvertFrom-Json + + $result.settings = $settings + $result.lastWriteTime = $fileInfo.LastWriteTime + + $result +}.ToString() +``` + +The next snippet defines `testScript` for the instance. As with `getScript`, the script must define +a single parameter. Unlike `getScript`, this script must return exactly one boolean value. + +The test script: + +1. Checks whether the configuration file (`input.filePath`) exists. If it doesn't, the script emits + an info message and returns `$false`. +1. Checks whether the configuration file is empty. If it is, the script emits an info message and + returns `$false`. +1. Iterates over the key-value pairs for the desired settings (`input.settings`) to check whether + each of them is in the desired state. If the desired setting isn't defined or is defined with + an incorrect value the script emits an info message and marks the resource as noncompliant but + _doesn't_ stop processing. + + This ensures that the instance can fully report on the desired settings instead of only reporting + the first missing or incorrect setting. +1. Returns `$false` if any setting wasn't in the desired state and otherwise `$true`. + +```powershell +$instance.testScript = { + param($inputData) + + if (-not (Test-Path -Path $inputData.filePath)) { + Write-Verbose "Config file doesn't exist" + return $false + } + + $content = Get-Content -Raw -Path $inputData.filePath + if ([string]::IsNullOrEmpty($content)) { + Write-Verbose "Config file is empty" + return $false + } + + # Initialize variable for result. If any check fails, set to `$false` + # From this point on we want to fully validate state for info messages to + # the user instead of returning early. + $inDesiredState = $true + + # Loop over the desired state to compare to actual settings + $desiredSettings = $inputData.settings.psobject.Properties + $actualSettings = ($content | ConvertFrom-Json).psobject.Properties + foreach ($setting in $desiredSettings) { + $name = $setting.Name + $desiredValue = $setting.Value + $actualSetting = $actualSettings | Where-Object Name -EQ $name + + if ($null -eq $actualSetting) { + Write-Verbose "Missing setting '$name'" + $inDesiredState = $false + continue + } + + if ($actualSetting.Value -ne $setting.Value) { + $message = "Expected setting '{0}' to be ``{1}`` but it is ``{2}``" -f @( + $name + $desiredValue + $actualSetting.Value + ) + Write-Verbose $message + $inDesiredState = $false + } + } + + $inDesiredState +}.ToString() +``` + +To enforce the desired state, define the `setScript` for the instance. The script must define a +single parameter. To make the result for the **Set** operation readable the script emits the same +data structure as `getScript`. + +The script is defined to be idempotent, only modifying the system if needed. It follows these steps: + +1. Define the result object with `filePath` as the `input.filePath` value and `exists` as `true`. +1. Check whether the configuration file exists. If it doesn't, emit a message to indicate that the + instance is creating the file. Then create the file and write the desired state settings + (`input.settings`) into it. Populate the `settings` and `lastWriteTime` fields for the result + object and then use the `return` keyword to emit the result and stop processing the script. +1. If the configuration file does exist retrieve the settings from it. Iterate over the desired + state settings (`input.settings`). If the setting is missing or defined incorrectly, emit an + info message and mark the instance as requiring an update with the `$shouldUpdate` variable. + This ensures that the instance only modifies the file when the settings aren't in the desired + state. + + > [!NOTE] + > This is necessary for version `0.1.0` of this resource. In this release the resource doesn't + > use the `testScript` to determine whether to actually invoke the `setScript`. The resource + > _always_ invokes `setScript` when you invoke the **Set** operation for the resource or on a + > configuration document containing an instance of the resource. + + If the setting is missing, add the desired state setting to the object representing the actual + state. If the setting has the incorrect value, set that property on the same object to the + desired state. This ensures that the resource doesn't inadvertently modify or remove any + settings in the configuration file that the instance isn't managing (the setting is defined in + the file but not `input.settings`). +1. If any of the desired state settings weren't defined in the configuration file or were defined + with invalid values emit a message and update the file with the combined settings. Otherwise + emit a message indicating that the configuration file didn't require any modification. +1. Update the result object to include the final settings and the last write time for the file and + emit the result. + +`setScript` returns the same structured output data as `getScript` regardless of whether the script +creates, updates, or doesn't modify the configuration file. This helps make the output for the +**Set** operation readable and enable directly comparing the `beforeState` and `afterState` fields +in the result. + +```powershell +$instance.setScript = { + param($inputData) + + $filePath = $inputData.filePath + $settings = $inputData.settings + $result = [ordered]@{ + filePath = $filePath + exists = $true + } + + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "Creating config file with specified settings" + $null = New-Item -Path $filePath -Force -Verbose + $json = $settings | ConvertTo-Json + $json | Out-File -FilePath $filePath -Encoding utf8NoBOM + + $result.settings = $settings + $result.lastWriteTime = Get-Item -Path $filePath | + Select-Object -ExpandProperty LastWriteTime + + return $result + } + + $content = Get-Content -Raw -Path $filePath + $actualSettings = $content | ConvertFrom-Json + $shouldUpdate = $false + # Iterate over defined settings, updating the actual settings as needed. + # Don't remove any non-managed settings, only enforce specified settings. + # Set shouldUpdate to $true if any changes are needed, but wait to write + # to the file until all changes are processed to avoid multiple writes. + foreach ($setting in $settings.psobject.Properties) { + $name = $setting.Name + $value = $setting.Value + Write-Verbose "Processing setting '$name' with desired value ``$value``" + $actual = $actualSettings.psobject.Properties | + Where-Object Name -EQ $name | + Select-Object -First 1 + + if ($null -eq $actual) { + Write-Verbose "Adding setting '$name' as ``$value``" + + $shouldUpdate = $true + $memberParams = @{ + InputObject = $actualSettings + MemberType = 'NoteProperty' + Name = $name + Value = $value + } + Add-Member @memberParams + } elseif ($value -eq $actual.Value) { + Write-Verbose "Setting '$name' is already set to ``$value``" + } else { + $message = "Changing setting '{0}' from ``{1}`` to ``{2}``" -f @( + $name + $actual.Value + $value + ) + Write-Verbose $message + + $shouldUpdate = $true + $actualSettings.$name = $value + } + } + + if ($shouldUpdate) { + Write-Verbose "Updating config file with new settings" + $json = $actualSettings | ConvertTo-Json + $json | Out-File -FilePath $filePath -Encoding utf8NoBOM + } else { + Write-Verbose "Config file is already in the desired state. No update needed." + } + + $result.settings = $actualSettings + $result.lastWriteTime = Get-Item -Path $filePath | + Select-Object -ExpandProperty LastWriteTime + + $result +}.ToString() +``` + +With the instance fully defined, invoke the **Get** operation to ensure that returning the actual +state works as expected: + +```powershell +dsc --trace-level info resource get --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Config file doesn't exist +actualState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: false +``` + +The output shows that the configuration file doesn't exist. + +Next, invoke the **Set** operation to create the file: + +```powershell +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Config file doesn't exist + INFO PID : Creating config file with specified settings + INFO PID : Performing the operation "Create File" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\PowerShellScript\output.json". + INFO diff: key 'updateAutomatically' is not an object + INFO diff: key 'updateFrequency' is not an object + INFO diff: key '_exist' is not an object + INFO diff: key 'lastWriteTime' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: false +afterState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +changedProperties: +- output +``` + +The emitted messages show that the configuration file doesn't exist and the resource is creating +it. The `beforeState` is populated by the `getScript` and shows that the file doesn't exist. The +`afterState` then shows that the instance created the file with the expected settings and includes +the last write time. + +Invoking the **Set** operation again shows that the defined instance is idempotent: + +```powershell +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Retrieving settings and last write time from config file + INFO PID : Processing setting 'updateAutomatically' with desired value `True` + INFO PID : Setting 'updateAutomatically' is already set to `True` + INFO PID : Processing setting 'updateFrequency' with desired value `30` + INFO PID : Setting 'updateFrequency' is already set to `30` + INFO PID : Config file is already in the desired state. No update needed. +beforeState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +afterState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +changedProperties: [] +``` + +The output in `beforeState` and `afterState` is identical and `changedProperties` is an empty +array. The emitted messages clarify that the instance checked each setting in the configuration +file and found them compliant to the desired state. + +Finally, update `input.settings` by: + +- Removing `updateAutomatically` +- Updating `updateFrequency` to `45` +- Adding `logLevel` as `info` + +Then invoke the resource again to see how the instance updates the configuration file. + +```powershell +$instance.input.settings.Remove('updateAutomatically') +$instance.input.settings.updateFrequency = 45 +$instance.input.settings.logLevel = 'info' + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/PowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/PowerShellScript' using 'pwsh' + INFO PID : Retrieving settings and last write time from config file + INFO PID : Processing setting 'updateFrequency' with desired value `45` + INFO PID : Changing setting 'updateFrequency' from `30` to `45` + INFO PID : Processing setting 'logLevel' with desired value `info` + INFO PID : Adding setting 'logLevel' as `info` + INFO PID : Updating config file with new settings + INFO diff: key 'logLevel' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +afterState: + output: + - filePath: Temp:/dsc/examples/PowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 45 + logLevel: info + lastWriteTime: 2026-06-02T16:53:39.2138244-05:00 +changedProperties: +- output +``` + +The emitted messages indicate that the instance only checked the `updateFrequency` and `logLevel` +settings - it didn't enforce `updateAutomatically`. The messages show that the instance updated +`updateFrequency` from `30` to `45` and added the missing `logLevel` setting. + +The result object again shows how `beforeState` differs from `afterState`, confirming that the +instance did modify system state. + + +[01]: ../index.md +[02]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.6#success-stream +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-output +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-output#-noenumerate +[05]: ../index.md#emitting-messages +[06]: ./invoke-with-messaging.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/powershell-script-with-input-output.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/powershell-script-with-input-output.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/psscript.config.dsc.yaml b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/psscript.config.dsc.yaml new file mode 100644 index 000000000..b3d6126ff --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/psscript.config.dsc.yaml @@ -0,0 +1,105 @@ +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.vscode.json + +parameters: + motd: + type: string + defaultValue: "Hello, friend!" + minLength: 1 + maxLength: 100 + +resources: +- type: Microsoft.DSC.Transitional/PowerShellScript + name: Report processor info + properties: + getScript: |- + $count = [System.Environment]::ProcessorCount + $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture + + [ordered]@{ + processorCount = $count + processorArchitecture = $arch.ToString() + } + setScript: |- + $count = [System.Environment]::ProcessorCount + $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture + + [ordered]@{ + processorCount = $count + processorArchitecture = $arch.ToString() + } +- type: Microsoft.DSC.Transitional/PowerShellScript + name: Message of the Day + properties: + input: "[parameters('motd')]" + getScript: | + param($motd) + + $filePath = 'Temp:/example.motd' + + Write-Verbose "Checking for MOTD file at '$filePath'" + $result = [ordered]@{ + filePath = $filePath + exists = Test-Path -Path $filePath + } + + if ($result.exists) { + Write-Verbose "MOTD file found at '$filePath', retrieving content and last updated time" + $result.motd = (Get-Content -Path $filePath -Raw).TrimEnd("`r", "`n") + $result.lastUpdated = (Get-Item -Path $filePath).LastWriteTime + } else { + Write-Verbose "MOTD file not found at '$filePath'" + } + + $result + setScript: |- + param($motd) + + $filePath = 'Temp:/example.motd' + $result = [ordered]@{ + filePath = $filePath + exists = $true + motd = $motd + } + + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "MOTD file not found at '$filePath', creating new file" + New-Item -Path $filePath -ItemType File -Force | Out-Null + Write-Verbose "MOTD file created at '$filePath', setting content" + $motd | Set-Content -Path $filePath -Force + } else { + Write-Verbose "MOTD file found at '$filePath', checking content" + $currentMotd = (Get-Content -Path $filePath -Raw).TrimEnd("`r", "`n") + if ($currentMotd -ne $motd) { + Write-Verbose "MOTD content differs from desired value, updating file" + $motd | Set-Content -Path $filePath -Force + } else { + Write-Verbose "MOTD content matches desired value, no update needed" + } + } + + $result.lastUpdated = (Get-Item -Path $filePath).LastWriteTime + + $result + testScript: |- + param($motd) + + $filePath = 'Temp:/example.motd' + + Write-Verbose "Checking for MOTD file at '$filePath'" + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "MOTD file not found at '$filePath'" + return $false + } + + Write-Verbose "MOTD file found at '$filePath', retrieving content" + $currentMotd = Get-Content -Path $filePath -Raw + if ([string]::IsNullOrEmpty($currentMotd)) { + Write-Verbose "MOTD file at '$filePath' is empty" + return $false + } elseif ($currentMotd -ne $motd) { + Write-Verbose "Expected MOTD content '$motd' does not match actual content '$currentMotd'" + return $false + } + + Write-Verbose "MOTD content is the expected value '$motd'" + $true \ No newline at end of file diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/run-a-simple-powershell-script.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/run-a-simple-powershell-script.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/index.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/index.md index 29b0a1f6a..6fec96e40 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/index.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/index.md @@ -5,6 +5,8 @@ ms.topic: reference title: Microsoft.DSC.Transitional/PowerShellScript --- + + # Microsoft.DSC.Transitional/PowerShellScript ## Synopsis @@ -12,9 +14,8 @@ title: Microsoft.DSC.Transitional/PowerShellScript Enable running PowerShell 7 scripts inline. > [!IMPORTANT] -> The `psscript` command and `Microsoft.DSC.Transitional/PowerShellScript` resource -> is intended as a temporary transitional resource while migrating DSCv3 resources for -> your needs. +> The `Microsoft.DSC.Transitional/PowerShellScript` resource is intended as a temporary +> transitional resource while defining DSC resources for your needs. ## Metadata @@ -43,19 +44,183 @@ resources: ## Description -The `Microsoft.DSC.Transitional/PowerShellScript` resource enables you to run PowerShell 7 scripts inline -as part of your DSC configuration. This resource is useful for executing PowerShell logic that hasn't -been fully transitioned to a dedicated DSC resource. +The `Microsoft.DSC.Transitional/PowerShellScript` resource enables you to run PowerShell 7 scripts +inline as part of your DSC configuration. This resource is useful for executing PowerShell logic +that hasn't been fully transitioned to a dedicated DSC resource. The resource allows you to: -- Define separate PowerShell scripts for get, set, and test operations -- Pass input data to the scripts -- Receive output data from the scripts -- Control the desired state behavior through the `_inDesiredState` property +- Define separate PowerShell scripts for **Get**, **Set**, and **Test** operations. +- Pass input data to the scripts. +- Receive output data from the scripts. +- Control the desired state behavior through the `_inDesiredState` property. + +The properties you define determine how the resource behaves. + +- If you don't define `getScript`, the `actualState` field in **Get** operation results and + `beforeState` field in **Set** operation results is always an empty object (`{}`). +- If you don't define `testScript`, the `inDesiredState` field for **Test** operation results is + always `true`. +- If you don't define `setScript`, the `afterState` field in **Set** operation results is always + an empty object (`{}`). +- If you define `input`, every script property you define _must_ start with a `param()` statement + that defines a single parameter. The value for `input` is _always_ passed to the scripts when the + resource invokes them. + + When the instance _doesn't_ define `input` the script properties must **not** include a `param()` + statement. > [!NOTE] -> This resource requires PowerShell 7 (`pwsh`) to be installed on the system. +> This resource always invokes the script properties in PowerShell (`pwsh`). To define a resource +> instance with script properties that execute in Windows PowerShell (`powershell.exe`), see +> [`Microsoft.DSC.Transitional/WindowsPowerShellScript`][01]. + +### Defining script properties + +For an instance to be functional you must define one or more script properties: + +- Define `getScript` to retrieve actual system state with the **Get** operation or to show how the + instance modified the system during a **Set** operation. +- Define `testScript` to indicate whether the system is in the desired state with the **Test** + operation. + + > [!IMPORTANT] + > Version `0.1.0` of the resource does _not_ invoke the `testScript` to determine whether to + > invoke the `setScript`. The resource always invokes `setScript` for the **Set** operation. + > + > Ensure that you define the `setScript` to be idempotent or include a check before making any + > changes to the system to avoid unnecessary processing and unintended behaviors. + +- Define `setScript` to modify the system with the **Set** operation. You can use this resource to + define an instance that performs a specific task, such as warming a cache or clearing logs, or to + enforce a specific desired state for any number of system components. + + In either case, consider [emitting messages](#emitting-messages) to the user that helps them + understand what the instance is doing during an operation. + + If you are using the resource instance to enforce a specific desired state you should: + + 1. Emit one or more output objects representing the final state of the system components the + instance is modifying. + 1. Define `getScript` to emit the same data structures as output objects representing the actual + state of the system components the instance is managing. + + This ensures that the user can more easily compare the `beforeState` and `afterState` fields of + the **Set** operation result to see how the instance modified the system. + +The following subsections provide more information on input, error handling, output, and emitting +messages from within the script properties. + +#### Handling input + +To pass input to a script, you must: + +1. Define the script property with a `param()` statement that specifies a single parameter. + Omitting the `param()` statement, defining an empty `param()` statement, or defining more than + one parameter all cause the resource to fail. +1. Define the [`input`](#input) property for the resource instance with a non-null value. When you + omit the `input` property or define it with a null value, like `input: null`, the resource + raises an error causing the operation to fail. + +The data bound to the script parameter is the result of using the `ConvertFrom-Json` cmdlet on the +value for the `input` property of the resource instance. + +You can define the script parameter with a type, like `[string[]]` when the script expects the input +as an array of strings. PowerShell's normal parameter binding and type conversion behavior applies +to the script parameter. If the input data can't be converted to the defined type then the script +fails and raises an error indicating that the input data was invalid. + +You can also apply [validation attributes][02] to the parameter to further validate that the input +data is correct for your script. + +For detailed examples of using input data with this resource, see +[Invoke the PowerShellScript resource with input data][03]. + +#### Handling errors + +This resource invokes the PowerShell scripts with the [`$ErrorActionPreference` variable][04] set +to `Stop`. By default, _any_ error raised by the script, regardless of whether it's terminating, +stops script execution. + +You can control whether script execution continues on an error message in two ways: + +1. Specify the [`-ErrorAction` common parameter][05] for any command you expect to fail. Specify + the value for the parameter as `Continue` to emit the error message or `Ignore` to skip the + error message. In either case, execution will continue after the error. +1. Use a [`try`/`catch` statement][06] to add error handling for errors. When a statement in the + `try` block raises an error, the code in the `catch` block will execute before the code in the + `finally` block (if defined). Unless code in the `catch` or `finally` blocks raises an error, + the script will continue to execute. + +Providing error handling enables you to emit better information for users when something goes wrong +with the script behavior. + +However, even when you provide handling for errors, like using a `try`/`catch` statement or passing +`-ErrorAction Ignore` to a command you expect to fail, the resource considers the operation to have +failed. The resource doesn't populate the `output` property for failed scripts. + +There is no way with the current version of the resource for a script to raise any errors and _not_ +fail. You can only provide better diagnostics for the user in the event of a failure. + +For detailed examples of emitting errors from scripts, see ["Emitting errors"][07] in +[Invoke the PowerShellScript resource with trace messaging][08]. + +#### Returning output + +Any objects emitted by the script for an operation are converted to JSON with the `ConvertTo-Json` +cmdlet and appended to the `output` property array returned by the resource. The ordering of the +items in `output` is the same that they were emitted by the script. + +You can emit any number of items. You don't need to use any specific PowerShell cmdlet to emit +output for this resource. Any output from a PowerShell statement that isn't redirected or captured +as a variable is automatically included in the output. + +You can prevent statements from emitting output by assigning them to `$null`. For example, if your +script uses the `New-Item` cmdlet to create a file, the output for that command is emitted from +your script by default. To avoid emitting that data, you could use the following snippet: + +```powershell +$null = New-Item -Path $filePath +``` + +To provide more readable results to users, consider only emitting a single structured object from +both `getScript` and `setScript`. Emitting an object with descriptive property names makes it +easier to compare the `beforeState` and `afterState` fields for a **Set** operation result. Using +the same data structure also enables DSC to correctly determine the `changedProperties` field for +the **Set** operation result. If the output from `getScript` and `setScript` are identical then +`changedProperties` is an empty array. + +For `testScript`, be sure to _only_ and _always_ emit a single boolean value (`$true` or `$false`). +If `testScript` emits any non-boolean value, more than one boolean value, or no values at all then +the resource considers the operation to have failed and raises an error. + +For comprehensive examples showing how to emit and control output from scripts, see +[Invoke the PowerShellScript resource with output data][09]. + +#### Emitting messages + +The following table maps DSC's tracing levels to PowerShell output streams and `Write-*` cmdlets: + +| DSC trace level | PowerShell stream | PowerShell cmdlets | +|:---------------:|:-----------------:|:-----------------------------:| +| - | Success | `Write-Output` | +| `error` | Error | `Write-Error` | +| `warn` | Warning | `Write-Warning` | +| `info` | Verbose | `Write-Verbose`, `Write-Host` | +| `debug` | Debug | `Write-Debug` | +| `trace` | Information | `Write-Information` | + +> [!IMPORTANT] +> Remember that _any_ error emitted from the script causes the resource and DSC to consider the +> script execution to have failed, even when the script continued after an error. + +For comprehensive examples of emitting messages from scripts, see +[Invoke the PowerShellScript resource with trace messaging][08]. + +## Requirements + +- Using this adapter requires a supported version of PowerShell (`pwsh`) to be installed on the + system. ## Capabilities @@ -65,13 +230,18 @@ The resource has the following capabilities: - `set` - You can use the resource to enforce the desired state for an instance. - `test` - You can use the resource to test whether an instance is in the desired state. -This resource implements its own test functionality through the `testScript` property. For more information about resource capabilities, see [DSC resource capabilities][00]. ## Examples -1. [Run a simple PowerShell script][01] - Shows how to run a basic PowerShell script. -2. [PowerShell script with input and output][02] - Shows how to pass data to and from PowerShell scripts. +1. [Configure a system with the PowerShellScript resource][10] - Shows how to use this resource + in a configuration document. +1. [Invoke the PowerShellScript resource with input data][11] - Shows how to pass data to this + resource. +1. [Invoke the PowerShellScript resource with output data][09] - Shows how to return data from this + resource. +1. [Invoke the PowerShellScript resource with trace messaging][08] - Shows how to emit DSC trace + messages from this resource. ## Properties @@ -80,12 +250,12 @@ The following list describes the properties for the resource. - **Instance properties:** The following properties are optional. They define the desired state for an instance of the resource. - - [getScript](#getscript) - The PowerShell script to run during the get operation. - - [setScript](#setscript) - The PowerShell script to run during the set operation. - - [testScript](#testscript) - The PowerShell script to run during the test operation. + - [getScript](#getscript) - The PowerShell script to run during the **Get** operation. + - [setScript](#setscript) - The PowerShell script to run during the **Set** operation. + - [testScript](#testscript) - The PowerShell script to run during the **Test** operation. - [input](#input) - Input data to pass to the PowerShell scripts. - [output](#output) - Output data returned from the PowerShell scripts. - - [_inDesiredState](#_indesiredstate) - Indicates whether the resource is in the desired state. + - [_inDesiredState](#_indesiredstate) - Indicates whether the resource instance is in the desired state. ### getScript @@ -101,9 +271,17 @@ IsWriteOnly : false -Defines the PowerShell script to execute during the **Get** operation. This script should return -the current state of the resource. The script can access input data and should return relevant -state information. +Defines the PowerShell script to execute during the **Get** operation. This property is never +returned by the resource. The resource invokes the script this property defines for the **Get** +operation and to populate the `beforeState` for a **Set** operation. + +This script should return the current state of the instance. The script can access input data and +should return relevant state information. _Every_ item the script emits to the PowerShell success +stream is inserted into the [`output`](#output) property. + +When possible, prefer emitting a single structured object to the success stream. This makes reading +the `actualState` for a **Get** operation result and the `beforeState` for a **Set** operation +result easier for users. ### setScript @@ -119,9 +297,15 @@ IsWriteOnly : false -Defines the PowerShell script to execute during the **Set** operation. This script should -configure the system to match the desired state. The script can access input data and -should perform the necessary changes to bring the system into compliance. +Defines the PowerShell script to execute during the **Set** operation. This script should configure +the system to match the desired state. The script can access input data and should perform the +necessary changes to bring the system into compliance. + +If the instance defines the [`getScript`](#getscript) property to return data then this property +_should_ return data in the same order and structure. The result object for the **Set** operation +includes `beforeState` (populated by the output for `getScript`) and `afterState` (populated by the +output for `setScript`). Keeping the output order and structure the same for both scripts enables +easier comparison of the changes in resource state. ### testScript @@ -138,8 +322,20 @@ IsWriteOnly : false Defines the PowerShell script to execute during the **Test** operation. This script should -determine whether the system is in the desired state and return appropriate state information. -The script can access input data and should return state information including the `_inDesiredState` property. +determine whether the system is in the desired state and return appropriate state information. The +script can access input data and should return a single boolean value of `$true` or `$false`. + +The script should _not_ emit any other data for output. Emitting more data than a single boolean +value or emitting a non-boolean value causes the resource to raise an error. + +Instead, [emit messages](#emitting-messages) to indicate how and why the instance is out of the +desired state. + +> [!IMPORTANT] +> In version `0.1.0` for the resource, this script is _only_ invoked for the **Test** operation +> when you use the `dsc config test` or `dsc resource test` commands. When you invoke the **Set** +> operation the resource _always_ invokes the [`setScript`](#setscript) even when `testScript` +> would report that the resource is in the desired state. ### input @@ -155,9 +351,51 @@ IsWriteOnly : false -Defines input data to pass to the PowerShell scripts. This can be any valid JSON data type -including strings, booleans, integers, objects, arrays, or null. The input data is available -to all scripts (get, set, test) and can be used to parameterize script behavior. +Defines input data to pass to the PowerShell scripts. This can be any of the following JSON data +types: + +- `string` +- `boolean` +- `integer` +- `object` +- `array` + +The input data is available to every script property and can be used to parameterize script +behavior. + +When passing input data to a script, always define the `params` keyword with a single named +parameter, like `params($inputData)`. The resource binds the value from the `input` property to +that parameter. + +The value for this property affects how it is passed to the PowerShell scripts for the resource: + +| JSON value type | Bound PowerShell parameter value | +|:---------------:|:--------------------------------:| +| `string` | `[String]` | +| `object` | `[PSCustomObject]` | +| `array` | `[Object[]]` | +| `integer` | `[Int64]` | +| `number` | Invalid † | +| `boolean` | `[Boolean]` | +| `null` | Invalid † | + +> [!NOTE] +> Passing a number with a fractional part, such as `1.23`, or `null` is invalid for the top-level +> value of the `input` field. However, you can pass numbers and `null` values nested as object +> properties or array items. +> +> For example, `input: 1.23` is invalid while `input: {"num": 1.23}` and `input: [1.23]` are valid. +> Similarly, `input: null` is invalid while `input: {nested: null}` and `input: [null]` are both +> valid. + +If you define your scriptblock parameters without providing a type for the input data, like +`params($inputData)`, the type for that parameter is exactly as described in the prior table. You +can also define a type for the parameter, which causes PowerShell to cast the input data to the +given type. For example, `params([string[]]$inputData)` will cast the value for `input` to an array +of strings. + +For comprehensive examples of how to use input data with this resource, see +[Invoking the PowerShellScript resource with input data][03]. ### output @@ -173,8 +411,31 @@ IsWriteOnly : false -Defines output data returned from the PowerShell scripts. This property contains the results -of script execution and can include any data that the scripts choose to return. +Defines output data returned from the PowerShell scripts. This property contains the results of +script execution and can include any data that the scripts choose to return. + +Every object emitted to the PowerShell success output stream is inserted into the `output` for the +operation in the order that the scriptblock emits those objects. The emitted items are +automatically converted to JSON values by the resource. Don't use the `ConvertTo-Json` cmdlet to +transform the items yourself. + +When emitting objects with nested properties the resource will emit the object up 9 levels deep. +Objects with more deep nesting fail to serialize correctly into JSON. + +Where possible, limit the output data to the value you need. You can use the `Select-Object` cmdlet +to select only the required properties or create a custom object to represent the output data. + +> [!IMPORTANT] +> This resource doesn't populate the `output` property for failed scripts. The resource considers +> a script to have failed when it emits _any_ errors, even when those errors are explicitly handled. +> For more information, see the [Handling errors](#handling-errors) section of this documentation. + +Using the `Write-*` cmdlets to emit messages to PowerShell's other output streams doesn't populate +the `output` property. Instead, those messages are surfaced through DSC's tracing. For more +information, see the [Emitting messages](#emitting-messages) section of this documentation. + +For comprehensive examples of how to return output data with this resource, see +[Invoking the PowerShellScript resource with output data][09]. ### _inDesiredState @@ -191,9 +452,13 @@ DefaultValue : null -Indicates whether the resource is in the desired state. This property is typically set by -the `testScript` and used by DSC to determine whether the `setScript` needs to be executed. -When `null` (default), DSC will rely on the test script logic to determine state. +Indicates whether the resource is in the desired state. This property is only returned when a +caller invokes the **Test** operation for the resource. The value of this property depends on +whether the resource defines the [`testScript](#testscript) property: + +1. When the resource instance defines `testScript`, DSC invokes that script and uses the boolean + result it returns as the value of this property. +1. When the resource instance doesn't define `testScript`, the value is `true`. ## Instance validating schema @@ -242,8 +507,8 @@ Indicates the resource operation completed without errors. ### Exit code 1 -Indicates the PowerShell script execution failed. When the resource returns this -exit code, it also emits an error message with details about the execution failure. +Indicates the PowerShell script execution failed. When the resource returns this exit code, it also +emits an error message with details about the execution failure. ### Exit code 2 @@ -252,20 +517,27 @@ exit code, it writes the error to the console. ### Exit code 3 -Indicates the script had errors, typically due to missing or invalid input data. -This exit code is commonly returned when required input parameters are not provided -to the PowerShell scripts or when the input data is in an unexpected format. +Indicates the script had errors, typically due to missing or invalid input data. This exit code is +commonly returned when required input parameters are not provided to the PowerShell scripts or when +the input data is in an unexpected format. ## See also -- [Microsoft.DSC.Transitional/RunCommandOnSet][03] -- [Microsoft.DSC.PowerShell][04] -- [Microsoft.Windows.WindowsPowerShell][05] +- [Microsoft.DSC.Transitional/RunCommandOnSet][12] +- [Microsoft.DSC.Transitional/WindowsPowerShellScript][13] [00]: ../../../../concepts/dsc/resource-capabilities.md -[01]: ./examples/run-simple-powershell-script.md -[02]: ./examples/powershell-script-with-input-output.md -[03]: ../RunCommandOnSet/index.md -[04]: ../../PowerShell/index.md -[05]: ../../../../Microsoft/Windows/WindowsPowerShell/index.md +[01]: ../WindowsPowerShellScript/index.md +[02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#parameter-and-variable-validation-attributes +[03]: ./examples/invoke-with-input-data.md +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#erroractionpreference +[05]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-erroraction +[06]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally +[07]: ./examples/invoke-with-messaging.md#emitting-errors +[08]: ./examples/invoke-with-messaging.md +[09]: ./examples/invoke-with-output-data.md +[10]: ./examples/configure-with-script.md +[11]: ./examples/powershell-script-with-input-output.md +[12]: ../RunCommandOnSet/index.md +[13]: ../WindowsPowerShellScript/index.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md index e69de29bb..54f39c2dc 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md @@ -0,0 +1,183 @@ +--- +description: > + Example showing how to use the Microsoft.DSC.Transitional/WindowsPowerShellScript resource + to get, test, and set Windows service state using inline Windows PowerShell 5.1 scripts. +ms.date: 05/10/2026 +ms.topic: reference +title: Manage Windows service state with PowerShell +--- + +# Manage Windows service state with PowerShell + +> [!IMPORTANT] +> This example is intended to illustrate the capabilities and patterns of the +> `Microsoft.DSC.Transitional/WindowsPowerShellScript` resource, not as a recommended approach for +> managing Windows services. DSC ships the [`Microsoft.Windows/Service`][02] resource specifically +> for this purpose. Use `Microsoft.Windows/Service` instead — it provides a dedicated schema, +> better error messages, and does not require you to write inline scripts. + +This example shows how to use Windows PowerShell 5.1 scripts to get the status of a Windows +service, test whether it is running, and start or stop it. + +## Get service status + +The following snippet uses the [dsc resource get][03] command to retrieve the current status of the +**Print Spooler** service. + +```powershell +$instance = @' +getScript: | + param($inputObj) + $svc = Get-Service -Name $inputObj.name + [PSCustomObject]@{ + name = $svc.Name + displayName = $svc.DisplayName + status = $svc.Status.ToString() + startType = $svc.StartType.ToString() + } +input: + name: spooler +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```yaml +actualState: + output: + - name: spooler + displayName: Print Spooler + status: Running + startType: Automatic +``` + +## Test service state + +The following snippet uses the [dsc resource test][04] command to check whether the **Print +Spooler** service is running. The `testScript` must return exactly one boolean value. + +```powershell +$instance = @' +testScript: | + param($inputObj) + $svc = Get-Service -Name $inputObj.name + $svc.Status -eq 'Running' +input: + name: spooler +'@ + +dsc resource test --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```yaml +actualState: + _inDesiredState: true +inDesiredState: true +differingProperties: [] +``` + +## Set service state + +The following snippet uses the [dsc resource set][05] command to ensure the **Print Spooler** +service is running. The `getScript` captures `beforeState` and the `setScript` starts the service +if it is not already running, then captures `afterState`. + +```powershell +$instance = @' +getScript: | + param($inputObj) + $svc = Get-Service -Name $inputObj.name + [PSCustomObject]@{ + name = $svc.Name + status = $svc.Status.ToString() + } +setScript: | + param($inputObj) + $svc = Get-Service -Name $inputObj.name + if ($svc.Status -ne 'Running') { + Start-Service -Name $inputObj.name + } + $svc.Refresh() + [PSCustomObject]@{ + name = $svc.Name + status = $svc.Status.ToString() + } +input: + name: spooler +'@ + +dsc resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```yaml +beforeState: + output: + - name: spooler + status: Stopped +afterState: + output: + - name: spooler + status: Running +``` + +## Using a configuration document + +The following configuration document ensures the **Print Spooler** service is running and the +**Fax** service is stopped. Use the [dsc config set][01] command to enforce it. + +```yaml +# services.config.dsc.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: ensureSpoolerRunning + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + properties: + getScript: | + param($inputObj) + [PSCustomObject]@{ name = $inputObj.name; status = (Get-Service $inputObj.name).Status.ToString() } + testScript: | + param($inputObj) + (Get-Service -Name $inputObj.name).Status -eq 'Running' + setScript: | + param($inputObj) + Start-Service -Name $inputObj.name + input: + name: spooler + + - name: ensureFaxStopped + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + properties: + getScript: | + param($inputObj) + [PSCustomObject]@{ name = $inputObj.name; status = (Get-Service $inputObj.name).Status.ToString() } + testScript: | + param($inputObj) + (Get-Service -Name $inputObj.name).Status -eq 'Stopped' + setScript: | + param($inputObj) + Stop-Service -Name $inputObj.name -Force + input: + name: fax +``` + +```powershell +dsc config set --file services.config.dsc.yaml +``` + +> [!TIP] +> If you find yourself writing patterns like the one above for multiple services, switch to +> [`Microsoft.Windows/Service`][02]. It handles the get/test/set logic for you with a declarative +> schema and requires no inline scripts. +> +> More broadly, before writing any inline script resource, check whether DSC already ships a +> purpose-built resource for what you need. Native resources provide validated schemas, better +> error messages, and safer idempotency guarantees than hand-written scripts. Browse the +> [resource reference][06] to see what is available. + + +[01]: ../../../../../../cli/config/set.md +[02]: ../../../Windows/Service/index.md +[03]: ../../../../../../cli/resource/get.md +[04]: ../../../../../../cli/resource/test.md +[05]: ../../../../../../cli/resource/set.md +[06]: ../../../../../../resources/overview.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md index 12c64bf15..6f8e1e03f 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md @@ -260,7 +260,7 @@ to the PowerShell scripts or when the input data is in an unexpected format. [00]: ../../../../concepts/dsc/resource-capabilities.md -[01]: ./examples/manage-windows-services-with-powershell.md +[01]: ./examples/manage-windows-service-state-with-powershell.md [02]: ../PowerShellScript/index.md [03]: ../RunCommandOnSet/index.md [04]: ../../../../Microsoft/Windows/WindowsPowerShell/index.md From 65b9a48edd52fb12a3268d84aada90de22abaf00 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Mon, 8 Jun 2026 11:06:32 -0500 Subject: [PATCH 2/3] (DOCS) Update WindowsPowerShellScript This change updates the reference docs for the `WindowsPowerShellScript` resource, including new examples and updates to the index page. It is largely a mirror of the `PowerShellScript` resource, as the resources share an implementation script. --- .../examples/configure-with-script.md | 310 ++++++ .../examples/invoke-with-input-data.md | 469 +++++++++ .../examples/invoke-with-messaging.md | 519 ++++++++++ .../examples/invoke-with-output-data.md | 888 ++++++++++++++++++ ...e-windows-service-state-with-powershell.md | 183 ---- .../examples/winpsscript.config.dsc.yaml | 105 +++ .../WindowsPowerShellScript/index.md | 360 ++++++- 7 files changed, 2609 insertions(+), 225 deletions(-) create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/configure-with-script.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-input-data.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-messaging.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-output-data.md delete mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md create mode 100644 docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/winpsscript.config.dsc.yaml diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/configure-with-script.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/configure-with-script.md new file mode 100644 index 000000000..8b58caadb --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/configure-with-script.md @@ -0,0 +1,310 @@ +--- +description: > + Example showing how to use the WindowsPowerShellScript resource in a DSC + configuration document. +ms.date: 05/10/2026 +ms.topic: reference +title: Configure a system with the WindowsPowerShellScript resource +--- + + + +# Configure a system with the WindowsPowerShellScript resource + +This example shows how you can use the [`Microsoft.DSC.Transitional/WindowsPowerShellScript`][01] +resource in a configuration both to invoke non-idempotent scripts and to idempotently manage a +message of the day file that doesn't have a specific DSC resource. + +## Definition + +The configuration document for this example defines two instances of the resource: + +1. The first instance, `Report processor info`, returns the number of processor cores and the + processor architecture from both `getScript` and `setScript`. This instance is informational + only - it doesn't modify the system. +1. The second instance, `Message of the Day`, idempotently manages a message of the day file. It + uses `input` to define the contents of the file and pulls the value for the input from the + `parameters` definition. It defines all three script properties: `getScript` to return the + actual state, `testScript` to determine if the instance is in the desired state, and `setScript` + to enforce the desired state. + + The `getScript` and `setScript` definitions return the same structured output representing the + state of the MOTD file to make monitoring how the instance modifies the system easier. All three + script definitions use the `Write-Verbose` cmdlet to emit informational messages about what the + instance is doing. In particular the messages from `testScript` describe whether and how the + file isn't in the desired state to address the limited information the script can surface in its + output. + +:::code language="yaml" source="winpsscript.config.dsc.yaml"::: + +Copy the configuration document and save it as `winpsscript.config.dsc.yaml`. + +## Get the current state + +To retrieve the current state of the system, use the [dsc config get][02] command on the +configuration document. + +```powershell +dsc --trace-level info config get --file ./winpsscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT1.2985379S + metadata: + Microsoft.DSC: + duration: PT1.2985379S + name: Report processor info + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + actualState: + output: + - processorCount: 8 + processorArchitecture: X64 +- executionInformation: + duration: PT0.9556133S + metadata: + Microsoft.DSC: + duration: PT0.9556133S + name: Message of the Day + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + actualState: + output: + - filePath: Temp:/example.motd + exists: false +messages: [] +hadErrors: false +``` + +The command emitted messages to stderr and the result to stdout. The messages include informational +messages from `getScript` for the message of the day instance indicating that the script looked for +but did not find the MOTD file. + +The result includes structured output from both instances: + +- The processor report instance shows that the system has `8` cores and is an `X64` architecture. +- The message of the day instance shows that the expected MOTD file doesn't exist at + `Temp:/example.motd`. + +## Enforce the desired state + +To update the system to the desired state, use the [dsc config set][03] command on the +configuration document. + +```powershell +dsc --trace-level info config set --file ./winpsscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd' + INFO PID : MOTD file not found at 'Temp:/example.motd', creating new file + INFO PID : MOTD file created at 'Temp:/example.motd', setting content + INFO diff: key 'motd' missing + INFO diff: key 'lastUpdated' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT2.1871641S + metadata: + Microsoft.DSC: + duration: PT2.1871641S + name: Report processor info + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] +- executionInformation: + duration: PT1.708226S + metadata: + Microsoft.DSC: + duration: PT1.708226S + name: Message of the Day + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - exists: false + filePath: Temp:/example.motd + afterState: + output: + - exists: true + motd: Hello, friend! + filePath: Temp:/example.motd + lastUpdated: 2026-06-02T18:05:16.8811712-05:00 + changedProperties: + - output +messages: [] +hadErrors: false +``` + +As before, the message of the day instance surfaces informational messages. The messages show that +the MOTD file wasn't found and then the `setScript` reports that it is creating the file and +setting the content. + +It's easier to review the result data for each instance separately: + +- ```yaml + name: Report processor info + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] + ``` + + The processor info report shows the same state for the system before and after the **Set** + operation. If the instance didn't define `setScript` then `afterState` would be an empty object + (`{}`) and the `changedProperties` field would report that `output` was modified. Providing + identical output for the `setScript` ensures that the result doesn't imply any system changes. + +- ```yaml + name: Message of the Day + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - exists: false + filePath: Temp:/example.motd + afterState: + output: + - exists: true + motd: Hello, friend! + filePath: Temp:/example.motd + lastUpdated: 2026-06-02T18:05:16.8811712-05:00 + changedProperties: + - output + ``` + + The result for the message of the day instance shows that `exists` changed from `false` to `true`. + The `afterState` also includes the `motd` property showing the newly-set MOTD and reports the + last updated time for the file. + +If you invoke the **Set** operation for the configuration again you should see that neither instance +modifies the system: + +```powershell +dsc --trace-level info config set --file ./winpsscript.config.dsc.yaml +``` + +```Messages + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Checking for MOTD file at 'Temp:/example.motd' + INFO PID : MOTD file found at 'Temp:/example.motd', retrieving content and last updated time + INFO PID : MOTD file found at 'Temp:/example.motd', checking content + INFO PID : MOTD content matches desired value, no update needed +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + Microsoft.DSC: + # Elided for brevity +results: +- executionInformation: + duration: PT3.8028321S + metadata: + Microsoft.DSC: + duration: PT3.8028321S + name: Report processor info + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - processorCount: 8 + processorArchitecture: X64 + afterState: + output: + - processorCount: 8 + processorArchitecture: X64 + changedProperties: [] +- executionInformation: + duration: PT2.6216447S + metadata: + Microsoft.DSC: + duration: PT2.6216447S + name: Message of the Day + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + beforeState: + output: + - filePath: Temp:/example.motd + motd: Hello, friend! + exists: true + lastUpdated: 2026-06-03T08:46:38.0491245-05:00 + afterState: + output: + - motd: Hello, friend! + exists: true + lastUpdated: 2026-06-03T08:46:38.0491245-05:00 + filePath: Temp:/example.motd + changedProperties: [] +messages: [] +hadErrors: false +``` + +## Cleanup + +To return your system to its original state, invoke the following Windows PowerShell command to +remove the MOTD file from the `Temp:/` folder: + +```powershell +Remove-Item -Path 'Temp:/example.motd' -Verbose +``` + + +[01]: ../index.md +[02]: ../../../../../../cli/config/get.md +[03]: ../../../../../../cli/config/set.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-input-data.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-input-data.md new file mode 100644 index 000000000..153f8d34c --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-input-data.md @@ -0,0 +1,469 @@ +--- +description: > + Example showing how to pass input data to a WindowsPowerShellScript resource + and access properties, array elements, and nested values inside the script. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the WindowsPowerShellScript resource with input data +--- + + + +# Invoke the WindowsPowerShellScript resource with input data + +These examples show how you can pass input data to the +[`Microsoft.DSC.Transitional/WindowsPowerShellScript` resource][01] and how to bind that data to your +script with a [`param()` statement][02]. + +## Input data types + +The following examples show how data input is bound to the parameters for a defined scriptblock +when the parameter isn't defined with a specific type. + +The data that the resource passes to a script is first converted from the JSON input that DSC sends +with the [`ConvertFrom-Json` cmdlet][03]. + +### Passing string input data + +When you define `input` as a string value, the parameter for the script is a `[string]` object. + +```powershell +$instance = @' +input: hello world +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.String]' + boundDataValue: hello world +``` + +### Passing integer input data + +When you define `input` as an integer value, the parameter for the script is an `[Int64]` value. + +```powershell +$instance = @' +input: 10 +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.Int64]' + boundDataValue: 10 +``` + +### Passing boolean input data + +When you define `input` as a boolean value, the parameter for the script is a `[Boolean]` value. + +```powershell +$instance = @' +input: true +getScript: |- + param($inputData) + + [ordered]@{ + boundDataType = "[$($inputData.GetType().FullName)]" + boundDataValue = $inputData + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataType: '[System.Boolean]' + boundDataValue: true +``` + +### Passing array input data + +When you define `input` as a string value, the parameter for the script is a `[Object[]]` array. +The items in the array are data types as emitted by the [`ConvertFrom-Json` cmdlet][03]. + +```powershell +$instance = @' +input: +- hello world +- 10 +- 1.23 +- true +- null +- nested: object +- - nested + - array +getScript: |- + param($inputData) + + $inputData | ForEach-Object -Begin { $i = 0 } -Process { + [ordered]@{ + boundDataItemIndex = $i + boundDataItemType = if ($null -eq $_) { + '$null' + } else { + "[$($_.GetType().FullName)]" + } + boundDataItemValue = $_ + } + $i++ + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataItemIndex: 0 + boundDataItemType: '[System.String]' + boundDataItemValue: hello world + - boundDataItemIndex: 1 + boundDataItemType: '[System.Int64]' + boundDataItemValue: 10 + - boundDataItemIndex: 2 + boundDataItemType: '[System.Double]' + boundDataItemValue: 1.23 + - boundDataItemIndex: 3 + boundDataItemType: '[System.Boolean]' + boundDataItemValue: true + - boundDataItemIndex: 4 + boundDataItemType: $null + boundDataItemValue: null + - boundDataItemIndex: 5 + boundDataItemType: '[System.Management.Automation.PSCustomObject]' + boundDataItemValue: + nested: object + - boundDataItemIndex: 6 + boundDataItemType: '[System.Object[]]' + boundDataItemValue: + - nested + - array +``` + +### Passing object input data + +When you define `input` as an object value, the parameter for the script is a `[pscustomobject]`. +The values for each property of the object are data types as emitted by the +[`ConvertFrom-Json` cmdlet][03]. + +```powershell +$instance = @' +input: + string: hello world + integer: 10 + number: 1.23 + boolean: true + "null": null + nestedObject: + foo: bar + nestedArray: + - nested + - array +getScript: |- + param($inputData) + + $inputData.psobject.Properties | ForEach-Object -Process { + [ordered]@{ + boundDataPropertyName = $_.Name + boundDataPropertyType = "[$($_.TypeNameOfValue)]" + boundDataPropertyValue = $_.Value + } + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - boundDataPropertyName: string + boundDataPropertyType: '[System.String]' + boundDataPropertyValue: hello world + - boundDataPropertyName: integer + boundDataPropertyType: '[System.Int64]' + boundDataPropertyValue: 10 + - boundDataPropertyName: number + boundDataPropertyType: '[System.Double]' + boundDataPropertyValue: 1.23 + - boundDataPropertyName: boolean + boundDataPropertyType: '[System.Boolean]' + boundDataPropertyValue: true + - boundDataPropertyName: 'null' + boundDataPropertyType: '[System.Object]' + boundDataPropertyValue: null + - boundDataPropertyName: nestedObject + boundDataPropertyType: '[System.Management.Automation.PSCustomObject]' + boundDataPropertyValue: + foo: bar + - boundDataPropertyName: nestedArray + boundDataPropertyType: '[System.Object[]]' + boundDataPropertyValue: + - nested + - array +``` + +## Casting input data + +When you define the parameters for a scriptblock, you can specify a type for the input data. The +script uses PowerShell's [parameter type conversion][04] to try to convert the input +data. If the type conversion is impossible for the input data, PowerShell raises an error and the +operation fails. + +The following example shows how you can convert the input data to a given type. In this case, it +converts every item in the input data into a `[datetime]` object. + +```powershell +$instance = @' +input: + - 2026-01-02 + - 01/20/2026 +getScript: |- + param([datetime[]]$inputData) + + $inputData | ForEach-Object { + [ordered]@{ + InputDate = $_ + NextDate = $_.AddDays(1) + } + } +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - InputDate: 2026-01-02T00:00:00 + NextDate: 2026-01-03T00:00:00 + - InputDate: 2026-01-20T00:00:00 + NextDate: 2026-01-21T00:00:00 +``` + +## Input related errors + +Passing input to a script has several requirements: + +1. The script property for the resource must use the `param()` statement to define exactly one + parameter. +1. The `input` property for the resource must be defined with a non-null value. +1. If the `param()` statement defines a type for the input data, the value for the `input` property + of the instance must be convertible to that type. + +The resource raises an error and prevents the script from executing when any of these requirements +aren't met by the resource instance definition. + +### Error: input provided but script has no parameters + +If you provide a value for `input` but the script does not define a `param()` statement, the +resource exits with code `2` and emits the following error message: + +```plaintext +Input was provided but script does not have a parameter to accept input. +``` + +```powershell +$instance = @' +getScript: | + "Script without parameters" +input: oops +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Input was provided but script does not have a parameter to accept input. + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines a parameter but no input provided + +If the script defines a `param()` statement but no `input` is specified for the instance, the +resource exits with code `2` and emits the following error message: + +```plaintext +Script has a parameter '' but no input was provided. +``` + +```powershell +$instance = @' +getScript: | + param($inputObj) + "This will not run" +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Script has a parameter 'inputObj' but no input was provided. + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines more than one parameter + +If the script defines a `param()` statement with two or more parameters, the resource exits with +code `1` and emits the following error message: + +```plaintext +Script must have exactly one parameter. +``` + +```powershell +$instance = @' +input: +- first +- second +getScript: |- + param($a, $b) + + [ordered]@{ + a = $a + b = $b + } +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID 23764: Script must have exactly one parameter. + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +### Error: Script defines a typed parameter but input is invalid + +If the script defines the `param()` statement with a parameter that has a defined type that the +input data can't convert into, the resource raises an error message about an argument +transformation failure. + +```powershell +$instance = @' +input: foo +getScript: |- + param([int]$inputData) + + $inputData +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "Cannot process argument transformation on parameter 'inputData'. Cannot convert value "foo" to type "System.Int32". Error: "The input string 'foo' was not in a correct format."" + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +## Using input in a configuration document + +You can pass input to a `WindowsPowerShellScript` instance inside a DSC configuration document, including +values from configuration parameters. The following configuration uses the [dsc config get][05] +command to pass a port number into the script: + +```yaml +# check-port.dsc.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + port: + type: int + defaultValue: 8080 +resources: + - name: checkPort + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + properties: + getScript: | + param($inputObj) + Write-Information "Checking port $($inputObj.port)..." + Test-NetConnection -ComputerName localhost -Port $inputObj.port | + Select-Object -ExpandProperty TcpTestSucceeded + input: + port: "[parameters('port')]" +``` + +```powershell +dsc config get --file check-port.dsc.yaml +``` + +```yaml +executionInformation: + # Elided for brevity +metadata: + # Elided for brevity +results: +- executionInformation: + duration: PT12.3218732S + metadata: + Microsoft.DSC: + duration: PT12.3218732S + name: checkPort + type: Microsoft.DSC.Transitional/WindowsPowerShellScript + result: + actualState: + output: + - false +messages: [] +hadErrors: false +``` + + +[01]: ../index.md +[02]: https://learn.microsoft.com\powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#parameter-declaration +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/convertfrom-json +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#type-conversion-of-parameter-values +[05]: ../../../../../../cli/config/get.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-messaging.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-messaging.md new file mode 100644 index 000000000..45171efed --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-messaging.md @@ -0,0 +1,519 @@ +--- +description: > + Example showing how to emit trace messages from a WindowsPowerShellScript + resource. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the WindowsPowerShellScript resource with trace messaging +--- + + + +# Invoke the WindowsPowerShellScript resource with trace messaging + +These examples show how you can emit messages from the +[`Microsoft.DSC.Transitional/WindowsPowerShellScript` resource][01]. + +## Emitting errors + +By default, any errors raised during script execution cause the execution to emit the error message +and immediately halt script execution. The following example snippets show how you can provide +error details for the user when a script fails. + +### Emitting an error from a failed cmdlet + +In this example, the script depends on the `tstoy` command being available on the system. When the +command isn't available, the script fails and reports the error. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy -CommandType Application | + Select-Object -ExpandProperty Path + + & $tstoyCmd version --full --format json | ConvertFrom-Json +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The term 'tstoy' is not recognized as a name of a cmdlet, function, script file, or executable program. +Check the spelling of the name, or if a path was included, verify that the path is correct and try again." + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +The first error in the output indicates that the script execution was stopped by the error from the +`Get-Command` invocation, which showed that `tstoy` wasn't available on the system. + +### Emitting errors with `Write-Error` + +Instead of raising the default error from a failed command, you can use the [`Write-Error`][02] +cmdlet to emit a specific error message. In this example, the script depends on the `tstoy` command +being available on the system. When the command isn't available, the script fails and reports the +error. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy* -CommandType Application | + Where-Object {$_.Name -match 'tstoy(\.exe)?' } | + Select-Object -ExpandProperty Path + if ([string]::IsNullOrEmpty($tstoyCmd)) { + Write-Error "command 'tstoy' not found; unable to report version for 'tstoy'" + } + + & $tstoyCmd version --full --format json | ConvertFrom-Json +'@ + + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: command 'tstoy' not found; unable to report version for 'tstoy'" + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +The first error in the output indicates that the script execution was stopped and includes the +message emitted from the `Write-Error` command. + +### Throwing an error from a `catch` block + +In the previous error examples, the emitted error includes information about execution stopping +because of the error action preference being set to stop. You can make the error message clearer +by rethrowing the underlying exception from a the `catch` block in a [`try`/`catch` statement][03]. + +```powershell +$instance = @' +getScript: |- + try { + $tstoyCmd = Get-Command -Name tstoy -CommandType Application | + Select-Object -ExpandProperty Path + + & $tstoyCmd version --full --format json | ConvertFrom-Json + } catch { + throw $_.Exception + } +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + ERROR PID : Exception calling "EndInvoke" with "1" argument(s): "The term 'tstoy' is not recognized as a name of a cmdlet, function, script file, or executable program. +Check the spelling of the name, or if a path was included, verify that the path is correct and try again." + ERROR Failed to run process 'powershell': Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed + ERROR Command: Resource 'powershell' [exit code 1] manifest description: PowerShell script execution failed +``` + +## Emitting warning messages + +You can emit warning messages from a script with the [`Write-Warning`][04] cmdlet. + +This example shows how you can emit a warning from a script without halting execution. The script +looks for the `tstoy` command and returns the version information for that command if it exists. If +the command isn't available, the script raises a warning and returns no output data. + +```powershell +$instance = @' +getScript: |- + $tstoyCmd = Get-Command -Name tstoy* -CommandType Application | + Where-Object {$_.Name -match 'tstoy(\.exe)?' } | + Select-Object -ExpandProperty Path + + if ([string]::IsNullOrEmpty($tstoyCmd)) { + Write-Warning "command 'tstoy' not found; unable to report version for 'tstoy'" + } else { + & $tstoyCmd version --full --format json | ConvertFrom-Json + } +'@ + + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```Output + WARN PID : command 'tstoy' not found; unable to report version for 'tstoy' +actualState: + output: [] +``` + +## Emitting info messages + +You can emit `info` level messages for DSC with the [`Write-Verbose`][05] and [`Write-Host`][06] +cmdlets. When a cmdlet used in your script emits verbose or information messages, you can use the +[`-Verbose` common parameter][07] or specify the [`-InformationAction` common parameter][08] as +`Continue` to have those messages emitted for DSC. + +### Emitting verbose messages from cmdlets + +The following snippet creates a temporary file. It uses commands that emit verbose messages, like +`New-Item`. The example shows how you can specify the `-Verbose` parameter on cmdlets to surface +their verbose messaging in DSC as `info` level trace messages. + +```powershell +$instance = [ordered]@{ + input = 'create' + getScript = { + param( + [ValidateSet('create', 'delete')] + [string] $fileOperation + ) + + $tempFolder = "Temp:/dsc/examples/WindowsPowerShellScript/messaging" + $tempFile = Join-Path $tempFolder 'info.txt' + + if (Test-Path $tempFile) { + $fileInfo = Get-Item -Path $tempFile + + [ordered]@{ + path = $fileInfo.FullName + exists = $true + creationTimeUtc = $fileInfo.CreationTimeUtc + lastWriteTimeUtc = $fileInfo.LastWriteTimeUtc + attributes = $fileInfo.Attributes.ToString() + } + } else { + [ordered]@{ + path = $fileInfo.FullName + exists = $false + } + } + }.ToString() + setScript = { + param( + [ValidateSet('create', 'delete')] + [string] $fileOperation + ) + + $tempFolder = "Temp:\dsc\examples\WindowsPowerShellScript\messaging" + $tempFile = Join-Path $tempFolder 'info.txt' + + switch ($fileOperation) { + 'create' { + if (-not (Test-Path $tempFolder)) { + $null = New-Item -Path $tempFolder -ItemType Directory -Force -Verbose + } + if (-not (Test-Path $tempFile)) { + $null = New-Item -Path $tempFile -ItemType File -Verbose + } + + $fileInfo = Get-Item -Path $tempFile + + [ordered]@{ + path = $fileInfo.FullName + exists = $true + creationTimeUtc = $fileInfo.CreationTimeUtc + lastWriteTimeUtc = $fileInfo.LastWriteTimeUtc + attributes = $fileInfo.Attributes + } + } + 'delete' { + if (Test-Path $tempFile) { + Remove-Item -Path $tempFile -Force -Verbose + } + + [ordered]@{ + path = $fileInfo.FullName + exists = $false + } + } + } + }.ToString() +} + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + $instance | ConvertTo-Json -Compress +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Performing the operation "Create Directory" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\WindowsPowerShellScript". + INFO PID : Performing the operation "Create Directory" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\WindowsPowerShellScript\messaging". + INFO PID : Performing the operation "Create File" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\WindowsPowerShellScript\messaging\info.txt". + INFO diff: key 'creationTimeUtc' missing + INFO diff: key 'lastWriteTimeUtc' missing + INFO diff: key 'attributes' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - path: null + exists: false +afterState: + output: + - path: C:\Users\\AppData\Local\Temp\dsc\examples\WindowsPowerShellScript\messaging\info.txt + exists: true + creationTimeUtc: 2026-05-21T17:58:31.2115007Z + lastWriteTimeUtc: 2026-05-21T17:58:31.2115007Z + attributes: 32 +changedProperties: +- output +``` + +The info messages emitted by DSC include the verbose messages from creating the temporary directory +and file. + +Invoke the resource again but with the `input` set to `delete` to remove the temporary file: + +```powershell +$instance.input = 'delete' + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + $instance | ConvertTo-Json -Compress +) +``` + +### Emitting verbose messages with `Write-Verbose` + +You can surface custom `info` level messages from scripts with the [`Write-Verbose`][05] cmdlet. + +The following snippet shows how messages from `Write-Verbose` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Verbose "Setting things up" + Write-Verbose "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource get @arguments +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Setting things up + INFO PID : Retrieving data +actualState: + output: [] +``` + +### Emitting verbose messages with `Write-Host` + +You can surface custom `info` level messages from scripts with the [`Write-Host`][06] cmdlet. + +The following snippet shows how messages from `Write-Host` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Host "Setting things up" + Write-Host "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource get @arguments +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Setting things up + INFO PID : Retrieving data +actualState: + output: [] +``` + +## Emitting debug messages + +You can emit `debug` level messages for DSC with the [`Write-Debug`][09] cmdlet. When a cmdlet used +in your script emits debug messages, you can use the [`-Debug` common parameter][07] to have those +messages emitted for DSC. + +### Emitting debug messages from cmdlets + +The following snippet shows how debug messages from commands are captured by the resource. It +defines a function that emits debug messages and then invokes that function. + +```powershell +$instance = @' +getScript: |- + function Get-Data { + [CmdletBinding()] + param() + + Write-Debug "Starting process..." + Write-Debug "Doing things..." + Write-Debug "Done." + } + + Get-Data +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Starting process... + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Doing things... + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Done. + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'powershell' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' +actualState: + output: [] +``` + +The output shows `debug` level messages emitted by the invoked function in the script. + +### Emitting debug messages with `Write-Debug` + +You can surface custom `debug` level messages from scripts with the [`Write-Debug`][05] cmdlet. + +The following snippet shows how messages from `Write-Debug` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Debug "Setting things up" + Write-Debug "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Setting things up + DEBUG dsc_lib::dscresources::command_resource: 1218: PID : Retrieving data + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'powershell' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' +actualState: + output: [] +``` + +## Emitting trace messages + +You can emit `trace` level messages for DSC with the [`Write-Information`][10] cmdlet. When a +cmdlet used in your script emits debug messages, you can specify the +[`-InformationAction` common parameter][11] as `Continue` to have those messages emitted for DSC. + +### Emitting trace messages from cmdlets + +The following snippet shows how information messages from commands are captured by the resource as +trace messages. It defines a function that emits information messages and then invokes that +function with `-InformationAction` as `Continue`. + +```powershell +$instance = @' +getScript: |- + function Get-Data { + [CmdletBinding()] + param() + + Write-Information "Starting process..." + Write-Information "Doing things..." + Write-Information "Done." + } + + Get-Data -InformationAction Continue +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level trace resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + TRACE dsc_lib::dscresources::command_resource: 898: Invoking command 'powershell' with args Some(["-NoLogo", "-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "$input | ./psscript.ps1", "get"]) + TRACE dsc_lib::dscresources::command_resource: 900: Current working directory: C:\code\dsc\dsc-pr-review\bin\debug + TRACE dsc_lib::dscresources::command_resource: 806: Writing to command STDIN: {"getScript":"function Get-Data {\n [CmdletBinding()]\n param()\n\n Write-Information \"Starting process...\"\n Write-Information \"Doing things...\"\n Write-Information \"Done.\"\n}\n\nGet-Data -InformationAction Continue"} + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Starting process... + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Doing things... + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Done. + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'powershell' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + TRACE dsc_lib::dscresources::command_resource: 1083: Verify JSON for 'Microsoft.DSC.Transitional/WindowsPowerShellScript': {"output":[]} + +actualState: + output: [] +``` + +The output shows `trace` level messages emitted by the invoked function in the script. + +### Emitting trace messages with `Write-Information` + +You can surface custom `trace` level messages from scripts with the [`Write-Information`][10] cmdlet. + +The following snippet shows how messages from `Write-Information` surface as DSC trace messages. + +```powershell +$instance = @' +getScript: |- + Write-Information "Setting things up" + Write-Information "Retrieving data" +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level trace resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + TRACE dsc_lib::dscresources::command_resource: 898: Invoking command 'powershell' with args Some(["-NoLogo", "-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "$input | ./psscript.ps1", "get"]) + TRACE dsc_lib::dscresources::command_resource: 900: Current working directory: C:\code\dsc\dsc-pr-review\bin\debug + TRACE dsc_lib::dscresources::command_resource: 806: Writing to command STDIN: {"getScript":"Write-Information \"Setting things up\"\nWrite-Information \"Retrieving data\""} + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Setting things up + TRACE dsc_lib::dscresources::command_resource: 1220: PID : Retrieving data + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'powershell' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + TRACE dsc_lib::dscresources::command_resource: 1083: Verify JSON for 'Microsoft.DSC.Transitional/WindowsPowerShellScript': {"output":[]} + +actualState: + output: [] +``` + + +[01]: ../index.md +[02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-error +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-warning +[05]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-verbose +[06]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-host +[07]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-verbose +[08]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-informationaction +[09]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-debug +[10]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-information +[11]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-informationaction diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-output-data.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-output-data.md new file mode 100644 index 000000000..338ec8d56 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/invoke-with-output-data.md @@ -0,0 +1,888 @@ +--- +description: > + Example showing how to return output data from a WindowsPowerShellScript + resource. +ms.date: 05/10/2026 +ms.topic: reference +title: Invoke the WindowsPowerShellScript resource with output data +--- + + + +# Invoke the WindowsPowerShellScript resource with output data + +These examples show how you can return output from the +[`Microsoft.DSC.Transitional/WindowsPowerShellScript` resource][01]. + +## Output data types + +All output that a script emits for this resource is inserted into the `output` array for the +resource instance. The resource uses the `ConvertTo-Json` cmdlet for every item emitted to the +[Success stream][02]. The converted representation is what the resource inserts into the `output` +array. + +When the resource serializes the output data as JSON it retains up to `9` levels of depth. This can +make the output for typical PowerShell objects a script may return very large and difficult to +parse in the result for an operation. + +### Outputting scalar values + +The following snippet shows how scalar values (not objects or arrays) are handled by the resource +when emitted by a script. Scalar values include strings, integers, floats, booleans, and `$null`. + +```powershell +$instance = @' +getScript: |- + $true # boolean scalar value + 1 # integer scalar value + 1.2 # float scalar value + $null # null scalar value + 'apple' # string scalar value +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - true + - 1 + - 1.2 + - null + - apple +``` + +### Outputting objects + +When a script emits objects that aren't scalar values, the conversion to JSON representation +includes up to `9` levels of depth. Objects often have properties that are _also_ objects with +sub-properties or arrays of nested objects. + +When the object output is particularly large and complex it can cause the resource operation to +fail when DSC needs to validate the output data. The following snippet shows how emitting a +`[FileInfo]` object directly can cause the resource to fail. + +The script creates a new temporary file, which emits the `[FileInfo]` object for the new file as +output. + +```powershell +$instance = @' +getScript: |- + $filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' + + New-Item -Path $filePath -Force +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level debug resource get @arguments +``` + +```Output + INFO dsc_lib::dscresources::command_resource: 69: Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + DEBUG dsc_lib::dscresources::command_resource: 850: Process 'powershell' id exited with code 0 + DEBUG dsc_lib::dscresources::command_resource: 72: Verifying output of get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + ERROR dsc::resource_command: 67: JSON: expected value at line 1 column 1 +``` + +We can demonstrate the failure independently of DSC. When you invoke the following snippet, +PowerShell hangs. A `[FileInfo]` object can be extremely large as the object contains references to +its parent folder, which references that object's parent folder, and so on. + +```powershell +$fileInfo = Get-Item -Path 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' +$fileJson = ConvertTo-Json -Depth 9 -InputObject $fileInfo +# The following commands never run because the session hangs +$outputSize = [System.Text.Encoding]::UTF8.GetByteCount($fileJson) / 1MB +"The output JSON is {0} MB" -f [Math]::Round($outputSize, 2) +``` + + +You can cancel the command by pressing Ctrl+C in your console. + +If you update the depth to `5` and invoke the command again, you can see that the size of the JSON +object is _substantial_. + +```powershell +$fileInfo = Get-Item -Path 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' +$fileJson = ConvertTo-Json -Depth 5 -InputObject $fileInfo +# The following commands never run because the session hangs +$outputSize = [System.Text.Encoding]::UTF8.GetByteCount($fileJson) / 1MB +"The output JSON is {0} MB" -f [Math]::Round($outputSize, 2) +``` + +```Output +WARNING: Resulting JSON is truncated as serialization has exceeded the set depth of 5. +The output JSON is 58.37 MB +``` + +Instead of emitting complex objects directly, consider constructing your output objects +intentionally. For a comprehensive example of emitting structured output, see the +["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +section of this article. + +### Outputting arrays + +By default, when a script emits an array as output, each item in the array is captured as a +separate item in the `output` property for the resource. + +The following snippet shows the default behavior. + +```powershell +$instance = @' +getScript: |- + @('a', 'b', 'c') + @(1, 2, 3) +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - a + - b + - c + - 1 + - 2 + - 3 +``` + +In the previous snippet, the script emitted two arrays: + +1. An array containing three strings +1. An array containing three integers + +The `output` for the resource included six separate items representing each of the items in the +emitted arrays in the order that the script emitted them. + +The following snippet shows how you can use the [`Write-Object` cmdlet][03] with the +[`-NoEnumerate`][04] parameter to emit arrays from the script and keep them as arrays. + +```powershell +$instance = @' +getScript: |- + Write-Output -NoEnumerate @('a', 'b', 'c') + Write-Output -NoEnumerate @(1, 2, 3) +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - - a + - b + - c + - - 1 + - 2 + - 3 +``` + +Now the output from the script shows two items in the `output` array. Each item is an array +containing three items. + +## Discarding unwanted output + +Every item emitted to the success stream is included in the `output` for the resource. To avoid +including unwanted data in the output you need to discard that data. To discard data from a +statement that would otherwise emit unwanted output, you can: + +- Assign the statement to `$null`. +- Redirect the statement to `$null`. +- Cast the statement to `[void]`. +- Pipe the statement to `Out-Null`. + +The first three options have nearly identical performance. Piping to `Out-Null` can be much slower +when looping over a large set of data. + +The following snippet shows examples for discarding unwanted output in a script. + +```powershell +$instance = @' +getScript: |- + $filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' + # Assign to `$null` + $null = New-Item -Path $filePath -Force + # Redirect to `$null` + New-Item -Path $filePath -Force > $null + # Cast to `[void]` + [void](New-Item -Path $filePath -Force) + # Pipe to `Out-Null` + New-Item -Path $filePath -Force | Out-Null + + 'this is the only output' +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc resource get @arguments +``` + +```yaml +actualState: + output: + - this is the only output +``` + +## Output for `getScript` + +For `getScript` you can emit any data to the PowerShell success stream that you want to surface to +the user. The emitted data is returned in the `actualState.output` field for the **Get** operation +result and the `beforeState.output` field for the **Set** operation result. + +If you're defining the resource instance to idempotently manage the state of one or more system +components, ensure that the output you emit from `getScript` uses the same structure as the output +from `setScript` to make the results readable for the user. + +Otherwise, return any data that you want to surface to the user. If you want to give the user more +information, you can [emit messages][05]. For comprehensive examples of emitting messages from your +script see [Invoke the WindowsPowerShellScript resource with trace messaging][06]. + +The following example shows how you can emit items from `getScript` to inform the user. For a +comprehensive example of structured output for an instance that idempotently manages system state, +see ["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +in this article. + +```powershell +$instance = @' +getScript: |- + "Current context is interactive: {0}" -f [Environment]::UserInteractive + "Current context is privileged: {0}" -f [Environment]::IsPrivilegedProcess +'@ + +dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```yaml +actualState: + output: + - 'Current context is interactive: True' + - 'Current context is privileged: False' +``` + +## Output for `testScript` + +The `testScript` definition _must_ return a single boolean value - `$true` to indicate that the +system is in the desired state or `$false` otherwise. + +Any of the following will cause the resource to raise an error when invoking the `testScript`: + +- Not emitting any output at all to the success stream. +- Emitting any non-boolean data to the success stream. +- Emitting more than one boolean value to the success stream. + +You can [emit messages][05] To indicate to the user how and why the +system isn't in the desired state. For detailed examples of emitting messages from your script +see [Invoke the WindowsPowerShellScript resource with trace messaging][06]. + +The following example shows how you can define `testScript` to check whether a file exists and +isn't empty. It emits info messages to clarify whether and how the instance is in the desired +state. + +```powershell +$instance = @' +testScript: |- + $filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' + + if (-not (Test-Path $filePath)) { + Write-Verbose "The file '$filePath' doesn't exist" + return $false + } + + if ([string]::IsNullOrEmpty((Get-Content -Raw -Path $filePath))) { + Write-Verbose "The file '$filePath' is empty" + return $false + } + + Write-Verbose "The file '$filePath' exists and contains content" + $true +'@ + +$arguments = @( + '--resource', 'Microsoft.DSC.Transitional/WindowsPowerShellScript' + '--input', $instance +) + +dsc --trace-level info resource test @arguments +``` + +```console + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking test on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : The file 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' is empty + INFO diff: key 'testScript' missing +desiredState: + testScript: |- + $filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' + + if (-not (Test-Path $filePath)) { + Write-Verbose "The file '$filePath' doesn't exist" + return $false + } + + if ([string]::IsNullOrEmpty((Get-Content -Raw -Path $filePath))) { + Write-Verbose "The file '$filePath' is empty" + return $false + } + + Write-Verbose "The file '$filePath' exists and contains content" + $true +actualState: + _inDesiredState: false +inDesiredState: false +differingProperties: +- testScript +``` + +For a more comprehensive example that idempotently manages system state see the +["Structure output for an idempotent instance"](#structure-output-for-an-idempotent-instance) +section of this article. + +## Output for `setScript` + +For `setScript` you can emit any data to the PowerShell success stream that you want to surface to +the user. The emitted data is returned in the `afterState.output` field for the **Set** operation +result. + +If you're defining the resource instance to idempotently manage the state of one or more system +components, ensure that the output you emit from `setScript` uses the same structure as the output +from `getScript` to make the results readable for the user. + +Otherwise, return any data that you want to surface to the user. If you want to give the user more +information, you can [emit messages][05]. For comprehensive examples of +emitting messages from your script see +[Invoke the WindowsPowerShellScript resource with trace messaging][06]. + +The following example shows how you can emit items from `setScript` to inform the user about how +the script is modifying the system. + +```powershell +$instance = @' +setScript: |- + $filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' + $content = 'Hello world' + if (-not (Test-Path $filePath)) { + "File '$filePath' doesn't exist - creating it" + $null = New-Item -Path $filePath -Force + } + + $currentContent = Get-Content -Raw -Path $filePath + if ([string]::IsNullOrEmpty($currentContent)) { + "File '$filePath' is empty - adding content" + $content | Set-Content -Path $filePath -NoNewline + } elseif ($currentContent -ne $content) { + "File '$filePath' contains invalid content - overriding content" + $content | Set-Content -Path $filePath -NoNewline + } else { + "File '$filePath' contains desired content'" + } + + [ordered]@{ + initialContent = $currentContent + finalContent = $content + } +'@ + +dsc resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance +``` + +```yaml +beforeState: {} +afterState: + output: + - File 'Temp:/dsc/examples/WindowsPowerShellScript/output.txt' is empty - adding conent + - initialContent: null + finalContent: Hello world +changedProperties: +- output +``` + +In this example, `beforeState` is an empty object because the instance doesn't define `getScript`. +The output from `setScript` includes two items. The first is a message indicating that the file +exists but is empty. The second item is an object showing both the initial content and final +content of the file. + +For a comprehensive example of structured output for an instance that idempotently manages system +state, including defining `getScript` to populate the `beforeState` in the **Set** result, see the +[Structure output for an idempotent instance](#structure-output-for-an-idempotent-instance) section +of this article. + +## Structure output for an idempotent instance + +To return output that is readable for the user, consider returning only objects. Use property names +to orient the user when reviewing the output. Limit the depth of the object to no more than three +levels when possible. + +The following example shows how you can return information about a JSON configuration file that +isn't managed by a specific DSC resource. It follows best practices by: + +1. Implementing scripts for all three operations. +1. Returning a single structured object from `getScript`. +1. Returning a boolean for `testScript` and emitting trace messages to indicate _how_ the instance + is out of the desired state. +1. Returning the same structured object from `setScript` as `getScript`. +1. Emitting trace messages to indicate which settings the `setScript` is modifying. + +> [!NOTE] +> This example uses an ordered dictionary to represent the instance because the script properties +> are much longer and more detailed than earlier examples in this article. Defining the scripts +> this way makes it easier to review the script code than defining it all together in a YAML +> snippet. +> +> The `getScript` and `setScript` snippets define the output object as an ordered dictionary with +> the `[ordered]` type accelerator. This ensures that the emitted object always keeps the key-value +> pairs in the defined order. Defining the output object as a normal hashtable causes the ordering +> of the output object properties to be nondeterministic, which can make comparing results more +> difficult. +> +> You could also define the output object as a `[pscustomobject]` and use the `Add-Member` function +> to add more properties to the initial object. + +First, define an ordered dictionary to represent the instance. Define the `input` for the scripts +the instance will use. In this example, the input data includes both the path to the file and the +settings to manage in that file. + +```powershell +$instance = [ordered]@{ + input = [ordered]@{ + filePath = 'Temp:/dsc/examples/WindowsPowerShellScript/output.json' + settings = [ordered]@{ + updateAutomatically = $true + updateFrequency = 30 + } + } +} +``` + +Next, define `getScript` to retrieve the actual state of the configuration file. The script must +define a `param()` statement to accept the input data. + +The script returns an object that always includes the `filePath` and `exists` properties. +`filePath` is identical to `input.filePath` for the instance. `exists` indicates whether the file +actually exists on the system. + +If the file doesn't exist, that's all the information the instance can provide. The script returns +that data and stops processing. + +If the file does exist, the output object also includes the `settings` and `lastWriteTime` +properties. `settings` is the contents of the file converted from JSON. `lastWriteTime` is the +actual last write time for the file itself. + +```powershell +$instance.getScript = { + param($inputData) + + $result = [ordered]@{ + filePath = $inputData.filePath + exists = Test-Path -Path $inputData.filePath + } + + if (-not $result.exists) { + Write-Verbose "Config file doesn't exist" + return $result + } + Write-Verbose "Retrieving settings and last write time from config file" + $fileInfo = Get-Item -Path $inputData.filePath + $settings = Get-Content -Raw -Path $inputData.filePath | ConvertFrom-Json + + $result.settings = $settings + $result.lastWriteTime = $fileInfo.LastWriteTime + + $result +}.ToString() +``` + +The next snippet defines `testScript` for the instance. As with `getScript`, the script must define +a single parameter. Unlike `getScript`, this script must return exactly one boolean value. + +The test script: + +1. Checks whether the configuration file (`input.filePath`) exists. If it doesn't, the script emits + an info message and returns `$false`. +1. Checks whether the configuration file is empty. If it is, the script emits an info message and + returns `$false`. +1. Iterates over the key-value pairs for the desired settings (`input.settings`) to check whether + each of them is in the desired state. If the desired setting isn't defined or is defined with + an incorrect value the script emits an info message and marks the resource as noncompliant but + _doesn't_ stop processing. + + This ensures that the instance can fully report on the desired settings instead of only reporting + the first missing or incorrect setting. +1. Returns `$false` if any setting wasn't in the desired state and otherwise `$true`. + +```powershell +$instance.testScript = { + param($inputData) + + if (-not (Test-Path -Path $inputData.filePath)) { + Write-Verbose "Config file doesn't exist" + return $false + } + + $content = Get-Content -Raw -Path $inputData.filePath + if ([string]::IsNullOrEmpty($content)) { + Write-Verbose "Config file is empty" + return $false + } + + # Initialize variable for result. If any check fails, set to `$false` + # From this point on we want to fully validate state for info messages to + # the user instead of returning early. + $inDesiredState = $true + + # Loop over the desired state to compare to actual settings + $desiredSettings = $inputData.settings.psobject.Properties + $actualSettings = ($content | ConvertFrom-Json).psobject.Properties + foreach ($setting in $desiredSettings) { + $name = $setting.Name + $desiredValue = $setting.Value + $actualSetting = $actualSettings | Where-Object Name -EQ $name + + if ($null -eq $actualSetting) { + Write-Verbose "Missing setting '$name'" + $inDesiredState = $false + continue + } + + if ($actualSetting.Value -ne $setting.Value) { + $message = "Expected setting '{0}' to be ``{1}`` but it is ``{2}``" -f @( + $name + $desiredValue + $actualSetting.Value + ) + Write-Verbose $message + $inDesiredState = $false + } + } + + $inDesiredState +}.ToString() +``` + +To enforce the desired state, define the `setScript` for the instance. The script must define a +single parameter. To make the result for the **Set** operation readable the script emits the same +data structure as `getScript`. + +The script is defined to be idempotent, only modifying the system if needed. It follows these steps: + +1. Define the result object with `filePath` as the `input.filePath` value and `exists` as `true`. +1. Check whether the configuration file exists. If it doesn't, emit a message to indicate that the + instance is creating the file. Then create the file and write the desired state settings + (`input.settings`) into it. Populate the `settings` and `lastWriteTime` fields for the result + object and then use the `return` keyword to emit the result and stop processing the script. +1. If the configuration file does exist retrieve the settings from it. Iterate over the desired + state settings (`input.settings`). If the setting is missing or defined incorrectly, emit an + info message and mark the instance as requiring an update with the `$shouldUpdate` variable. + This ensures that the instance only modifies the file when the settings aren't in the desired + state. + + > [!NOTE] + > This is necessary for version `0.1.0` of this resource. In this release the resource doesn't + > use the `testScript` to determine whether to actually invoke the `setScript`. The resource + > _always_ invokes `setScript` when you invoke the **Set** operation for the resource or on a + > configuration document containing an instance of the resource. + + If the setting is missing, add the desired state setting to the object representing the actual + state. If the setting has the incorrect value, set that property on the same object to the + desired state. This ensures that the resource doesn't inadvertently modify or remove any + settings in the configuration file that the instance isn't managing (the setting is defined in + the file but not `input.settings`). +1. If any of the desired state settings weren't defined in the configuration file or were defined + with invalid values emit a message and update the file with the combined settings. Otherwise + emit a message indicating that the configuration file didn't require any modification. +1. Update the result object to include the final settings and the last write time for the file and + emit the result. + +`setScript` returns the same structured output data as `getScript` regardless of whether the script +creates, updates, or doesn't modify the configuration file. This helps make the output for the +**Set** operation readable and enable directly comparing the `beforeState` and `afterState` fields +in the result. + +```powershell +$instance.setScript = { + param($inputData) + + $filePath = $inputData.filePath + $settings = $inputData.settings + $result = [ordered]@{ + filePath = $filePath + exists = $true + } + + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "Creating config file with specified settings" + $null = New-Item -Path $filePath -Force -Verbose + $json = $settings | ConvertTo-Json + $json | Out-File -FilePath $filePath -Encoding utf8NoBOM + + $result.settings = $settings + $result.lastWriteTime = Get-Item -Path $filePath | + Select-Object -ExpandProperty LastWriteTime + + return $result + } + + $content = Get-Content -Raw -Path $filePath + $actualSettings = $content | ConvertFrom-Json + $shouldUpdate = $false + # Iterate over defined settings, updating the actual settings as needed. + # Don't remove any non-managed settings, only enforce specified settings. + # Set shouldUpdate to $true if any changes are needed, but wait to write + # to the file until all changes are processed to avoid multiple writes. + foreach ($setting in $settings.psobject.Properties) { + $name = $setting.Name + $value = $setting.Value + Write-Verbose "Processing setting '$name' with desired value ``$value``" + $actual = $actualSettings.psobject.Properties | + Where-Object Name -EQ $name | + Select-Object -First 1 + + if ($null -eq $actual) { + Write-Verbose "Adding setting '$name' as ``$value``" + + $shouldUpdate = $true + $memberParams = @{ + InputObject = $actualSettings + MemberType = 'NoteProperty' + Name = $name + Value = $value + } + Add-Member @memberParams + } elseif ($value -eq $actual.Value) { + Write-Verbose "Setting '$name' is already set to ``$value``" + } else { + $message = "Changing setting '{0}' from ``{1}`` to ``{2}``" -f @( + $name + $actual.Value + $value + ) + Write-Verbose $message + + $shouldUpdate = $true + $actualSettings.$name = $value + } + } + + if ($shouldUpdate) { + Write-Verbose "Updating config file with new settings" + $json = $actualSettings | ConvertTo-Json + $json | Out-File -FilePath $filePath -Encoding utf8NoBOM + } else { + Write-Verbose "Config file is already in the desired state. No update needed." + } + + $result.settings = $actualSettings + $result.lastWriteTime = Get-Item -Path $filePath | + Select-Object -ExpandProperty LastWriteTime + + $result +}.ToString() +``` + +With the instance fully defined, invoke the **Get** operation to ensure that returning the actual +state works as expected: + +```powershell +dsc --trace-level info resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Invoking get 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Config file doesn't exist +actualState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: false +``` + +The output shows that the configuration file doesn't exist. + +Next, invoke the **Set** operation to create the file: + +```powershell +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Config file doesn't exist + INFO PID : Creating config file with specified settings + INFO PID : Performing the operation "Create File" on target "Destination: C:\Users\\AppData\Local\Temp\dsc\examples\WindowsPowerShellScript\output.json". + INFO diff: key 'updateAutomatically' is not an object + INFO diff: key 'updateFrequency' is not an object + INFO diff: key '_exist' is not an object + INFO diff: key 'lastWriteTime' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: false +afterState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +changedProperties: +- output +``` + +The emitted messages show that the configuration file doesn't exist and the resource is creating +it. The `beforeState` is populated by the `getScript` and shows that the file doesn't exist. The +`afterState` then shows that the instance created the file with the expected settings and includes +the last write time. + +Invoking the **Set** operation again shows that the defined instance is idempotent: + +```powershell +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Retrieving settings and last write time from config file + INFO PID : Processing setting 'updateAutomatically' with desired value `True` + INFO PID : Setting 'updateAutomatically' is already set to `True` + INFO PID : Processing setting 'updateFrequency' with desired value `30` + INFO PID : Setting 'updateFrequency' is already set to `30` + INFO PID : Config file is already in the desired state. No update needed. +beforeState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +afterState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +changedProperties: [] +``` + +The output in `beforeState` and `afterState` is identical and `changedProperties` is an empty +array. The emitted messages clarify that the instance checked each setting in the configuration +file and found them compliant to the desired state. + +Finally, update `input.settings` by: + +- Removing `updateAutomatically` +- Updating `updateFrequency` to `45` +- Adding `logLevel` as `info` + +Then invoke the resource again to see how the instance updates the configuration file. + +```powershell +$instance.input.settings.Remove('updateAutomatically') +$instance.input.settings.updateFrequency = 45 +$instance.input.settings.logLevel = 'info' + +dsc --trace-level info resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input ( + ConvertTo-Json -InputObject $instance +) +``` + +```Output + INFO Trace-level is Info + INFO Discovering 'Extension' using filter: * + INFO Discovering 'Resource' using filter: * + INFO No results returned for discovery extension 'Microsoft.PowerShell/Discover' + INFO Getting current state for set by invoking get on 'Microsoft.DSC.Transitional/WindowsPowerShellScript' using 'powershell' + INFO PID : Retrieving settings and last write time from config file + INFO PID : Processing setting 'updateFrequency' with desired value `45` + INFO PID : Changing setting 'updateFrequency' from `30` to `45` + INFO PID : Processing setting 'logLevel' with desired value `info` + INFO PID : Adding setting 'logLevel' as `info` + INFO PID : Updating config file with new settings + INFO diff: key 'logLevel' missing + INFO diff: actual array missing expected item + INFO diff: arrays differ for 'output' +beforeState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 30 + lastWriteTime: 2026-06-02T16:45:23.9746807-05:00 +afterState: + output: + - filePath: Temp:/dsc/examples/WindowsPowerShellScript/output.json + exists: true + settings: + updateAutomatically: true + updateFrequency: 45 + logLevel: info + lastWriteTime: 2026-06-02T16:53:39.2138244-05:00 +changedProperties: +- output +``` + +The emitted messages indicate that the instance only checked the `updateFrequency` and `logLevel` +settings - it didn't enforce `updateAutomatically`. The messages show that the instance updated +`updateFrequency` from `30` to `45` and added the missing `logLevel` setting. + +The result object again shows how `beforeState` differs from `afterState`, confirming that the +instance did modify system state. + + +[01]: ../index.md +[02]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.6#success-stream +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-output +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-output#-noenumerate +[05]: ../index.md#emitting-messages +[06]: ./invoke-with-messaging.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md deleted file mode 100644 index 54f39c2dc..000000000 --- a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/manage-windows-service-state-with-powershell.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -description: > - Example showing how to use the Microsoft.DSC.Transitional/WindowsPowerShellScript resource - to get, test, and set Windows service state using inline Windows PowerShell 5.1 scripts. -ms.date: 05/10/2026 -ms.topic: reference -title: Manage Windows service state with PowerShell ---- - -# Manage Windows service state with PowerShell - -> [!IMPORTANT] -> This example is intended to illustrate the capabilities and patterns of the -> `Microsoft.DSC.Transitional/WindowsPowerShellScript` resource, not as a recommended approach for -> managing Windows services. DSC ships the [`Microsoft.Windows/Service`][02] resource specifically -> for this purpose. Use `Microsoft.Windows/Service` instead — it provides a dedicated schema, -> better error messages, and does not require you to write inline scripts. - -This example shows how to use Windows PowerShell 5.1 scripts to get the status of a Windows -service, test whether it is running, and start or stop it. - -## Get service status - -The following snippet uses the [dsc resource get][03] command to retrieve the current status of the -**Print Spooler** service. - -```powershell -$instance = @' -getScript: | - param($inputObj) - $svc = Get-Service -Name $inputObj.name - [PSCustomObject]@{ - name = $svc.Name - displayName = $svc.DisplayName - status = $svc.Status.ToString() - startType = $svc.StartType.ToString() - } -input: - name: spooler -'@ - -dsc resource get --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance -``` - -```yaml -actualState: - output: - - name: spooler - displayName: Print Spooler - status: Running - startType: Automatic -``` - -## Test service state - -The following snippet uses the [dsc resource test][04] command to check whether the **Print -Spooler** service is running. The `testScript` must return exactly one boolean value. - -```powershell -$instance = @' -testScript: | - param($inputObj) - $svc = Get-Service -Name $inputObj.name - $svc.Status -eq 'Running' -input: - name: spooler -'@ - -dsc resource test --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance -``` - -```yaml -actualState: - _inDesiredState: true -inDesiredState: true -differingProperties: [] -``` - -## Set service state - -The following snippet uses the [dsc resource set][05] command to ensure the **Print Spooler** -service is running. The `getScript` captures `beforeState` and the `setScript` starts the service -if it is not already running, then captures `afterState`. - -```powershell -$instance = @' -getScript: | - param($inputObj) - $svc = Get-Service -Name $inputObj.name - [PSCustomObject]@{ - name = $svc.Name - status = $svc.Status.ToString() - } -setScript: | - param($inputObj) - $svc = Get-Service -Name $inputObj.name - if ($svc.Status -ne 'Running') { - Start-Service -Name $inputObj.name - } - $svc.Refresh() - [PSCustomObject]@{ - name = $svc.Name - status = $svc.Status.ToString() - } -input: - name: spooler -'@ - -dsc resource set --resource Microsoft.DSC.Transitional/WindowsPowerShellScript --input $instance -``` - -```yaml -beforeState: - output: - - name: spooler - status: Stopped -afterState: - output: - - name: spooler - status: Running -``` - -## Using a configuration document - -The following configuration document ensures the **Print Spooler** service is running and the -**Fax** service is stopped. Use the [dsc config set][01] command to enforce it. - -```yaml -# services.config.dsc.yaml -$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: - - name: ensureSpoolerRunning - type: Microsoft.DSC.Transitional/WindowsPowerShellScript - properties: - getScript: | - param($inputObj) - [PSCustomObject]@{ name = $inputObj.name; status = (Get-Service $inputObj.name).Status.ToString() } - testScript: | - param($inputObj) - (Get-Service -Name $inputObj.name).Status -eq 'Running' - setScript: | - param($inputObj) - Start-Service -Name $inputObj.name - input: - name: spooler - - - name: ensureFaxStopped - type: Microsoft.DSC.Transitional/WindowsPowerShellScript - properties: - getScript: | - param($inputObj) - [PSCustomObject]@{ name = $inputObj.name; status = (Get-Service $inputObj.name).Status.ToString() } - testScript: | - param($inputObj) - (Get-Service -Name $inputObj.name).Status -eq 'Stopped' - setScript: | - param($inputObj) - Stop-Service -Name $inputObj.name -Force - input: - name: fax -``` - -```powershell -dsc config set --file services.config.dsc.yaml -``` - -> [!TIP] -> If you find yourself writing patterns like the one above for multiple services, switch to -> [`Microsoft.Windows/Service`][02]. It handles the get/test/set logic for you with a declarative -> schema and requires no inline scripts. -> -> More broadly, before writing any inline script resource, check whether DSC already ships a -> purpose-built resource for what you need. Native resources provide validated schemas, better -> error messages, and safer idempotency guarantees than hand-written scripts. Browse the -> [resource reference][06] to see what is available. - - -[01]: ../../../../../../cli/config/set.md -[02]: ../../../Windows/Service/index.md -[03]: ../../../../../../cli/resource/get.md -[04]: ../../../../../../cli/resource/test.md -[05]: ../../../../../../cli/resource/set.md -[06]: ../../../../../../resources/overview.md diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/winpsscript.config.dsc.yaml b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/winpsscript.config.dsc.yaml new file mode 100644 index 000000000..a246c0d2f --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/examples/winpsscript.config.dsc.yaml @@ -0,0 +1,105 @@ +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.vscode.json + +parameters: + motd: + type: string + defaultValue: "Hello, friend!" + minLength: 1 + maxLength: 100 + +resources: +- type: Microsoft.DSC.Transitional/WindowsPowerShellScript + name: Report processor info + properties: + getScript: |- + $count = [System.Environment]::ProcessorCount + $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture + + [ordered]@{ + processorCount = $count + processorArchitecture = $arch.ToString() + } + setScript: |- + $count = [System.Environment]::ProcessorCount + $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture + + [ordered]@{ + processorCount = $count + processorArchitecture = $arch.ToString() + } +- type: Microsoft.DSC.Transitional/WindowsPowerShellScript + name: Message of the Day + properties: + input: "[parameters('motd')]" + getScript: | + param($motd) + + $filePath = 'Temp:/example.motd' + + Write-Verbose "Checking for MOTD file at '$filePath'" + $result = [ordered]@{ + filePath = $filePath + exists = Test-Path -Path $filePath + } + + if ($result.exists) { + Write-Verbose "MOTD file found at '$filePath', retrieving content and last updated time" + $result.motd = (Get-Content -Path $filePath -Raw).TrimEnd("`r", "`n") + $result.lastUpdated = (Get-Item -Path $filePath).LastWriteTime + } else { + Write-Verbose "MOTD file not found at '$filePath'" + } + + $result + setScript: |- + param($motd) + + $filePath = 'Temp:/example.motd' + $result = [ordered]@{ + filePath = $filePath + exists = $true + motd = $motd + } + + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "MOTD file not found at '$filePath', creating new file" + New-Item -Path $filePath -ItemType File -Force | Out-Null + Write-Verbose "MOTD file created at '$filePath', setting content" + $motd | Set-Content -Path $filePath -Force + } else { + Write-Verbose "MOTD file found at '$filePath', checking content" + $currentMotd = (Get-Content -Path $filePath -Raw).TrimEnd("`r", "`n") + if ($currentMotd -ne $motd) { + Write-Verbose "MOTD content differs from desired value, updating file" + $motd | Set-Content -Path $filePath -Force + } else { + Write-Verbose "MOTD content matches desired value, no update needed" + } + } + + $result.lastUpdated = (Get-Item -Path $filePath).LastWriteTime + + $result + testScript: |- + param($motd) + + $filePath = 'Temp:/example.motd' + + Write-Verbose "Checking for MOTD file at '$filePath'" + if (-not (Test-Path -Path $filePath)) { + Write-Verbose "MOTD file not found at '$filePath'" + return $false + } + + Write-Verbose "MOTD file found at '$filePath', retrieving content" + $currentMotd = Get-Content -Path $filePath -Raw + if ([string]::IsNullOrEmpty($currentMotd)) { + Write-Verbose "MOTD file at '$filePath' is empty" + return $false + } elseif ($currentMotd -ne $motd) { + Write-Verbose "Expected MOTD content '$motd' does not match actual content '$currentMotd'" + return $false + } + + Write-Verbose "MOTD content is the expected value '$motd'" + $true \ No newline at end of file diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md index 6f8e1e03f..06b2f85de 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md @@ -5,6 +5,8 @@ ms.topic: reference title: Microsoft.DSC.Transitional/WindowsPowerShellScript --- + + # Microsoft.DSC.Transitional/WindowsPowerShellScript ## Synopsis @@ -12,9 +14,8 @@ title: Microsoft.DSC.Transitional/WindowsPowerShellScript Enable running Windows PowerShell 5.1 scripts inline. > [!IMPORTANT] -> The `winpsscript` command and `Microsoft.DSC.Transitional/WindowsPowerShellScript` resource -> is intended as a temporary transitional resource while migrating DSCv3 resources for -> your needs. +> The `Microsoft.DSC.Transitional/WindowsPowerShellScript` resource is intended as a temporary +> transitional resource while defining DSC resources for your needs. ## Metadata @@ -49,10 +50,176 @@ been fully transitioned to a dedicated DSC resource. The resource allows you to: -- Define separate PowerShell scripts for get, set, and test operations -- Pass input data to the scripts -- Receive output data from the scripts -- Control the desired state behavior through the `_inDesiredState` property +- Define separate PowerShell scripts for **Get**, **Set**, and **Test** operations. +- Pass input data to the scripts. +- Receive output data from the scripts. +- Control the desired state behavior through the `_inDesiredState` property. + +The properties you define determine how the resource behaves. + +- If you don't define `getScript`, the `actualState` field in **Get** operation results and + `beforeState` field in **Set** operation results is always an empty object (`{}`). +- If you don't define `testScript`, the `inDesiredState` field for **Test** operation results is + always `true`. +- If you don't define `setScript`, the `afterState` field in **Set** operation results is always + an empty object (`{}`). +- If you define `input`, every script property you define _must_ start with a `param()` statement + that defines a single parameter. The value for `input` is _always_ passed to the scripts when the + resource invokes them. + + When the instance _doesn't_ define `input` the script properties must **not** include a `param()` + statement. + +> [!NOTE] +> This resource always invokes the script properties in PowerShell (`pwsh`). To define a resource +> instance with script properties that execute in Windows PowerShell (`powershell.exe`), see +> [`Microsoft.DSC.Transitional/PowerShellScript`][01]. + +### Defining script properties + +For an instance to be functional you must define one or more script properties: + +- Define `getScript` to retrieve actual system state with the **Get** operation or to show how the + instance modified the system during a **Set** operation. +- Define `testScript` to indicate whether the system is in the desired state with the **Test** + operation. + + > [!IMPORTANT] + > Version `0.1.0` of the resource does _not_ invoke the `testScript` to determine whether to + > invoke the `setScript`. The resource always invokes `setScript` for the **Set** operation. + > + > Ensure that you define the `setScript` to be idempotent or include a check before making any + > changes to the system to avoid unnecessary processing and unintended behaviors. + +- Define `setScript` to modify the system with the **Set** operation. You can use this resource to + define an instance that performs a specific task, such as warming a cache or clearing logs, or to + enforce a specific desired state for any number of system components. + + In either case, consider [emitting messages](#emitting-messages) to the user that helps them + understand what the instance is doing during an operation. + + If you are using the resource instance to enforce a specific desired state you should: + + 1. Emit one or more output objects representing the final state of the system components the + instance is modifying. + 1. Define `getScript` to emit the same data structures as output objects representing the actual + state of the system components the instance is managing. + + This ensures that the user can more easily compare the `beforeState` and `afterState` fields of + the **Set** operation result to see how the instance modified the system. + +The following subsections provide more information on input, error handling, output, and emitting +messages from within the script properties. + +#### Handling input + +To pass input to a script, you must: + +1. Define the script property with a `param()` statement that specifies a single parameter. + Omitting the `param()` statement, defining an empty `param()` statement, or defining more than + one parameter all cause the resource to fail. +1. Define the [`input`](#input) property for the resource instance with a non-null value. When you + omit the `input` property or define it with a null value, like `input: null`, the resource + raises an error causing the operation to fail. + +The data bound to the script parameter is the result of using the `ConvertFrom-Json` cmdlet on the +value for the `input` property of the resource instance. + +You can define the script parameter with a type, like `[string[]]` when the script expects the input +as an array of strings. PowerShell's normal parameter binding and type conversion behavior applies +to the script parameter. If the input data can't be converted to the defined type then the script +fails and raises an error indicating that the input data was invalid. + +You can also apply [validation attributes][02] to the parameter to further validate that the input +data is correct for your script. + +For detailed examples of using input data with this resource, see +[Invoke the WindowsPowerShellScript resource with input data][03]. + +#### Handling errors + +This resource invokes the PowerShell scripts with the [`$ErrorActionPreference` variable][04] set +to `Stop`. By default, _any_ error raised by the script, regardless of whether it's terminating, +stops script execution. + +You can control whether script execution continues on an error message in two ways: + +1. Specify the [`-ErrorAction` common parameter][05] for any command you expect to fail. Specify + the value for the parameter as `Continue` to emit the error message or `Ignore` to skip the + error message. In either case, execution will continue after the error. +1. Use a [`try`/`catch` statement][06] to add error handling for errors. When a statement in the + `try` block raises an error, the code in the `catch` block will execute before the code in the + `finally` block (if defined). Unless code in the `catch` or `finally` blocks raises an error, + the script will continue to execute. + +Providing error handling enables you to emit better information for users when something goes wrong +with the script behavior. + +However, even when you provide handling for errors, like using a `try`/`catch` statement or passing +`-ErrorAction Ignore` to a command you expect to fail, the resource considers the operation to have +failed. The resource doesn't populate the `output` property for failed scripts. + +There is no way with the current version of the resource for a script to raise any errors and _not_ +fail. You can only provide better diagnostics for the user in the event of a failure. + +For detailed examples of emitting errors from scripts, see ["Emitting errors"][07] in +[Invoke the WindowsPowerShellScript resource with trace messaging][08]. + +#### Returning output + +Any objects emitted by the script for an operation are converted to JSON with the `ConvertTo-Json` +cmdlet and appended to the `output` property array returned by the resource. The ordering of the +items in `output` is the same that they were emitted by the script. + +You can emit any number of items. You don't need to use any specific PowerShell cmdlet to emit +output for this resource. Any output from a PowerShell statement that isn't redirected or captured +as a variable is automatically included in the output. + +You can prevent statements from emitting output by assigning them to `$null`. For example, if your +script uses the `New-Item` cmdlet to create a file, the output for that command is emitted from +your script by default. To avoid emitting that data, you could use the following snippet: + +```powershell +$null = New-Item -Path $filePath +``` + +To provide more readable results to users, consider only emitting a single structured object from +both `getScript` and `setScript`. Emitting an object with descriptive property names makes it +easier to compare the `beforeState` and `afterState` fields for a **Set** operation result. Using +the same data structure also enables DSC to correctly determine the `changedProperties` field for +the **Set** operation result. If the output from `getScript` and `setScript` are identical then +`changedProperties` is an empty array. + +For `testScript`, be sure to _only_ and _always_ emit a single boolean value (`$true` or `$false`). +If `testScript` emits any non-boolean value, more than one boolean value, or no values at all then +the resource considers the operation to have failed and raises an error. + +For comprehensive examples showing how to emit and control output from scripts, see +[Invoke the WindowsPowerShellScript resource with output data][09]. + +#### Emitting messages + +The following table maps DSC's tracing levels to PowerShell output streams and `Write-*` cmdlets: + +| DSC trace level | PowerShell stream | PowerShell cmdlets | +|:---------------:|:-----------------:|:-----------------------------:| +| - | Success | `Write-Output` | +| `error` | Error | `Write-Error` | +| `warn` | Warning | `Write-Warning` | +| `info` | Verbose | `Write-Verbose`, `Write-Host` | +| `debug` | Debug | `Write-Debug` | +| `trace` | Information | `Write-Information` | + +> [!IMPORTANT] +> Remember that _any_ error emitted from the script causes the resource and DSC to consider the +> script execution to have failed, even when the script continued after an error. + +For comprehensive examples of emitting messages from scripts, see +[Invoke the WindowsPowerShellScript resource with trace messaging][08]. + +## Requirements + +- The resource is only usable on a Windows system. ## Capabilities @@ -62,12 +229,18 @@ The resource has the following capabilities: - `set` - You can use the resource to enforce the desired state for an instance. - `test` - You can use the resource to test whether an instance is in the desired state. -This resource implements its own test functionality through the `testScript` property. For more information about resource capabilities, see [DSC resource capabilities][00]. ## Examples -1. [Manage Windows service state with PowerShell][01] - Shows how to manage Windows services using Windows PowerShell scripts. +1. [Configure a system with the WindowsPowerShellScript resource][10] - Shows how to use this + resource in a configuration document. +1. [Invoke the WindowsPowerShellScript resource with input data][11] - Shows how to pass data to + this resource. +1. [Invoke the WindowsPowerShellScript resource with output data][09] - Shows how to return data + from this resource. +1. [Invoke the WindowsPowerShellScript resource with trace messaging][08] - Shows how to emit DSC + trace messages from this resource. ## Properties @@ -76,12 +249,12 @@ The following list describes the properties for the resource. - **Instance properties:** The following properties are optional. They define the desired state for an instance of the resource. - - [getScript](#getscript) - The Windows PowerShell script to run during the get operation. - - [setScript](#setscript) - The Windows PowerShell script to run during the set operation. - - [testScript](#testscript) - The Windows PowerShell script to run during the test operation. + - [getScript](#getscript) - The Windows PowerShell script to run during the **Get** operation. + - [setScript](#setscript) - The Windows PowerShell script to run during the **Set** operation. + - [testScript](#testscript) - The Windows PowerShell script to run during the **Test** operation. - [input](#input) - Input data to pass to the Windows PowerShell scripts. - [output](#output) - Output data returned from the Windows PowerShell scripts. - - [_inDesiredState](#_indesiredstate) - Indicates whether the resource is in the desired state. + - [_inDesiredState](#_indesiredstate) - Indicates whether the resource instance is in the desired state. ### getScript @@ -97,9 +270,17 @@ IsWriteOnly : false -Defines the Windows PowerShell script to execute during the **Get** operation. This script should return -the current state of the resource. The script can access input data and should return relevant -state information. +Defines the Windows PowerShell script to execute during the **Get** operation. This property is +never returned by the resource. The resource invokes the script this property defines for the +**Get** operation and to populate the `beforeState` for a **Set** operation. + +This script should return the current state of the instance. The script can access input data and +should return relevant state information. _Every_ item the script emits to the PowerShell success +stream is inserted into the [`output`](#output) property. + +When possible, prefer emitting a single structured object to the success stream. This makes reading +the `actualState` for a **Get** operation result and the `beforeState` for a **Set** operation +result easier for users. ### setScript @@ -116,8 +297,14 @@ IsWriteOnly : false Defines the Windows PowerShell script to execute during the **Set** operation. This script should -configure the system to match the desired state. The script can access input data and -should perform the necessary changes to bring the system into compliance. +configure the system to match the desired state. The script can access input data and should +perform the necessary changes to bring the system into compliance. + +If the instance defines the [`getScript`](#getscript) property to return data then this property +_should_ return data in the same order and structure. The result object for the **Set** operation +includes `beforeState` (populated by the output for `getScript`) and `afterState` (populated by the +output for `setScript`). Keeping the output order and structure the same for both scripts enables +easier comparison of the changes in resource state. ### testScript @@ -134,8 +321,20 @@ IsWriteOnly : false Defines the Windows PowerShell script to execute during the **Test** operation. This script should -determine whether the system is in the desired state and return appropriate state information. -The script can access input data and should return state information including the `_inDesiredState` property. +determine whether the system is in the desired state and return appropriate state information. The +script can access input data and should return a single boolean value of `$true` or `$false`. + +The script should _not_ emit any other data for output. Emitting more data than a single boolean +value or emitting a non-boolean value causes the resource to raise an error. + +Instead, [emit messages](#emitting-messages) to indicate how and why the instance is out of the +desired state. + +> [!IMPORTANT] +> In version `0.1.0` for the resource, this script is _only_ invoked for the **Test** operation +> when you use the `dsc config test` or `dsc resource test` commands. When you invoke the **Set** +> operation the resource _always_ invokes the [`setScript`](#setscript) even when `testScript` +> would report that the resource is in the desired state. ### input @@ -151,9 +350,51 @@ IsWriteOnly : false -Defines input data to pass to the Windows PowerShell scripts. This can be any valid JSON data type -including strings, booleans, integers, objects, arrays, or null. The input data is available -to all scripts (get, set, test) and can be used to parameterize script behavior. +Defines input data to pass to the PowerShell scripts. This can be any of the following JSON data +types: + +- `string` +- `boolean` +- `integer` +- `object` +- `array` + +The input data is available to every script property and can be used to parameterize script +behavior. + +When passing input data to a script, always define the `params` keyword with a single named +parameter, like `params($inputData)`. The resource binds the value from the `input` property to +that parameter. + +The value for this property affects how it is passed to the PowerShell scripts for the resource: + +| JSON value type | Bound PowerShell parameter value | +|:---------------:|:--------------------------------:| +| `string` | `[String]` | +| `object` | `[PSCustomObject]` | +| `array` | `[Object[]]` | +| `integer` | `[Int64]` | +| `number` | Invalid † | +| `boolean` | `[Boolean]` | +| `null` | Invalid † | + +> [!NOTE] +> Passing a number with a fractional part, such as `1.23`, or `null` is invalid for the top-level +> value of the `input` field. However, you can pass numbers and `null` values nested as object +> properties or array items. +> +> For example, `input: 1.23` is invalid while `input: {"num": 1.23}` and `input: [1.23]` are valid. +> Similarly, `input: null` is invalid while `input: {nested: null}` and `input: [null]` are both +> valid. + +If you define your scriptblock parameters without providing a type for the input data, like +`params($inputData)`, the type for that parameter is exactly as described in the prior table. You +can also define a type for the parameter, which causes PowerShell to cast the input data to the +given type. For example, `params([string[]]$inputData)` will cast the value for `input` to an array +of strings. + +For comprehensive examples of how to use input data with this resource, see +[Invoke the WindowsPowerShellScript resource with input data][03]. ### output @@ -169,8 +410,31 @@ IsWriteOnly : false -Defines output data returned from the Windows PowerShell scripts. This property contains the results -of script execution and can include any data that the scripts choose to return. +Defines output data returned from the Windows PowerShell scripts. This property contains the +results of script execution and can include any data that the scripts choose to return. + +Every object emitted to the PowerShell success output stream is inserted into the `output` for the +operation in the order that the scriptblock emits those objects. The emitted items are +automatically converted to JSON values by the resource. Don't use the `ConvertTo-Json` cmdlet to +transform the items yourself. + +When emitting objects with nested properties the resource will emit the object up 9 levels deep. +Objects with more deep nesting fail to serialize correctly into JSON. + +Where possible, limit the output data to the value you need. You can use the `Select-Object` cmdlet +to select only the required properties or create a custom object to represent the output data. + +> [!IMPORTANT] +> This resource doesn't populate the `output` property for failed scripts. The resource considers +> a script to have failed when it emits _any_ errors, even when those errors are explicitly handled. +> For more information, see the [Handling errors](#handling-errors) section of this documentation. + +Using the `Write-*` cmdlets to emit messages to PowerShell's other output streams doesn't populate +the `output` property. Instead, those messages are surfaced through DSC's tracing. For more +information, see the [Emitting messages](#emitting-messages) section of this documentation. + +For comprehensive examples of how to return output data with this resource, see +[Invoke the WindowsPowerShellScript resource with output data][09]. ### _inDesiredState @@ -187,9 +451,13 @@ DefaultValue : null -Indicates whether the resource is in the desired state. This property is typically set by -the `testScript` and used by DSC to determine whether the `setScript` needs to be executed. -When `null` (default), DSC will rely on the test script logic to determine state. +Indicates whether the resource is in the desired state. This property is only returned when a +caller invokes the **Test** operation for the resource. The value of this property depends on +whether the resource defines the [`testScript](#testscript) property: + +1. When the resource instance defines `testScript`, DSC invokes that script and uses the boolean + result it returns as the value of this property. +1. When the resource instance doesn't define `testScript`, the value is `true`. ## Instance validating schema @@ -238,29 +506,37 @@ Indicates the resource operation completed without errors. ### Exit code 1 -Indicates the PowerShell script execution failed. When the resource returns this -exit code, it also emits an error message with details about the execution failure. +Indicates the Windows PowerShell script execution failed. When the resource returns this exit code, +it also emits an error message with details about the execution failure. ### Exit code 2 -Indicates a PowerShell exception occurred during script execution. When the resource returns this -exit code, it writes the error to the console. +Indicates a Windows PowerShell exception occurred during script execution. When the resource +returns this exit code, it writes the error to the console. ### Exit code 3 -Indicates the script had errors, typically due to missing or invalid input data. -This exit code is commonly returned when required input parameters are not provided -to the PowerShell scripts or when the input data is in an unexpected format. +Indicates the script had errors, typically due to missing or invalid input data. This exit code is +commonly returned when required input parameters are not provided to the PowerShell scripts or when +the input data is in an unexpected format. ## See also -- [Microsoft.DSC.Transitional/PowerShellScript][02] -- [Microsoft.DSC.Transitional/RunCommandOnSet][03] -- [Microsoft.Windows.WindowsPowerShell][04] +- [Microsoft.DSC.Transitional/RunCommandOnSet][12] +- [Microsoft.DSC.Transitional/PowerShellScript][13] [00]: ../../../../concepts/dsc/resource-capabilities.md -[01]: ./examples/manage-windows-service-state-with-powershell.md -[02]: ../PowerShellScript/index.md -[03]: ../RunCommandOnSet/index.md -[04]: ../../../../Microsoft/Windows/WindowsPowerShell/index.md +[01]: ../WindowsPowerShellScript/index.md +[02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#parameter-and-variable-validation-attributes +[03]: ./examples/invoke-with-input-data.md +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#erroractionpreference +[05]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-erroraction +[06]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally +[07]: ./examples/invoke-with-messaging.md#emitting-errors +[08]: ./examples/invoke-with-messaging.md +[09]: ./examples/invoke-with-output-data.md +[10]: ./examples/configure-with-script.md +[11]: ./examples/powershell-script-with-input-output.md +[12]: ../RunCommandOnSet/index.md +[13]: ../PowerShellScript//index.md From fc8d87d10e9ecdfadaca54f36930f70621f767d7 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Sat, 13 Jun 2026 13:22:49 +0200 Subject: [PATCH 3/3] Resolve remark Steve --- .../PowerShellScript/examples/invoke-with-input-data.md | 8 +++++++- .../PowerShellScript/examples/invoke-with-messaging.md | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md index 3422c0fa3..2805ce7ec 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-input-data.md @@ -15,6 +15,11 @@ These examples show how you can pass input data to the [`Microsoft.DSC.Transitional/PowerShellScript` resource][01] and how to bind that data to your script with a [`param()` statement][02]. +> [!NOTE] +> The script parameter can use any valid parameter name, but the script must define exactly one +> parameter. Don't name the parameter `$input`, because `$input` is an automatic variable in +> PowerShell. + ## Input data types The following examples show how data input is bound to the parameters for a defined scriptblock @@ -296,7 +301,8 @@ actualState: Passing input to a script has several requirements: 1. The script property for the resource must use the `param()` statement to define exactly one - parameter. + parameter. The parameter can use any valid parameter name except `$input`, which is an automatic + variable in PowerShell. 1. The `input` property for the resource must be defined with a non-null value. 1. If the `param()` statement defines a type for the input data, the value for the `input` property of the instance must be convertible to that type. diff --git a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md index ed66a48c2..bc170e906 100644 --- a/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md @@ -19,6 +19,13 @@ By default, any errors raised during script execution cause the execution to emi and immediately halt script execution. The following example snippets show how you can provide error details for the user when a script fails. +> [!IMPORTANT] +> The `PowerShellScript` resource runs scripts with `$ErrorActionPreference = 'Stop'` by default. +> Non-terminating errors from cmdlets are treated as terminating errors unless the script or cmdlet +> overrides the error action. Native command failures don't automatically stop script execution; +> script authors should check `$LASTEXITCODE` explicitly unless they enable +> [`$PSNativeCommandUseErrorActionPreference`][12] in PowerShell 7. + ### Emitting an error from a failed cmdlet In this example, the script depends on the `tstoy` command being available on the system. When the @@ -516,3 +523,4 @@ actualState: [09]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-debug [10]: https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-information [11]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-informationaction +[12]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#psnativecommanduseerroractionpreference