DSC
DSC copied to clipboard
Multi-string support
Prerequisites
- [X] Write a descriptive title.
- [X] Make sure you are able to repro it on the latest version
- [X] Search the existing issues.
Summary
Problem statement
This morning, I was rambling on my keyboard and looking at some examples to migrate PSDSC configuration documents to DSC. Experimenting with the PSDesiredStateConfiguration/Script resource was like opening a Pandora's box, with odd behaviors emerging out of the shadows especially when using multi-strings.
That brought me to do some investigation and see where things are going wrong.
Investigation
The initial start of my investigation, was using the following document:
# powershell.script.dsc.config.yaml
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
resources:
- type: Microsoft.Windows/WindowsPowerShell
name: Run script
properties:
resources:
- name: Run script
type: PSDesiredStateConfiguration/Script
properties:
Ensure: "Present"
GetScript: |
$testFile = C:\Temp\test.txt
@{result = $(Get-Content $testFile)}
TestScript: |
Throw
SetScript: |
Throw
Running the script, gave me the following error message:
The strange thing while executing the document (dsc config get --path powershell.script.dsc.config.yaml), is that a piece is cut off:
Now, forget about the fact on YAML. Let's dive into the JSON part and see why the error was thrown. What I would have expected, is the following JSON being passed through to Invoke-DscResource:
// Example 1
{"GetScript":"$var = \"C:\\temp\\test.txt\"; @{result = $(Get-Content $var)}","SetScript":"throw","TestScript":"throw","type":"PSDesiredStateConfiguration/Script"}
Unfortunately, that does not happen, nor does the following JSONs are being passed through:
// Example 2
$res = @'
{"GetScript":["$var = \"C:\\temp\\test.txt\", "@{result = $(Get-Content $var)}"],"SetScript":"throw","TestScript":"throw","type":"PSDesiredStateConfiguration/Script"}
'@
// Example 3
$$string = @'
{
"GetScript": [
{
"$var": "C:\\Temp\\test.txt"
},
{
"@{result}": "Get-Content $var"
}
],
"SetScript": "throw",
"TestScript": "throw",
"type": "PSDesiredStateConfiguration/Script"
}
'@
That third one sticks out like a sore thumb to me too.
Thoughts and ideas
That brought me even deeper to look at the adapter code and a train of thoughts on how to potentially look at this.
Firstly, passing in example 2, which looks more naturally to me, only reveals the real error message when you change the following line and add the -ErrorAction Stop parameter:
$invokeResult = Invoke-DscResource -Method $Operation -ModuleName $cachedDscResourceInfo.ModuleName -Name $cachedDscResourceInfo.Name -Property $property -ErrorAction Stop
The error message that is returned:
That still doesn't really say what is happening behind the scenes, so you've to dump out the objects.
Example 2 vs 1:
While example 1 is running smoothly, but example 2 is a breeze to type. It raises the question of why it doesn’t flow the same way when you input YAML through DSC's core engine.
Still, I was curious why the object was a mismatch and dove into the code. Looking at how the property hashtable is built, you can spot the difference and I applied the following:
# morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource
$DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process {
if ($_.Value.GetType().Name -eq 'Object[]')
{
$property[$_.Name] = $_.Value -as [System.String]
}
$property[$_.Name] = $_.Value
}
If you want me to raise a PR for the fix and create Pester tests around it, just let me know.
Steps to reproduce
See problem statement
Expected behavior
Apply multi-strings.
Actual behavior
Multi-strings are not passed through YAML.
Error details
No response
Environment data
Name Value
---- -----
PSVersion 5.1.22621.3880
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.22621.3880
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
Version
dsc 3.0.0-preview.8
Visuals
No response
@michaeltlombardi and @anamnavi Do you mind having a look into this issue?
Running dsc -l trace config get with the example yaml shows:
2024-08-16T03:30:01.339224Z TRACE : dsc_lib::configure: 657: Invoke property expression for resources: [{"name":"Run script","type":"PSDesiredStateConfiguration/Script","properties":{"Ensure":"Present","GetScript":"$testFile = C:\\Temp\\test.txt\n@{result = $(Get-Content $testFile)}\n","TestScript":"Throw\n","SetScript":"Throw\n"}}]
So the YAML is directly converted to JSON which means each line of the script is a newline. I think the PSAdapter could just replace newlines with a semi-colon. There might be some edge cases where this won't work, but we can document this behavior.
Thanks for checking out the issue Steve. If you want me to take a look into it or create documentation around somewhere, just give me a heads-up.
Actually thinking about this further, we can't just automatically assume a multiline string is a script. It's possible there's a PS resource that takes a string and the user wants to preserve the newlines.
Ideally, the script resource would account for this, but we're not going to make a change to that legacy resource. So, it might be better in this case to special case the script resource and also document this behavior so that other PS resource authors handle it correctly within the resource.
Actually, just adding semi colons for a script won't work, for example:
$a = @"
this is text
"@
$a
Did some debugging, looks like the problem might be in the parser which currently isn't expected to handle multi-line strings. Working on a fix.
@SteveL-MSFT Legend. Thanks for checking it out. I was also thinking of another example were you use the > in YAML. Haven't tested that one out.