PSDesiredStateConfiguration
PSDesiredStateConfiguration copied to clipboard
Invoke-DscResource cmdlet throws an exception when using class-based DSC Resource, when module path contains a space
When using a custom class-based DSC resource, the **Invoke-DscResource **cmdlet fails. The issue seems to be in the function Invoke-DscClassBasedResource in the PSDesiredStateConfiguration.psm1 of the PSDesiredStateConfiguration module:
$script = @"
using module $path
Write-Host -Message ([$type]::new | out-string)
return [$type]::new()
"@
When $path is a string with white spaces for example: C:\My Modules\MyModule.psd1 the path gets cut off at the first space and results in C:\My. This leads to an error when trying to use the module, due to the fact that the path is invalid.
Steps to reproduce
- Create a Dsc Resource located in : C:\My Modules\MyDscResource.psm1 or the standard Module path: C:\Program Files\WindowsPowerShell\Modules
enum Ensure
{
Absent
Present
}
[DscResource()]
class FileResource
{
[DscProperty(Key)]
[string]$Path
[DscProperty(Mandatory)]
[Ensure] $Ensure
[DscProperty(Mandatory)]
[string] $SourcePath
[DscProperty(NotConfigurable)]
[Nullable[datetime]] $CreationTime
[void] Set() {
$fileExists = $this.TestFilePath($this.Path)
if ($this.ensure -eq [Ensure]::Present) {
if (-not $fileExists) {
$this.CopyFile()
}
}
else {
if ($fileExists) {
Write-Verbose -Message "Deleting the file $($this.Path)"
Remove-Item -LiteralPath $this.Path -Force
}
}
}
[bool] Test() {
$present = $this.TestFilePath($this.Path)
if ($this.Ensure -eq [Ensure]::Present) {
return $present
} else {
return -not $present
}
}
[FileResource] Get() {
$present = $this.TestFilePath($this.Path)
if ($present) {
$file = Get-ChildItem -LiteralPath $this.Path
$this.CreationTime = $file.CreationTime
$this.Ensure = [Ensure]::Present
}
else {
$this.CreationTime = $null
$this.Ensure = [Ensure]::Absent
}
return $this
}
[bool] TestFilePath([string] $location) {
$present = $true
$item = Get-ChildItem -LiteralPath $location -ea Ignore
if ($item -eq $null) {
$present = $false
} elseif ($item.PSProvider.Name -ne "FileSystem") {
throw "Path $($location) is not a file path."
}
elseif ($item.PSIsContainer) {
throw "Path $($location) is a directory path."
}
return $present
}
[void] CopyFile() {
if (-not $this.TestFilePath($this.SourcePath)) {
throw "SourcePath $($this.SourcePath) is not found."
}
[System.IO.FileInfo] $destFileInfo = new-object System.IO.FileInfo($this.Path)
if (-not $destFileInfo.Directory.Exists) {
Write-Verbose -Message "Creating directory $($destFileInfo.Directory.FullName)"
[System.IO.Directory]::CreateDirectory($destFileInfo.Directory.FullName)
}
if (Test-Path -LiteralPath $this.Path -PathType Container) {
throw "Path $($this.Path) is a directory path"
}
Write-Verbose -Message "Copying $($this.SourcePath) to $($this.Path)"
Copy-Item -LiteralPath $this.SourcePath -Destination $this.Path -Force
}
} # This module defines a class for a DSC "FileResource" provider.
- Invoke the DSC Resource with the Invoke-DscResource cmdlet
$invokeParams = @{
Name = 'FileResource'
Modulename = 'MyDscResource'
Method = 'Test'
Property = @{
Path = "C:\Test\sample data.txt"
SourcePath = "C:\Test\sample data.txt"
Ensure = "Present"
}
Verbose = $true
}
Invoke-DscResource @invokeParams
Expected behavior
Should Invoke the resource correctly and return a valid result.
Actual behavior
Throws various exceptions due to not parsing the module path correctly.
Environment data
$psversiontable info PSVersion | 7.0.3 PSEdition | Core GitCommitId | 7.0.3 OS | Microsoft Windows 10.0.18363 Platform | Win32NT PSCompatibleVersions| {1.0, 2.0, 3.0, 4.0…} PSRemotingProtocolVersion | 2.3 SerializationVersion |1.1.0.1 WSManStackVersion |3.0
/cc @TravisEz13 for comments.
@KristiyanGK Can you run Get-DscResource
with the same module specification you used in Invoke-DscResource
to see if this is a bug in Invoke
or Get
?
@TravisEz13 This is the result I get by running Get-DscResource
.
@KristiyanGK Unfortunately, this lives in a private repo. I think I should transfer this issue to that repo, which will make it invisible to you. After, I do so, feel free to file a new issue here, and mention me and I'll link them. Does that sound fair?
Hopefully, the other repo will become public, at least for issue tracking soon enough.
It appears to me (having done some debugging with VS Code) that (part of) the bug is here:
$listPSModuleFolders = $env:PSModulePath.Split(":")
It's splitting on :
, but $PSModulePath
is ;
-separated on Windows, isn't it?
Yup... it should be using [System.IO.Path]::PathSeparator
for compatibility 😕
I think what I mentioned in my previous comment is actually an unrelated problem. Having done a lot more debugging, I think I've tracked the problem to Invoke-DscClassBasedResource
. This function (among other things) does
$iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2()
$powershell = [PowerShell]::Create($iss)
$script = @"
using module $path
Write-Host -Message ([$type]::new | out-string)
return [$type]::new()
"@
$null= $powershell.AddScript($script)
$dscType=$powershell.Invoke() | Select-object -First 1
Essentially, it's creating an entirely new PowerShell session as if it were a host application, and then using it to execute a script with a using module
(to make importing classes work) of the resource provider module which then returns an instance of the resource class.
From one perspective, it seems like it would be a lot easier to just use a script block rather than this hosting-PowerShell-in-PowerShell craziness, but the actual cause of the problem appears to be that the path is unquoted in the script, so, e.g. C:\Program Files\WindowsPowerShell\Modules\SomeModule
is interpreted as using module C:\Program
and then an expected semicolon or newline parse error happens.
I think the best fix for this is probably the long-overdue engine modifications to make Import-Module
work with classes, and then Invoke-DscClassBasedResource
can be simplified enormously. Until that can be done, though, replacing the whole PS host thing with
$script = @"
using module "$path"
return [$type]::new()
"@
$dscType = & ([ScriptBlock]::Create($script))
or similar should be a good fix. Testing it on my machine seems to indicate that it works. I would put in a PR, but as @TravisEz13 said above, PSDesiredStateConfiguration
is a private repository.
I should point out that, at least in my case, leaving the PowerShell-in-PowerShell thing in place and just fixing the quotes led to a bunch of other problems and crashed my script; it works fine with the script block.
Hi, how is the plan here ? Also run into this
This issue continues in the current version of PSDesiredStateConfiguration
, and makes it impossible to use DSC correctly without locally patching the module for users who e.g. have their Documents folder redirected to OneDrive for Business (as some organizations require, and which, at least in the default configuration for US English, introduces a space into the path to the Documents folder and thus also the Documents\PowerShell\Modules
directory where PowerShellGet installs per-user modules) and have installed DSC resource modules in per-user scope.
Does the PowerShell team plan to address this issue at some point in 2022?
Any Update on this issue ? This is nearly 2 years old jet.