passing quoted arguments from batch file to `powershell start` - self-elevation on demand

jez*_*jez 6 quotes powershell escaping batch-file elevated-privileges

I am writing a Windows batch file that automatically escalates itself to administrative permissions, provided the user clicks "Yes" on the User Access Control dialog that appears.

I am using a technique I learned here to detect whether we already have admin rights and another from here to escalate. When appropriate, the following script, let's call it foo.bat, re-launches itself via a powershell-mediated call to runas:

@echo off
net session >NUL 2>NUL
if %ERRORLEVEL% NEQ 0 (
powershell start -wait -verb runas "%~dpfx0" -ArgumentList '%*'
goto :eof
)

echo Now we are running with admin rights
echo First argument is "%~1"
echo Second argument is "%~2"
pause
Run Code Online (Sandbox Code Playgroud)

My problem is with escaping quotes in the -ArgumentList. The code above works fine if I call foo.bat one two from the command prompt, but not if one of the arguments contains a space, for example as in foo.bat one "two three" (where the second argument should be two words, "two three").

If I could even just get the appropriate behavior when I replace %* with static arguments:

powershell start -wait -verb runas "%~dpfx0" -ArgumentList 'one "two three"'
Run Code Online (Sandbox Code Playgroud)

then I could add some lines in foo.bat that compose an appropriately-escaped substitute for %*. However, even on that static example, every escape pattern I have tried so far has either failed (I see Second argument is "two" rather than Second argument is "two three") or caused an error (typically Start-Process: A positional parameter cannot be found that accepts argument 'two'). Drawing on the docs for powershell's Start-Process I have tried all manner of ridiculous combinations of quotes, carets, doubled and tripled quotes, backticks, and commas, but there's some unholy interaction going on between batch-file quoting and powershell quoting, and nothing has worked.

Is this even possible?

mkl*_*nt0 7

  • 您遇到了两个引用地狱(cmd PowerShell)的完美风暴,带有PowerShell 错误(从 PowerShell Core 6.2.0 开始)。

  • 要解决这个bug,该批处理文件不能被再次调用直接,而必须经由重新调用cmd /c

  • LotPings 的有用答案(考虑到了这一点)通常有效,但不适用于以下极端情况:

    • 如果批处理文件的完整路径包含空格(例如,c:\path\to\my batch file.cmd
    • 如果参数碰巧包含以下任何cmd 元字符(甚至在 内"..."):& | < > ^; 例如,one "two & three"
    • 如果 reinvoked-with-admin-privileges 批处理文件依赖于在最初调用它的同一工作目录中执行。

以下解决方案解决了所有这些边缘情况。虽然它远非微不足道,但它应该可以按原样重复使用:

@echo off
setlocal

:: Test whether this invocation is elevated (`net session` only works with elevation).
:: If already running elevated (as admin), continue below.
net session >NUL 2>NUL && goto :elevated

:: If not, reinvoke with elevation.
set args=%*
if defined args set args=%args:^=^^%
if defined args set args=%args:<=^<%
if defined args set args=%args:>=^>%
if defined args set args=%args:&=^&%
if defined args set args=%args:|=^|%
if defined args set "args=%args:"=\"\"%"
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  " Start-Process -Wait -Verb RunAs -FilePath cmd -ArgumentList \"/c \"\" cd /d \"\"%CD%\"\" ^&^& \"\"%~f0\"\" %args% \"\" \" "
exit /b

:elevated
:: =====================================================
:: Now we are running elevated, in the same working dir., with args passed through.
:: YOUR CODE GOES HERE.

echo First argument is "%~1"
echo Second argument is "%~2"

pause
Run Code Online (Sandbox Code Playgroud)