powershellbatch-fileescapingquoteselevated-privileges

Batch file auto-elevation using powershell and not mangling the arguments but problem with invokation


I am trying to implement the function proposed by

mklement0 on this answer

My goal is to implement this as a reusable batch subfunction

But before that I have run into a pattern of inconsistent behaviour

Mostly depending on how the script is called, where it is and where it called from

The script generally works if it is called from a folder and it is also in a folder. However if it is called from the root of a drive, it might fail with an error regarding && pipes.

For documenting the behaviours I have saved the script as the files

i:\Test privilege escalation powershell with arguments.bat

and i:\script\Test privilege escalation powershell with arguments.bat

The exact script is

@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

I have tried called these scripts from several locations c:\ d:\ i:, from a folder, from the drive root. I even tried multiple times and not always gotten the same results.

The full log of what happened can be found here https://pastebin.com/xCEJKE2f I will not copy the most relevant bits here

In a normal cmd.exe console, console is not privileged

C:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
C:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 3 fail to The token '&&' is not a valid statement separator in this version. https://i.imgur.com/5sUHPeB.png
C:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
Fails with The system cannot find the path specified.                            https://i.imgur.com/5sUHPeB.png
 
C:\Users\shodan>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Users\shodan>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Users\shodan>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Users\shodan>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 4 work https://i.imgur.com/DVGpJFI.png
 
d:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
d:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
d:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
all 3 don't work, "The token '&&' is not a valid statement separator in this version." https://i.imgur.com/60NHWLh.png
d:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
does not work The system cannot find the path specified.
 
Later attempt ??? Not the same result ?  d:\ vs D:\ ?!?
D:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All four fail with "The token '&&' is not a valid statement separator in this version."  https://i.imgur.com/c9WcxCp.png
 
d:\share>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
d:\share>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
d:\share>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
all 3 work             https://i.imgur.com/0be7DkQ.png
d:\share>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
The system cannot find the path specified.
 
Later attempt, not the same results ?!?! again  d:\ vs D:\ ?!?
D:\share>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\share>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
D:\share>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\share>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All four work and elevated properly ! https://i.imgur.com/EtfvRyb.png
 
I:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
all don't work, same error, "The token '&&' is not a valid statement separator in this version." https://i.imgur.com/HiJggQ4.png
This result tried twice, second time same result
 
I:\scripts>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
I:\scripts>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
I:\scripts>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 3 work
I:\scripts>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
does not work The system cannot find the path specified.
This result tried twice, second time same result
   
 
Double click on I:\Test privilege escalation powershell with arguments.bat
Does not work, console flashes briefly and disappear too fast to see what the error message was
It should pause, but doesn't !
 
Double clicking on I:\scripts\Test privilege escalation powershell with arguments.bat
works https://i.imgur.com/lFMD2MI.png
 
Drag and dropping a bunch of files onto I:\scripts\Test privilege escalation powershell with arguments.bat
works, dropped files are arguments https://i.imgur.com/nUXgWbA.png https://i.imgur.com/PtMT91d.png
 
Drag and dropping files onto 
https://i.imgur.com/AMMPXzf.png
Does not work, console flashes briefly and disappear too fast to see what the error message was
It should pause, but doesn't !
 
In a PRIVILEGE console
 
C:\Windows\system32>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Windows\system32>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Windows\system32>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\Windows\system32>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 4 work https://i.imgur.com/60owFpJ.png
 
C:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
C:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
C:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 4 work  https://i.imgur.com/sgT3EUx.png
 
D:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
D:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 4 work  https://i.imgur.com/TJyNGy4.png
 
I:\>"i:Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:\Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:\scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
I:\>"i:scripts\Test privilege escalation powershell with arguments.bat" 1 2 3
All 4 work  https://i.imgur.com/S1mxGsF.png

So my question is, why does this script behave differently based on where it is called from, where it is and how (i:\filename.bat vs i:filename.bat). Also this case where everything seemed the same, but the result was not the same.

Lastly, my goal is to turn this script into a re-usable subfunction and it should be in the following format.

@echo off
 
Call :IsAdmin IsAdmin
if not %IsAdmin%==true Call :Elevate %*
 
if %IsAdmin%==true echo Admin privileges found
if %IsAdmin%==true echo First argument is "%~1"
if %IsAdmin%==true echo Second argument is "%~2"
if %IsAdmin%==true echo third argument is "%~3"
if %IsAdmin%==true echo fourth argument is "%~4"
if %IsAdmin%==true echo fifth argument is "%~5"
if %IsAdmin%==true echo sixth argument is "%~6"
if %IsAdmin%==true echo seventh argument is "%~7"
if %IsAdmin%==true echo eigth argument is "%~8"
if %IsAdmin%==true echo nineth argument is "%~9"
if %IsAdmin%==true pause
 
:END
GoTo :EOF
 
:IsAdmin
  set %1=false
  net session >nul 2>&1
  if %ERRORLEVEL% == 0 set %1=true
GoTo :EOF
 
:Elevate
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

Solution

  • The problem occurs when the batch file happens to be called from a drive's root directory, because %CD% then expands to a value that ends in \, which breaks PowerShell's command-line parsing, because it interferes with the escaped " (\") that follows it.

    A pragmatic workaround is to simply append a space before the following \", which still works, because cmd.exe ignores trailing spaces in file-system paths:

    In other words: change %CD%\" to %CD% \"

    I've also updated the linked answer accordingly.


    As for your desire to modularize the code with subroutines that you call with call, including placing the elevating powershell.exe call in an Elevate subroutine:

    Here's a streamlined version of your code:

    @echo off & setlocal
     
    :: If not already running elevated, self-elevate and exit.
    :: Note: A helper variable must be used to capture %*,
    ::       because passing %* to a subroutine doubles ^ chars.
    call :IsAdmin || set args=%* && (call :Elevate & exit /b)
    
    :: Getting here means this is (now) an elevated session.
    
    ::Print the arguments received.
    echo Now running elevated; arguments received:
    echo.  %*
    pause
    
    :: End of the main body. 
    goto :EOF
    
    :: === Helper subroutines
    
    :: Test if this session is elevated.
    :: `net session` only succeeds and therefore reports exit code 0 
    :: in an elevated session.
    :IsAdmin
      net session >NUL 2>&1
    goto :EOF
     
     :: Perform self-elevation, passing all arguments through.
    :Elevate
      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:"=\"\"%"
      :: Note: 
      ::  * To not make the non-elevated incarnation wait for the
      ::    elevated one to complete, remove -Wait      
      ::  * To keep the elevated session open until explicitly exited by the user,
      ::    use /k instead of /c
      powershell -NoProfile -ExecutionPolicy Bypass -Command ^
        " Start-Process -Wait -Verb RunAs -FilePath cmd -ArgumentList \"/c \"\" cd /d \"\"%CD% \"\" ^&^& \"\"%~f0\"\" %args% \"\" \" "
    goto :EOF
    

    As an aside: