Search code examples
powershellwindows-10windows-11

Strange quoting of whitespaced arguments in PowerShell


I'm trying to develop a quite simple so called SendLet. It consists of a PowerShell script that is run by a Send to shortcut from the context menu in the Windows Explorer. Invoked on arbitrary files or folders these are given as argumets to the script. My approach ist the following:

SendLet.ps1

param (
    [Parameter(ValueFromRemainingArguments=$true)]
    $FilePath
)

Get-ChildItem -Recurse -Path $FilePath | ForEach-Object {
  Write-Host $_
}

Pause

Arbitrary Files

PowerShell 7.3.2
PS C:\Users\user\Desktop\Arbitrary Files> Get-ChildItem

    Directory: C:\Users\user\Desktop\Arbitrary Files

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          29.01.2023    16:44      276246528 Baz qux.AVI
-a---          29.01.2023    16:44        3787047 Foo bar.JPG

PS C:\Users\user\Desktop\Arbitrary Files>

Setup

  1. Save the SendLet script as C:\Users\user\Documents\PowerShell\Scripts\SendLet.ps1
  2. Create a shortcut in shell:sendto (e.g. as C:\Users\user\AppData\Roaming\Microsoft\Windows\SendTo\SendLet.lnk) with the target being C:\Users\user\AppData\Local\Microsoft\WindowsApps\pwsh.exe C:\Users\user\Documents\PowerShell\Scripts\SendLet.ps1.
  3. Send arbitrary files or folders to the script through the Send to context menu.

Run

Runing the above described approach on my Windows 11 22H2 machine results in the follwing output:

C:\Users\user\Desktop\Arbitrary Files\Baz qux.AVI
C:\Users\user\Desktop\Arbitrary Files\Foo bar.JPG
Press Enter to continue...:

Meaning: Two files with absolute path found to process...

However, running the very same approach on my Windows 10 Pro 22H2 machine results in the following error:

