I am trying to implement the function proposed by
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
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:
Don't try to pass the arguments through via %*
to the :Elevate
subroutine. Instead, capture them in a variable at the top level of the batch file, which the subroutine also sees and can operate on.
The reason that the %*
arguments pass-through via call
must be avoided is that it makes cmd.exe
invariably - and inexplicably - double any ^
characters in the arguments.
cmd.exe
's parsing and its quirks in extensive detail.Here's a streamlined version of your code:
Note the simplification of the :IsAdmin
subroutine to use the exit code from the net session
command to communicate success (0
) vs. failure (non-zero), which enables the ||
operator (as well as &&
) to operate on that exit code.
The non-elevated invocation launches the elevated incarnation and waits for its completion, and then exits. This means that any code after the line that starts with call :IsAdmin ...
is only ever executed in the elevated incarnation.
@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:
Normally, environment variables are inherited by child processes, and, given that all cmd.exe
variables (created with SET
) are invariably environment variables, there's at least a hypothetical concern about "polluting" a child process' environment with auxiliary variables from the caller (in this case, %args%
).
While this could be addressed in general, there is no need to do so here, because elevated child processes - for security reasons - do not inherit their (non-elevated) caller's environment.