I am creating a dos batch script (cmd.exe) .cmd
which through powershell elevates itself as administrator.
But if it's stored in a folder that contains both spaces and ampersand in its name, it won't work.
I am not an expert so I searched the web, but I could not find a solution or documentation to solve the problem. So I would also like to know how to debug this particular situation and the possible solutions to the problem and where to find the documentation describing the solution or the steps to get there .. (if any).
To recreate the problem: I created a folder under the desktop named "Pie & tea" without quotes. And in the folder I created a script "test.cmd" inside which I put only the essential to reproduce the problem. This is the content of the script:
@echo off
pause
PowerShell start "%~f0" -verb runas
pause
goto: eof
I run the script by double clicking on it (this is the desired mode). An error is shown with this code (I translate from italian with a translator):
Press any key to continue. . .
In row: 1 car: 34
start C:\Users\fposc\Desktop\Pie & tea\test.cmd -verb runas
~
Ampersand (&) not allowed. The & operator is reserved for future use. Use "&" to pass the ampersand as
string.
CategoryInfo: ParserError: (:) [], ParentContainsErrorRecordException
FullyQualifiedErrorId: AmpersandNotAllowed
Press any key to continue. . .
And following some instructions I modified the line of code with powershell by tripling the double quotes:
PowerShell start """%~f0""" -verb runas
But the cmd window opens and after pressing the space the UAC starts and after giving the consent another window appears for a moment but it closes immediately and I could not see any errors.
I have seen on the web any solutions using single quotes '
or the powershell escape character `
or the caret ^
of the dos batch or the --%
parameter of the powershell etc.
But i was unable to resolve.
I do not put the attempts made here because I copied in a stupid way (since I am not familiar with the mixed cmd / powershell operation) and I made numerous random attempts combining the various solutions. Without any positive outcome.
I have also seen solutions that I would like to evaluate, such as:
- change directories and use relative paths.
- prepare string variables and replace characters.
- Use powershell -encodedcommand to pass parameters.
But the preferred solution is the one that explains how to escape characters on the single line of code
PowerShell start "%~f0" -verb runas
and how to get there and the reference documentation.
Some links visited:
VBS Shell.Application and PowerShell Start-Process do not escape ampersands internally
Quoting issues with PowerShell
Issues on windows when the username contains &
Executing PowerShell script via explorer context menu on items containing ampersands in their names
Stackoverflow: batch file deal with Ampersand (&) in folder name
Stackoverflow: How to access file paths in PowerShell containing special characters
Stackoverflow: How to pass strings to powershell from cmd with ampersands in values?
CodePudding user response:
Debugging tips:
If something goes wrong on the PowerShell side, you'll see the error message in the calling
cmd.exe
session; to print the command line that PowerShell itself sees, prepend[Environment]::CommandLine;
to the command(s) being passed.[1]To make PowerShell's
Start-Process
cmdlet (whose built-in alias isstart
) launch the batch file in acmd.exe
process that keeps the session open, invokecmd.exe
explicitly, withcmd /k
, and pass it the batch-file path. Once you're sure everything works as expected, replace/k
with/c
(to create a session that terminates when the batch file terminates).
The primary problem in your case:
Regrettably, if a batch-file path contains
&
characters, a bug incmd.exe
requires manual^
-escaping of the&
as well as spaces, and possibly othercmd.exe
metacharacters, even if the path is enclosed in"..."
as a whole; additionally, it seems that the full batch-file path must be used on invocation (which%~f0
ensures):For instance, to invoke
C:\foo\a & b.cmd
,cmd /k "C:\foo\a & b.cmd"
is not enough - you must usecmd /k "C:\foo\a^ ^&^ b.cmd"
The command below uses a
-replace
operation to perform this^
-escaping in PowerShell code, on the value of%~f0
, whichcmd.exe
expands to the batch file's full path beforehand.For brevity, only
&
are escaped, but the set of chars. inside[...]
can be expanded as needed.
Note:
- Embedded
'...'
quoting is used in the PowerShell commands, so as to avoid having to escape embedded"
quotes, which can get tricky; this assumes that the batch-file path itself contains no'
chars (see below if your paths do contain'
chars.)
@echo off
:: Unless already elevated, relaunch this batch file with elevation.
net session >NUL 2>NUL || (powershell.exe -c "Start-Process -Verb RunAs cmd /k, ('%~f0' -replace '[ &]', '^$0')" & goto :eof)
echo Now running with elevation.
Note:
net session >NUL 2>NUL
is a simple trick to test if the current process is elevated (being run as admin) or not: it only succeeds in elevated sessions (reflected in exit code0
, which the||
and&&
operators implicitly act on).-c
(-Command
) makes it explicit that command(s) are being passed topowershell.exe
; while not strictly necessary, it is a good habit to form, given thatpwsh.exe
, the PowerShell (Core) 7 CLI, now requires it (see the linked CLI documentation below)./k, ...
creates an array of arguments, which are positionally passed to the-ArgumentList
parameter ofStart-Process
- in essence,Start-Process
builds a command line from the executable name,cmd
, and all arguments by string concatenation with spaces behind the scenes - see [this answer]https://stackoverflow.com/a/69427699/45375) to your follow-up question for details.
If your batch-file paths contain '
chars., embedded "..."
quoting must be used:
With
powershell.exe
, the Windows PowerShell CLI, the most robust approach - which is indeed required here - is to use"^""
(sic) to escape each embedded"
, as shown below.(By contrast,
pwsh.exe
, the PowerShell (Core) 7 CLI now fortunately accepts""
.)
net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$0')" & goto :eof)
If you additionally want to support passing arguments to the batch file:
To prevent further quoting headaches, an aux. environment variable, __ARGS
is used in the code below to store all arguments that the batch file received (%*
), which the PowerShell code can then access via $env:__ARGS
on re-invocation.
@echo off
:: Save the arguments received in an aux. environment variable.
:: Note: The space after "=" is required for the PowerShell command
:: to also work if *no* arguments were passed.
set __ARGS= %*
:: Unless already elevated, relaunch this batch file,
:: with arguments passed through.
net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$0'), $env:__ARGS" & goto :eof)
:: Reset the aux. variable.
set __ARGS=
echo Now running with elevation. Arguments received:
echo. %*
[1] A simple example (call from cmd.exe
): powershell -c "[Environment]::CommandLine; '%OS%'"