Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoke-DscResource cmdlet throws an exception when using class-based DSC Resource, when module path contains a space #73

Open
KristiyanGK opened this issue Jul 23, 2020 · 11 comments · May be fixed by #99

Comments

@KristiyanGK
Copy link

KristiyanGK commented Jul 23, 2020

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

  1. 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.
  1. 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.

image

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

@daxian-dbw
Copy link
Member

/cc @TravisEz13 for comments.

@TravisEz13
Copy link
Member

@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?

@KristiyanGK
Copy link
Author

@TravisEz13 This is the result I get by running Get-DscResource .

image

@TravisEz13
Copy link
Member

@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.

@ghost
Copy link

ghost commented Aug 28, 2020

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?

@vexx32
Copy link

vexx32 commented Aug 28, 2020

Yup... it should be using [System.IO.Path]::PathSeparator for compatibility 😕

@ghost
Copy link

ghost commented Sep 4, 2020

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.

@ghost
Copy link

ghost commented Sep 4, 2020

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.

@MaBauMeBad
Copy link

Hi, how is the plan here ? Also run into this

@ghost
Copy link

ghost commented Jan 18, 2022

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?

@MaBauMeBad
Copy link

Any Update on this issue ?
This is nearly 2 years old jet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants