Search code examples
powershellget-childitem

Powershell Parameter Seems to Truncate Value


I have a Powershell script that runs fine in VSCode but from the Powershell Prompt, I'm getting an error. Below is the output.

→ C:\WINDOWS\system32› powershell.exe -file 'D:\Source\Repos\Powershell Scripts\SD-Report-Archive.ps1' -sourcePath 'D:\Archives\' -targetPath 'D:\Archives2\'
D:\Archives\
Get-ChildItem : Cannot find path 'D:\A' because it does not exist.
At D:\Source\Repos\Powershell Scripts\SD-Report-Archive.ps1:25 char:14
+     $files = Get-ChildItem -Recurse -File -Path $sourcePath
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (D:\A:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

As you can see on the 2nd line of the output I do a Write-Output of the parameter value that I am sending in and it's correct. When I execute Get-ChildItem it seems to truncate the value to 'D:\A' and I don't know why.

Param(
    [Parameter(Mandatory = $true)]
    [string]$sourcePath,
    [Parameter(Mandatory = $true)]
    [string]$targetPath
)

function Copy-FilesIntoFolders {
    param()

    Write-Output $sourcePath;

    $files = Get-ChildItem -Path $sourcePath -Recurse -File
...
}

Solution

  • On Windows, PowerShell - of necessity - rebuilds the command line in order to invoke external programs.

    Notably, most external programs don't understand single-quoted strings ('...') via their CLI, so after having performed its own parsing, PowerShell re-quotes the resulting (stringified) arguments using double quotes ("...") if it deems that necessary.

    Unfortunately, this re-quoting is broken in several respects:

    • If the argument value doesn't contain spaces, no quoting is applied. Values without spaces but with special characters may therefore break commands, especially when another shell, such as cmd.exe is invoked.

      • E.g., cmd /c echo 'a&b' breaks, because a&b is ultimately passed without quotes, and & has special meaning in cmd.exe
    • If the argument has embedded double quotes (" chars.), the re-quoting does not automatically escape them for syntactically correct embedding inside "..." or unquoted literal use:

      • E.g., foo.exe 'Nat "King" Cole' is translated to foo.exe "Nat "King" Cole" - note the lack of escaping of the inner " chars. - which results in a different string when parsed by most applications, namely Nat King Cole (no double quotes).

      • You have to perform escaping manually, in addition to PowerShell's own escaping requirements, if applicable: foo.exe 'Nat \"King\" Cole' or, with double-quoting,
        foo.exe "Nat \`"King\`" Cole" (sic).

    • Similarly - as in your case - if the argument has spaces and ends in \, that trailing \ is not escaped in the resulting double-quoted string, which breaks the argument syntax:

      • E.g., foo.exe 'a b\' c becomes foo.exe "a b\" c - however, most programs - including PowerShell's own CLI - interpret the \" as an escaped " char. rather than the closing double quote, resulting in misinterpretation of the argument, merging it with the next argument to result in
        a b" c

      • Again you have to perform escaping manually, by doubling the \:
        foo.exe 'a b\\' c

        • Alternatively, if the argument happens to be a directory path whose trailing \ is optional, simply omit the latter.