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..fe7eeb62b --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/configure-with-script.md @@ -0,0 +1,310 @@ +--- +description: > + Example showing how 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="registry.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..bbe4dca00 --- /dev/null +++ b/docs/reference/resources/Microsoft/DSC/Transitional/PowerShellScript/examples/invoke-with-messaging.md @@ -0,0 +1,519 @@ +--- +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 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..01ecd0341 --- /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 in DSC `3.2.0` and 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..868bcd476 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 `psscript` command and `Microsoft.DSC.Transitional/PowerShellScript` resource is intended as +> a temporary transitional resource while defining DSC resources for your needs. ## Metadata @@ -43,19 +44,178 @@ 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. -> [!NOTE] -> This resource requires PowerShell 7 (`pwsh`) to be installed on the system. +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. + +### 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] + > In DSC 3.2.0 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][01] 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][02]. + +#### Handling errors + +This resource invokes the PowerShell scripts with the [`$ErrorActionPreference` variable][03] 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][04] 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][05] 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"][06] in +[Invoke the PowerShellScript resource with trace messaging][07]. + +#### 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][08]. + +#### 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][07]. + +## Requirements + +- Using this adapter requires a supported version of PowerShell (`pwsh`) to be installed on the + system. ## Capabilities @@ -65,13 +225,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][09] - Shows how to use this resource + in a configuration document. +1. [Invoke the PowerShellScript resource with input data][10] - Shows how to pass data to this + resource. +1. [Invoke the PowerShellScript resource with output data][08] - Shows how to return data from this + resource. +1. [Invoke the PowerShellScript resource with trace messaging][07] - Shows how to emit DSC trace + messages from this resource. ## Properties @@ -80,9 +245,9 @@ 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. @@ -101,9 +266,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 +292,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 +317,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 DSC 3.2.0, 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 +346,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][02]. ### output @@ -173,8 +406,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][08]. ### _inDesiredState @@ -191,9 +447,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 +502,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 +512,28 @@ 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][11] +- [Microsoft.DSC.PowerShell][12] +- [Microsoft.Windows.WindowsPowerShell][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]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters#parameter-and-variable-validation-attributes +[02]: ./examples/invoke-with-input-data.md +[03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#erroractionpreference +[04]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters#-erroraction +[05]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally +[06]: ./examples/invoke-with-messaging.md#emitting-errors +[07]: ./examples/invoke-with-messaging.md +[08]: ./examples/invoke-with-output-data.md +[09]: ./examples/configure-with-script.md +[10]: ./examples/powershell-script-with-input-output.md +[11]: ../RunCommandOnSet/index.md +[12]: ../../PowerShell/index.md +[13]: ../../../../Microsoft/Windows/WindowsPowerShell/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