3

My Windows Batch shall be started by the user without administrator privileges. At some step, it shall call itself with elevated privileges. I have learned that this is possible using the PowerShell's runas feature (batch.bat ⭢ PowerShell ⭢ batch.bat). This works like a charm.

Unfortunately, I am not able to receive the exit code from the elevated batch execution. I always get 1, although there is not any error message. I have no idea at which return the exit code gets lost, 1st (batch back to PowerShell) or 2nd (PowerShell back to batch).

I believe, I have tried all of the plenty suggested answers from similar questions, but apparently I am unable to get it going. I need advice.

MVE which should indicate that the elevated batch returns 0:

@echo off
echo param=%~1
openfiles /local >nul 2>&1

IF %ERRORLEVEL% EQU 0 (
    echo elevated, exit 0
    pause
    exit 0
) ELSE (
    echo not elevated. trying to elevate.
    powershell start-process -wait -verb runas '%0' -argumentlist /foo
    echo powershell returned %errorlevel%.
)

Nota bene (edited to eliminate misunderstanding): while the non-elevated call (by the user) does not require any parameter, the elevated call introduces an additional parameter '/foo'. This makes things worse for me because I did not find a solution to not lose this parameter. However, this appears to be a rather unusual use case.

2 Answers 2

2

To solve the argument problem, you could use

powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'

The exit code problem:
The first problem is the line

echo powershell returned %errorlevel%.

This can't work, because it's inside a code block and %errorlevel% will be expanded even before powershell will be called and therefore it is always 1 - the result of openfiles /local ...

But even with delayed expansion, I got always 0, probably because it's the exitcode of the successful runas, that it was able to start your batch.

You could use a work around and store the exitcode in a temporary file

@echo off
setlocal EnableDelayedExpansion
echo param=%*
openfiles /local >nul 2>&1

IF %ERRORLEVEL% EQU 0 (
    echo elevated, exit 13
    pause
    echo 13 > "%temp%\_exitcode.tmp"
    rem *** using 14 here to show that it doesn't be stored in errorlevel
    exit 14
) ELSE (
    echo not elevated. trying to elevate.
    powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
    set /p _exitcode= < "%temp%\_exitcode.tmp"
    del "%temp%\_exitcode.tmp"
    echo powershell returned !_exitcode!, lvl !errorlevel!.
)
Sign up to request clarification or add additional context in comments.

3 Comments

Sorry. I removed the EnableDelayedExpansion to make the example minimal after "finding out" that it had no influence. I noticed I was wrong. Next, I learned from your answer and your post at another question that I found the bbb (batch beginner bug) and need the exclamation mark expansion. However, I do not like the proposed solution to create the temp file for this appears rather hacky to me, but well. It works at least and that's great!
@Twonky You are right, it's only a workaround. I tested it with powershell ... -PassThru but with no better results
Moved _exitcode.tmp to %temp% and guarded the set command with IF EXISTS. Considering this solution good enough! Thanks, you're great!
0

You aren't putting the PowerShell commands to execute in quotes, and you would do well to use the full path as well as include any arguments to the script. A generic way to invoke this, so it could be copied across scripts, your PowerShell invocation should look like so:

powershell -c "if([bool]'%*'){ Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList ('%*' -split '\s+') } else { Start-Process -Wait -Verb runas '%~dpnx0' }"

For your needs above, this could be simplified since you know you have arguments passed into the batch file to process:

powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList '/foo'
  • %~dpnx0 - Automatic batch variable, this is the full path to the current script, including the script name
  • %* - Automatic batch variable, this is all arguments passed into the script.
  • ('%*' -split '\s'): This is a PowerShell expression takes the space-delimited %* variable and splits it on continuous whitespace, returning an array. For simplicity this does have a shortcoming in that it will split on spaces in between double quotes, but the regex can be tuned to account for that if needed.

This answer is worth a read for other automatic batch variables you may find use for in the future.

3 Comments

This could be a comment to optimize the use of %~dpnx0 or better use %~f0. But it doesn't solve the problem at all. Btw ''%~dpnx0 %*'" fails with if any parameter exists
Ah yeah I see my mistake, i'll fix it
Enlightening explanation on variables appreciated! Minor misunderstanding: my use case requires the additional parameter /foo being introduced by the elevated call, so the simplified solution would be powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList /foo" (mind the closing " which was missing in your post). But this is probably only my specialized use case and passing all arguments transparently is the more common case. Unfortunately, your generalized suggestion does not work. Even with EnableDelayedExpansion I get 1 as exit code instead of 0.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.