Get-ChildItem: D:\Users\user\Documents\PowerShell\Scripts\SendLet.ps1:6
Line |
   6 |  Get-ChildItem -Recurse -Path $FilePath | ForEach-Object {
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find path 'D:\Users\user\Documents\PowerShell\Scripts\Files\' because it does not exist.
Get-ChildItem: D:\Users\user\Documents\PowerShell\Scripts\SendLet.ps1:6
Line |
   6 |  Get-ChildItem -Recurse -Path $FilePath | ForEach-Object {
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find path 'D:\Users\user\Documents\PowerShell\Scripts\Files\' because it does not exist.
Press Enter to continue...:

The path being complained about in the error message is completely wrong and not existent. That's why it does not exist! So the error message itself is correct. But why does the script look for a wrong path argument? I suppose the argument quoting is not working properly.

/UPDATE: The only difference I can see is that the test data is placed in C: drive of my working machine. On my failing machine the test data is placed in D: drive, instead. But why should this matter?

/UPDATE 2: Now, I tested to place the test data on C: drive of my failing machine. Strangely all is working!? So, my SendLet works on files or folder in C: but not D: drive :-(

/UPDATE 3: I tested the both scripts suggested by @zett42 as solution. The outcome is a little bit odd.

Run on the Arbitrary Files folder (metioned above) on my working Windows 11 machine, the second script prints:

Processing file: C:\Users\user\Desktop\Arbitrary Files\Baz qux.AVI
Processing file: C:\Users\user\Desktop\Arbitrary Files\Foo bar.JPG
Press Enter to continue...:

The second script run on the two contained files prints:

Processing file: C:\Users\user\Desktop\Arbitrary Files\Baz qux.AVI
Processing file: C:\Users\user\Desktop\Arbitrary Files\Foo bar.JPG
Press Enter to continue...:

So far so good... And now the same on my problematic Windows 10 Pro machine.

The second script run onto the folder prints:

Processing file: D:\Users\user\Desktop\Arbitrary
Processing file: Files
Press Enter to continue...:

Run directly on the two contained files it prints:

Processing file: D:\Users\user\Desktop\Arbitrary
Processing file: Files\Baz
Processing file: qux.AVI
Processing file: D:\Users\user\Desktop\Arbitrary
Processing file: Files\Foo
Processing file: bar.JPG
Press Enter to continue...:

What is this? What is going on with my Windows 10 Pro machine?

It seems like my Windows 11 machine runs the PowerShell script on the folder with arguments as "C:\Users\user\Desktop\Arbitrary Files". See one quoted argument containing whitespaces.

However, my Windows 10 Pro machine seems to run the script on the folder with arguments as C:\Users\user\Desktop\Arbitrary Files, resulting in two arguments seperated by a whitespace.

Same behaviour seems to apply to the script run directly on the two test files in the folder as quoted arguments:

"C:\Users\user\Desktop\Arbitrary Files\Baz qux.AVI" "C:\Users\user\Desktop\Arbitrary Files\Foo bar.JPG" => Two quoted arguments

... or unquoted arguments:

C:\Users\user\Desktop\Arbitrary Files\Baz qux.AVI C:\Users\user\Desktop\Arbitrary Files\Foo bar.JPG => 6 unquoted arguments

This cannot be a normal behaviour. Or is it?

Maybe there is something I am missing? Any suggestions?

/UPDATE 4: I found the problem -__- which turns out to be very, very annoying... Once again I was inspecting the SendTo-Links on both my machines. The only difference was the absolute path to the interpreter pwsh.exe, because PowerShell was installed differently.

On the good machine the path to pwsh.exe points to C:\Program Files\PowerShell\7\pwsh.exe. So far so good.

On the problematic machine the path pointing to pwsh.exe was C:\Users\user\AppData\Local\Microsoft\WindowsApps\pwsh.exe, instead. So what, I was thinking. It's just a different path. But as I opened the folder in the explorer, I got suspicious because the pwsh.exe file has a size of 0 bytes :-O Then I opened the very same folder in a PowerShell Terminal and typed ls, to reveal something I was not expecting. All the EXE files in there are just links to somewhere else, but I don't know where to? Whar are the arrows -> meaning?

PowerShell 7.3.2
PS C:\Users\user\AppData\Local\Microsoft\WindowsApps> ls

    Directory: C:\Users\user\AppData\Local\Microsoft\WindowsApps

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10.01.2023    18:44                Backup
d----          25.01.2023    10:57                Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
d----          20.12.2022    11:50                Microsoft.MicrosoftEdge_8wekyb3d8bbwe
d----          28.01.2023    23:08                Microsoft.PowerShell_8wekyb3d8bbwe
d----          26.01.2023    17:18                Microsoft.SkypeApp_kzf8qxf38zg5c
d----          28.01.2023    14:19                Microsoft.WindowsTerminal_8wekyb3d8bbwe
d----          10.02.2023    10:50                Microsoft.XboxGamingOverlay_8wekyb3d8bbwe
d----          03.02.2023    15:55                SpotifyAB.SpotifyMusic_zpdnekdrzrea0
la---          10.02.2023    10:50              0 GameBarElevatedFT_Alias.exe ->
la---          20.12.2022    11:50              0 MicrosoftEdge.exe ->
la---          28.01.2023    23:08              0 pwsh.exe ->
la---          25.01.2023    10:57              0 python.exe ->
la---          25.01.2023    10:57              0 python3.exe ->
la---          26.01.2023    17:18              0 Skype.exe ->
la---          03.02.2023    15:55              0 Spotify.exe ->
la---          25.01.2023    10:57              0 WindowsPackageManagerServer.exe ->
la---          25.01.2023    10:57              0 winget.exe ->
la---          28.01.2023    14:19              0 wt.exe ->

PS C:\Users\user\AppData\Local\Microsoft\WindowsApps>

As I conclude, the very issue here is, that ALL THE arguments passed to the interpreter-link are not passed correctly (quotage gets lost) to the PowerShell interpreter!

Setting up my SendLet pointing to the interpreter itself, instead of a link, solved my issue.

Thanks to @all


Solution

  • Here I answer my own question based on /UPDATE 4 (see above) of the like.

    Pay attention to which PowerShell interpreter's EXE file your Send to shortcut (placed inside shell:SendTo) is pointing to. Whether it is the genuine EXE file (i.e. pwsh.exe for version 7) or a symbolic link to that? It turns out, sending file arguments to the symbolic link does not handle paths containing white-spaces, at all!

    One can recognize a symbolic link by its size of 0 bytes in the Windows explorer or by an right arrow -> printed by the ls command in the PowerShell's terminal.

    PowerShell 7.3.2
    PS C:\Users\user\AppData\Local\Microsoft\WindowsApps> ls
    
        Directory: C:\Users\user\AppData\Local\Microsoft\WindowsApps
    
    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    d----          10.01.2023    18:44                Backup
    d----          25.01.2023    10:57                Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
    d----          20.12.2022    11:50                Microsoft.MicrosoftEdge_8wekyb3d8bbwe
    d----          28.01.2023    23:08                Microsoft.PowerShell_8wekyb3d8bbwe
    d----          26.01.2023    17:18                Microsoft.SkypeApp_kzf8qxf38zg5c
    d----          28.01.2023    14:19                Microsoft.WindowsTerminal_8wekyb3d8bbwe
    d----          10.02.2023    10:50                Microsoft.XboxGamingOverlay_8wekyb3d8bbwe
    d----          03.02.2023    15:55                SpotifyAB.SpotifyMusic_zpdnekdrzrea0
    la---          10.02.2023    10:50              0 GameBarElevatedFT_Alias.exe ->
    la---          20.12.2022    11:50              0 MicrosoftEdge.exe ->
    la---          28.01.2023    23:08              0 pwsh.exe ->
    la---          25.01.2023    10:57              0 python.exe ->
    la---          25.01.2023    10:57              0 python3.exe ->
    la---          26.01.2023    17:18              0 Skype.exe ->
    la---          03.02.2023    15:55              0 Spotify.exe ->
    la---          25.01.2023    10:57              0 WindowsPackageManagerServer.exe ->
    la---          25.01.2023    10:57              0 winget.exe ->
    la---          28.01.2023    14:19              0 wt.exe ->
    
    PS C:\Users\user\AppData\Local\Microsoft\WindowsApps>