cd Program Files error: positional parameter cannot be found - powershell

PS C:\> cd Program Files
When I give this command, I don't know why, but it is not accepting Program Files. The same command is working perfectly fine in cmd.
This is the error it shows:
Set-Location : A positional parameter cannot be found that accepts argument 'Files'.
At line:1 char:1
+ cd Program Files
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Set-Location], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetLocationCommand

tl;dr
Since your directory name contains spaces, you must quote it, e.g.:
# Note: In PowerShell, 'cd' is an alias of 'Set-Location'
cd 'Program Files'
As stated in Maximilian Burszley's helpful answer, Program Files is parsed as two arguments, because spaces are used to separate command-line arguments.
Your attempt to use cd Program Files may be inspired by cmd.exe (the legacy Command Prompt), where this syntax indeed works; however, even there it conceptually violates the usual rules of argument parsing.
Therefore, you need to use a form of quoting in order to pass a value that contains spaces as a single argument.
You have several options to implement this quoting:
Use '...' around values that are literals, i.e., that should be used verbatim; a '...' string is called a verbatim (single-quoted) string.
Use "..." around values in which you want to embed variable references (e.g., $HOME) or subexpressions (e.g., $(Get-Date); that is, "..." strings perform string interpolation, and they're called expandable (double-quoted) strings.
Use ` to quote (escape) a single character; `, the so-called backtick, is PowerShell's general escape character.
Therefore, you could use any of the following:
cd 'Program Files'
cd "Program Files" # as there are no $-prefixed tokens, same as 'Program Files'
cd Program` Files # `-escape just the space char.
Also, you can use tab-completion to expand the (space-less) prefix of a space-containing path to its full form with quoting applied implicitly.
E.g., if you're in C:\ and you type:
cd Program<tab>
PowerShell automatically completes the command to:
cd '.\Program Files\'
Note how the Program Files (along with .\ to refer to the current dir. and a trailing \ to indicate a dir.) was automatically single-quoted.
Using wildcard expressions as arguments:
As noted, in PowerShell cd is a built-in alias of the Set-Location cmdlet.
Passing a path positionally - e.g. Set-Location 'C:\Program Files' - implicitly binds it to the -Path parameter; that is it, is equivalent to Set-Location -Path 'C:\Program Files'
-Path interprets its argument as a wildcard expression, so that you can do something like Set-Location C:\Win* in order to change to C:\Windows (assuming that the wildcard expression matches only one directory).
The tricky thing is that - unlike in cmd.exe - it isn't just * and ? that have special meaning in PowerShell wildcard expressions, but [ and ] as well (for character-set and character-range expressions such as [abc] and [a-c]), so that Set-Location Foo[1] will not work for changing to a directory literally named Foo[1].
In that case, you must use the -LiteralPath parameter -
Set-Location -LiteralPath Foo[1] - to ensure that the path is interpreted literally (verbatim).

cd C:\Program` Files\
` just escape the space in folder name.

The reason is invalid syntax. Each argument to a powershell command is separated by space, so what you're actually doing is something similar to:
Set-Location -Path Program -Arg2 Files
But Set-Location (aliased: cd) does not have any positional arguments for a second position, so it can't bind it to any parameters and use it in the command, hence the terminating error.
If you want a simpler cd alias, you could do something like this (in your $profile):
function ChangeDirectory {
param(
[Parameter(
Position = 0,
Mandatory,
ValueFromRemainingArguments
)]
[string[]] $Path
)
Set-Location -Path ($Path -join ' ')
}
Set-Alias -Name cd -Value ChangeDirectory
Do note, however, that if you're not specifying a relative path (.\), it will use the root path of your current drive (most likely, C:\). This can be tuned in the function to test for both locations (relative and drive-rooted), but logic for figuring out which one to use if they both exist would be tricky (or can always default to relative).

Use quotes (double or single) to go to the folder or path contains white spaces or special symbols.
Forexample:-
cd "C:\Users\LAPTOP0534\OneDrive - My Content (Active Directory)"
OR
cd 'OneDrive - My Content (Active Directory)\My Folder'
For your case it will be PS C:\> cd "Program Files"

Just run your command prompt as administrator .
See the picture attached.
It will run easily

This is because of the space between the sentences. Enter code here You complete the tab.
Replace PS F: > cd new folder with PS F: > cd "new folder" in the PowerShell. Or look at F: > cd './new folder'. I'm a beginner and you should try this.

Related

escape content of variable for cd

I am using Powershell, I loop through files and create a folder with each file's name to put some data there.
$files = #("[som]video.mkv")
$tmp_location = "."
# to reproduce just do it on files with a filename like [somen_id]restofname.ext
foreach ($file in $files){
$base_input = ([io.fileinfo]$file).basename
# base input may be a file called: [somen_id]restofname.ext
$tmp_dir = "$tmp_location/$base_input"
mkdir $tmp_dir # this line works and the directory is created
# do some stuff first before cd
cd $tmp_dir #this does not work
}
cd fails to handle the tmp_dir variable when it has special characters like [], but mkdir (and even rm) create/delete that directory just fine, which is a very inconsistent behavior in Powershell, I would expect it to either fail for all or work for all!
Any idea how to escape the variable such that it becomes readable to cd
(ofc in real life my array is not just 1 filename written by hand, but this example shows the error too)
Thanks
tl;dr
Use the -LiteralPath parameter to pass a path meant to be interpreted literally (verbatim) to the Set-Location cmdlet, which cd is a built-in alias of; by default (via the positionally implied -Path parameter), it is interpreted as a wildcard expression:
# * "cd" is a built-in alias of "Set-Location"
# * "sl" is the preferable, PowerShell-idiomatic built-in alias
# * Interactively, using PowerShell's elastic syntax,
# you can shorten "-LiteralPath" to "-l", given that no other parameter name
# (currently) starts with "l"
# * In PowerShell (Core) 7+, "-lp" is an official alias.
cd -LiteralPath $tmp_dir
The same applies analogously to rm (unlike what your question implies), which is a built-in alias of Remove-Item.
By contrast, mkdir (which is a wrapper function for New-Item -ItemType Directory) implicitly treats its argument literally.
Read on for details.
As for what you tried:
While cd and mkdir look like their cmd.exe counterparts, they are not:
cd is a built-in alias of the Set-Location cmdlet.
mkdir is a built-in wrapper function for the New-Item cmdlet, with implicitly applied argument -ItemType Directory.
(On Unix-like platforms, mkdir isn't an alias at all and instead refers to the external /bin/mkdir utility).
To learn what command a given name (ultimately) refers to in PowerShell, use the Get-Command cmdlet; e.g. Get-Command cd)
Thus,
cd $tmp_dir
is equivalent to the following call, given that Set-Location binds a positional argument (one not preceded by the target parameter name) to its -Path parameter:
Set-Location -Path $tmp_dir
Most PowerShell cmdlets interpret -Path arguments as wildcard expressions, including - perhaps surprisingly - Set-Location.[1]
Typically, the distinction between -Path and -LiteralPath doesn't matter, given that * and ? - the usual wildcard characters - aren't even allowed in file and directory names (at least on Windows).
However, the problem is that in PowerShell's wildcard language [ and ] also have special meaning (they form character sets - e.g. [abc] and/or character ranges - e.g. [a-c]), which conflicts with literal use of [ and ] in file names, and necessitates the use of -LiteralPath for disambiguation.[2]
By contrast, New-Item's (possibly positionally implied) -Path parameter acts like -LiteralPath, because interpreting a path argument as a wildcard expression in the context of creating a file or directory is pointless. That's why you had no problem creating a directory whose path literally contains [ and ] with mkdir.[3]
Why aliases named for a different shell's commands - such as cd and mkdir - are best avoided in PowerShell:
In a problematic attempt to ease the migration pain for cmd.exe users (and in part also for users of POSIX-compatible shells), PowerShell decided to define built-in aliases and wrapper functions that are named for cmd.exe's internal commands, whereas PowerShell's analogous internal commands, the so-called cmdlets, have very different names.
The latter isn't problematic per se - except that by their use you're missing out on the benefits of PowerShell's standard verb-noun naming convention (e.g., Set-Location), which also extends to how aliases are formed, given that the approved verbs have official alias forms (e.g., s for Set-; therefore, sl is another, but PowerShell-idiomatic Set-Location alias).
What is problematic, however, is that PowerShell commands have very different syntax from cmd.exe's.
If you use names such as cd and mkdir, you'll be tempted to think that, e.g. cd and mkdir function the same way as in cmd.exe - which is only true in the most basic of use cases, however.
It's best to use the true PowerShell command names or - for brevity in interactive use - their PowerShell aliases, which are (reasonably) predictably formed, as discussed above (e.g., sl for Set-Location and ni for New-Item)
[1] By contrast, cmd.exe's cd command does not accept wildcards. With Set-Location, wildcard support is of conceptual necessity limited, because the wildcard expression must resolve to exactly one matching directory.
[2] Alternatively, with -Path you can escape [ and ] as `[ and `], respectively, but this kind of escaping doesn't work consistently as of PowerShell 7.3.0 - see GitHub issue #7999.
[3] This parameter-naming inconsistency is unfortunate; arguably, -LiteralPath should at least be supported as a parameter alias name for -Path. That said, as of PowerShell 7.3.0, there is actually a case where -Path currently is interpreted as a wildcard expression, namely when combined with the -Name parameter, but this should be considered a bug - see GitHub issue #17106.

Powershell script is failing when files with a single quote are passed through script. Alternate batch file is also failing with & and ! characters

This is a deceptively complex issue, but I'll do my best to explain the problem.
I have a simple wrapper script as follows called VSYSCopyPathToClipboard.ps1:
param (
[Parameter(Mandatory,Position = 0)]
[String[]]
$Path,
[Parameter(Mandatory=$false)]
[Switch]
$FilenamesOnly,
[Parameter(Mandatory=$false)]
[Switch]
$Quotes
)
if($FilenamesOnly){
Copy-PathToClipboard -Path $Path -FilenamesOnly
}else{
Copy-PathToClipboard -Path $Path
}
Copy-PathToClipboard is just a function I have available that copies paths/filenames to the clipboard. It's irrelevant to the issue, just assume it does what it says.
The way the wrapper is called is through the Windows right click context menu. This involves creating a key here: HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\.
Mine looks like this:
The command is as follows:
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -Path $files" "%1"
And similarly for the "Copy as Filename":
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -q:' "-c:pwsh -noprofile -windowstyle hidden -Command "C:\Tools\scripts\VSYSCopyPathToClipboard.ps1" -FilenamesOnly -Path $files" "%1"
I am using a tool here called SingleInstanceAccumulator. This allows me to pass multiple selected files to a single instance of PowerShell. If I didn't use this program and ran my command with multiple files selected it would launch multiple instances of PowerShell for each file selected. It's the next best thing to creating your own shell extension and implementing IPC etc.
This has been working great until today when I encountered a file with a single quote in its filename (I.E.testing'video.mov) and the entire script failed. It's failing because the delimiter I'm using with SingleInstanceAccumulator is also a single quote and PowerShell sees no matching quote... thus errors out.
I could fix this if my variables were static by just doubling up the offending single quote, but since my parameters are files I have no opportunity to escape the single quote beyond renaming the file itself ... which is a non-solution.
So now I have no clue how to handle this.
My first try at solving the problem was as such:
Create a batch file and redirect my registry command to it.
Change the SingleInstanceAccumulator delimiter to '/' (All files will be separated by a forward slash.)
Replace the offending single quote to two single quotes.
Replace the '/' delimiters with single quotes.
Finally pass the whole argument list back to Powershell.
This image demonstrates how the above process looks:
This is the batch file's code:
#echo off
setlocal EnableDelayedExpansion
:: This script is needed to escape filenames that have
:: a single quote ('). It's replaced with double single
:: quotes so the filenames don't choke powershell
:: echo %cmdcmdline%
set "fullpath=%*"
echo Before
echo !fullpath!
echo ""
echo After
set fullpath=%fullpath:'=''%
set fullpath=%fullpath:/='%
echo !fullpath!
:: pwsh.exe -noprofile -windowstyle hidden -command "%~dpn0.ps1 -Path !fullpath!
pause
Once I got that wired up I started celebrating ... until I hit a file with an ampersand (&) or an exclamation point (!). Everything fell apart again. I did a whole bunch of google-fu with regards to escaping the & and ! characters but nothing suggested worked at all for me.
If I pass 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov' into my batch file, I get 'C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing back.
It truncates the string at the exact position of the ampersand.
I feel like there has to be a way to solve this, and that I'm missing something stupid. If I echo %cmdcmdline% it shows the full commandline with the &, so it's available somehow with that variable.
In conclusion: I'm sorry for the novel of a post. There is a lot of nuance in what I'm trying to accomplish that needs to be explained. My questions are as follows:
Can I accomplish this with Powershell only and somehow pre-escape single quotes?
Can I accomplish this with a batch file, and somehow pre-escape & and ! (and any other special characters that would cause failure)?
Any help at all would be hugely appreciated.
Edit1:
So in the most hideous and hackish way possible, I managed to solve my problem. But since it's so horrible and I feel horrible for doing it I am still looking for a proper solution.
Basically, to recap, when I do either of these variable assignments:
set "args=%*"
set "args=!%*!"
echo !args!
& and ! characters still break things, and I don't get a full enumeration of my files. Files with & get truncated, etc.
But I noticed when I do:
set "args=!cmdcmdline!"
echo !args!
I get the full commandline call with all special characters retained:
C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" /C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\KylieCan't.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\The !Rodinians - Future Forest !Fantasy - FFF Trailer.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Yelle - Je Veu&x Te Voir.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\Erik&Truffaz.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\my_file'name.mov/,/C:\Users\futur\Desktop\Testing\Video Files\MOV Batch\testing&video.mov/"
So what I did was simply strip out the initial C:\WINDOWS\system32\cmd.exe /c ""C:\Tools\scripts\VSYSCopyPathToClipboardTest.bat" part of the string:
#echo off
setlocal enableDelayedExpansion
set "args=!cmdcmdline!"
set args=!args:C:\WINDOWS\system32\cmd.exe=!
set args=!args: /c ""C:\Tools\scripts\VSYSCopyPathToClipboard.bat" =!
set args=!args:'=''!
set args=!args:/='!
set args=!args:~0,-1!
echo !args!
pwsh.exe -noprofile -noexit -command "%~dpn0.ps1 -Path !args!
And... it works flawlessly. It handles any crazy character I throw at it without needing to escape anything. I know It's totally the most degenerate garbage way of approaching this, but not finding a solution anywhere leads me to desperate measures. :)
I am probably going to make the string removal a bit more universal since it literally breaks if I change the filename.
I am still VERY much open to other solutions should anyone know of a way to accomplish the same thing in a more elegant way.
A fully robust solution based on PowerShell's -Command (-c) CLI parameter that can handle ' characters in paths as well as $ and ` ones requires a fairly elaborate workaround, unfortunately:[1]
Use an aux. cmd.exe call that echoes the $files macro as-is and pipe that to pwsh.exe; make SingleInstanceAccumulator.exe double-quote the individual paths (as it does by default), but use no delimiter (d:"") in order to effectively output a string in the form "<path 1>""<path 2>""...
Make pwsh.exe reference the piped input via the automatic $input variable and split it into an array of individual paths by " (removing empty elements that are a side effect of splitting with -ne ''). The necessity for providing the paths via the pipeline (stdin) is discussed in more detail in this related answer.
The resulting array can safely be passed to your scripts.
Also, enclose the entire -Command (-c) argument passed to pwsh.exe in \"...\" inside the "-c:..." argument.
Note: You may get away without doing this; however, this would result in whitespace normalization, which (however unlikely) would alter a file named, say, foo bar.txt to foo bar.txt (the run of multiple spaces was normalized to a single space).
Escaping " characters as \" is necessary for PowerShell's -Command (-c) CLI parameter to treat them verbatim, as part of the PowerShell code to execute that is seen after initial command-line parsing, during which any unescaped " characters are stripped.
Therefore, the first command stored in the registry should be (adapt the second one analogously; note that there must be no space between the echo $files and the subsequent |):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:"" "-c:cmd /c echo $files| pwsh.exe -noprofile -c \"& 'C:\Tools\scripts\VSYSCopyPathToClipboard.ps1' -Path ($input -split '\\\"' -ne '')\"" "%1"
Note:
If you modified your scripts to accept the paths as individual arguments rather than as an array, a much simpler solution via the -File CLI parameter (rather than -Command (-c)) is possible. This could be as simple as decorating the $Path parameter declaration with [Parameter(ValueFromRemainingArguments)] and then invoking the script without naming the target parameter explicitly (-Path):
"C:\Tools\scripts\BIN\SingleInstanceAccumulator.exe" -d:" " "-c:pwsh.exe -noprofile -File \"C:\Tools\scripts\VSYSCopyPathToClipboard.ps1\" $files" "%1"
Note the use of -d:" " to make SingleInstanceAccumulator.exe space-separate the (double-quoted by default) paths. Since -File passes the pass-through arguments verbatim, there is no concern about what characters the paths are composed of.
Self-contained PowerShell sample code:
The following code defines a Copy Paths to Clipboard shortcut-menu command for all file-system objects (except drives):
No separate .ps1 script is involved; instead, the code passed to -Command / -c directly performs the desired operation (copying the paths passed to the clipboard).
The following helps with troubleshooting:
The full command line with which PowerShell was invoked ([Environment]::CommandLine) is printed, as is the list of paths passed ($file)
-windowstyle hidden is omitted to keep the console window in which the PowerShell commands visible and -noexit is added so as to keep the window open after the command has finished executing.
Prerequisites:
Download and build the SingleInstanceAccumulator project using Visual Studio (using the .NET SDK is possible, but requires extra work).
Place the resulting SingleInstanceAccumulator.exe file in one of the directories listed in your $env:Path environment variable. Alternatively, specify the full path to the executable below.
Note:
reg.exe uses \ as its escape character, which means that \ characters that should become part of the string stored in the registry must be escaped, as \\.
The sad reality as of PowerShell 7.2 is that an extra, manual layer of \-escaping of embedded " characters is required in arguments passed to external programs. This may get fixed in a future version, which may require opt-in. See this answer for details. The code below does this by way of a -replace '"', '\"' operation, which can easily be removed if it should no longer be necessary in a future PowerShell version.
# RUN WITH ELEVATION (AS ADMIN).
# Determine the full path of SingleInstanceAccumulator.exe:
# Note: If it isn't in $env:PATH, specify its full path instead.
$singleInstanceAccumulatorExe = (Get-Command -ErrorAction Stop SingleInstanceAccumulator.exe).Path
# The name of the shortcut-menu command to create for all file-system objects.
$menuCommandName = 'Copy Paths To Clipboard'
# Create the menu command registry key.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName" /f /v "MultiSelectModel" /d "Player"
if ($LASTEXITCODE) { throw }
# Define the command line for it.
# To use *Windows PowerShell* instead, replace "pwsh.exe" with "powershell.exe"
# SEE NOTES ABOVE.
$null = reg.exe add "HKEY_CLASSES_ROOT\AllFilesystemObjects\shell\$menuCommandName\command" /f /ve /t REG_EXPAND_SZ /d (#"
"$singleInstanceAccumulatorExe" -d:"" "-c:cmd /c echo `$files| pwsh.exe -noexit -noprofile -c \\"[Environment]::CommandLine; `$paths = `$input -split [char] 34 -ne ''; `$paths; Set-Clipboard `$paths\\"" "%1"
"# -replace '"', '\"')
if ($LASTEXITCODE) { throw }
Write-Verbose -Verbose "Shortcut menu command '$menuCommandName' successfully set up."
Now you can right-click on multiple files/folders in File Explorer and select Copy Paths to Clipboard in order to copy the full paths of all selected items to the clipboard in a single operation.
[1] An alternative is to use the -f option instead, which causes SingleInstanceAccumulator.exe to write all file paths line by line to an auxiliary text file, and then expands $files to that file's full path. However, this requires the target scripts to be designed accordingly, and it is their responsibility to clean up the auxiliary text file.

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
...
}
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.
Get-ChildItem : Cannot find path 'D:\A' because it does not exist.
The backslash (\) here looks like an escape character to me. Please try with \\

Powershell Remove-Item %1 (Context Menu)

Been fighting with Powershell to do what I need it to do. It might be a simple solution but I've not found it.
If this question is a duplicate, I do apologize, but I couldn't find the answer I was looking for.
TL;DR at the bottom.
So, to the issue I have.
I'm trying to add a PS script to context menu via regedit that deletes a folder + files within, it works great on folders without any spaces in it but when I try to delete a folder with spaces (like "New Folder") it throws an error and closes.
(Tried looking for a pause / sleep command with Remove-Item but no luck, except for long scripts with error handling etc.)
I suspect the error is similar to
Remove-Item : A positional parameter cannot be found that accepts argument 'Folder'.
At line:1 char:12
+ Remove-item <<<< New Folder
+ CategoryInfo : InvalidArgument: (:) [Remove-Item], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
And the code I'm currently using is
cmd /c PowerShell Remove-Item "%1" -recurse -force
I've tried different variations for it to work without luck.
I.E replacing "%1" with "pathAsString", added another "% 1", added wildcard "* *", removed the flags.
Some different variations on the code I've tried:
cmd /c PowerShell Remove-Item \"%1\" -recurse -force
cmd /c PowerShell Remove-Item & "{"%1" -recurse -force"}"
TL;DR
cmd /c PowerShell Remove-Item "%1" -recurse -force ignores folders with white spaces, tried various things.
Usage in Context Menu (via Regedit).
Might be a solution that's obvious but I don't see it.
The following worked for me:
cmd /c PowerShell -command "& {Remove-Item -path '%1' -recurse -force -confirm:$false}"
When using cmd.exe, there is no such thing as a script block. This means the value passed to -command will always be a string. I added the single quotes ('') around %1 to keep your double quotes ("") with your argument. Apparently, this method will fail if you have a single quote (') in your file path. Also the call operator & is not required even though the online documentation says it is.
See PowerShell.exe Command-Line Help for more information on how to use PowerShell.exe.
See About Quoting Rules for more information on how PowerShell single and double quotation marks work.
One of your attempts:
cmd /c PowerShell Remove-Item \"%1\" -recurse -force
actually does work, albeit only if those paths that have embedded spaces don't contain multiple adjacent spaces (see the bottom section for why your first attempt with "%1" instead of \"%1\" didn't work).
To make the command robust (and also more efficient), use the following variation, which does not involve cmd.exe (because it isn't necessary), and the command to pass to PowerShell is enclosed in "..." as a whole with embedded " escaped as \" (as in your attempt):
powershell.exe -noprofile -command "Remove-Item -LiteralPath \"%1\" -Recurse -Force"
PowerShell CLI options used:
-noprofile suppresses loading of the current user's profile ($PROFILE), which would only unnecessarily slow down the command (or, worse, could change its behavior).
-command tells PowerShell that the remaining arguments are a PowerShell command (a piece of source code, as distinct from -file, which is used to invoke a script)
While you don't strictly need to specify -command in Windows PowerShell, because it is implied, that is no longer the case in PowerShell Core, which defaults to -file.
The Remove-Item command passed to PowerShell:
Enclosing the entire PowerShell command in "..." ensures that whitespace inside the command is preserved as-is, which matters with paths that happen to contain runs of multiple spaces, e.g. "c:\Users\jdoe\Invoices<space><space>2018"
\" is how PowerShell requires nested (embedded) " chars. with to be escaped when called from the command line with -command - see below for an explanation.
-LiteralPath ensures that Remove-Item interprets the path literally rather than as a wildcard pattern (as would happen by default, with the implied -Path parameter); while not very likely, paths such as c:\tmp\folder[] would break the command if taken as a wildcard.
Caveats:
Due to (ultimately) using double quotes ("...") to enclose the path (the directory path that File Explorer expands %1 to), it becomes an expandable string, i.e., it is subject to string interpolation, which means that folder paths that contain $ characters (e.g., c:\tmp\$foo) could be misinterpreted.
You can suppress this interpolation by replacing \"%1\" with '%1', i.e., by using a single-quoted string (which PowerShell always treats literally), but the problem is that you then won't be able to use the command on folders whose paths happen to have ' chars. in them (e.g., c:\tmp\o'reilly)
Why just "%1" isn't enough:
cmd /c PowerShell Remove-Item "%1" -recurse -force, your (first) command line (which you're using as a command definition for File Explorer's context menus), is processed as follows:
File Explorer replaces %1 with the absolute path of the file for folder that was right-clicked, as-is - whether or not the path has embedded spaces; e.g.:
c:\Users\jdoe\Invoices 2018.
The resulting command line is then executed; e.g.:
cmd /c PowerShell Remove-Item "c:\Users\jdoe\Invoices 2018" -recurse -force
cmd /c is incidental here (and not needed): It essentially just relays the command line to PowerShell (via its executable, powershell.exe); e.g.:
powershell.exe Remove-Item "c:\Users\jdoe\Invoices 2018" -recurse -force
PowerShell first processes the individual arguments - Remove-Item, "c:\Users\jdoe\Invoices 2018", -recurse, -force by removing enclosing double quotes.
Since the -command option is implied, the quote-stripped arguments are re-joined with a space as the separator and then interpreted as a snippet of PowerShell source code; e.g.:
Remove-Item c:\Users\jdoe\Invoices 2018 -recurse -force
As you can see, the removal of the double quotes resulted in a broken PowerShell command, because the path with spaces now lacks quoting so that c:\Users\jdoe\Invoices 2018 is no longer recognized as a single argument and is instead interpreted as 2 arguments, prefix c:\Users\jdoe\Invoices followed by 2018 as its own, syntactically extraneous argument.
While using \"%1\" instead of "%1" alone would prevent the up-front quote stripping - by telling PowerShell that the " chars. are to be retained during initial argument parsing - additionally enclosing the entire PowerShell command in "..." is necessary to correctly preserve file paths with multiple adjacent spaces (even though such paths may be the result of a typo during folder creation):
Without overall enclosing "...", a \"-quoted path such as C:\Users\jdoe\Invoices<space><space>2018 would result in the following 2 arguments:
\"C:\Users\jdoe\Invoices
2018\"
When PowerShell later re-joins the individual arguments with a single space between them (after having recognized \" as an escaped " to be retained), before interpreting the resulting string as PowerShell code, it sees "C:\Users\jdoe\Invoices<space>2018", i.e., only a single space.
With overall enclosing "...", the entire PowerShell command is parsed as a single argument, with interior whitespace preserved as-is, which avoids the problem.

How to access file paths in PowerShell containing special characters

I am calling PowerShell from within a Java application (through the Windows command prompt) to read various file attributes.
For example,
powershell (Get-Item 'C:\Users\erlpm\Desktop\Temp\s p a c e s.txt').creationTime
I am enclosing the file path in single quotes, because it may contain spaces.
It worked fine, until I encountered a file path containing square brackets, which seem to be interpreted as a wildcard character. I managed to solve it by adding the -literalPath parameter:
powershell (Get-Item -literalpath 'C:\Users\erlpm\Desktop\Temp\brackets[].txt').creationTime
So far, so good... But file paths may also contain single quotes, dollar signs, ampersands, etc., and all these characters seem to have a specific function in PowerShell for which the -literalPath parameter does not seem to work.
I tried enclosing the path with double quotes or escaping with the `character, but that did not solve my problem either :-(
Any suggestions on how to pass a file path to PowerShell which may contain spaces, single quotes, square brackets, ampersands, dollar signs, etc.?
Someone here already showed me how to get it working from within PowerShell, but somehow the answer has been removed(?).
Anyway, I did create a file called $ & ' [].txt.
This works form within PowerShell (needed to escape the &):
Get-Item -LiteralPath "C:\Users\erlpm\Desktop\Temp\`$ & ' [].txt"
Directory: C:\Users\erlpm\Desktop\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2012-08-23 14:22 0 $ & ' [].txt
But when I execute the same PowerShell command through the Windows command prompt, ...
powershell Get-Item -LiteralPath "C:\Users\erlpm\Desktop\Temp\`$ & ' [].txt"
... I get this error:
Ampersand not allowed. The & operator is reserved for future use; use "&" to pass ampersand as a string.
At line:1 char:55 \
Get-Item -LiteralPath C:\Users\erlpm\Desktop\Temp`$ & <<<< ' [].txt \
CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException \
FullyQualifiedErrorId : AmpersandNotAllowed
Using the -command parameter and putting the PowerShell command between {} gives exactly the same error message ...
powershell -command {Get-Item -LiteralPath "C:\Users\erlpm\Desktop\Temp\`$ & ' [].txt"}
This is really a question about cmd.exe string escaping, then. Combining cmd and PowerShell string escaping is a total nightmare. After quite a few attempts, I got your specific example to work:
powershell.exe -nologo -noprofile -command ^&{ dir -LiteralPath ^"""".\`$ & ' [].txt"" }
Directory: C:\Users\latkin
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 8/23/2012 8:46 AM 0 $ & ' [].txt
Totally intuitive, right?
You can spend 15 minutes wrestling with escape sequences every time, or you can try some other approaches.
Put the file name in a text file, and read it out of there.
powershell.exe -command "&{dir -literal (gc .\filename.txt) }"
or
Use a script
powershell.exe -file .\ProcessFiles.ps1 # In processfiles.ps1 you are in fully powershell environment, so escaping is easier
or
Use -EncodedCommand
See powershell.exe -? (last item shown) or http://dmitrysotnikov.wordpress.com/2011/07/06/passing-parameters-to-encodedcommand/
If you can write the path to a temporary .txt file, the following works OK:
(Get-Item -literalpath (gc 'C:\Temp\path.txt')).creationTime
The file # C:\Temp\path.txt contains the path with special characters in it, other than this I think you would have to escape each special character on a per path basis.
In addition to the hack above, it appears PowerShell V3 may help out here with the addition of a new 'magic parameter' language feature. Specifically, see the section headed 'Easier Reuse of Command Lines From Cmd.exe' here and because I hate link-rot, here it is, shamelessly reproduced below:
Easier Reuse of Command Lines From Cmd.exe
The web is full of command lines written for Cmd.exe. These commands
lines work often enough in PowerShell, but when they include certain
characters, e.g. a semicolon (;) a dollar sign ($), or curly braces,
you have to make some changes, probably adding some quotes. This
seemed to be the source of many minor headaches.
To help address this scenario, we added a new way to “escape” the
parsing of command lines. If you use a magic parameter --%, we stop
our normal parsing of your command line and switch to something much
simpler. We don’t match quotes. We don’t stop at semicolon. We
don’t expand PowerShell variables. We do expand environment variables
if you use Cmd.exe syntax (e.g. %TEMP%). Other than that, the
arguments up to the end of the line (or pipe, if you are piping) are
passed as is. Here is an example:
echoargs.exe --% %USERNAME%,this=$something{weird}
Arg 0 is <jason,this=$something{weird}>
This thread is 5 years old so maybe times have changed, but the current answer that worked for me was to use the backtick (`) symbol to escape the special character.
In my case, it was a dollar sign in a directory path that was failing. By putting a backtick before the dollar sign, everything worked.
Before:
$dir = "C:\folder$name\dir" # failed
After:
$dir = "C:\folder`$name\dir" # succeeded