277

I want my batch file to only run elevated. If not elevated, provide an option for the user to relaunch batch as elevated.

I'm writing a batch file to set a system variable, copy two files to a Program Files location, and start a driver installer. If a Windows user (UAC enabled *) and even if they are a local admin) runs it without right-clicking and selecting "Run as Administrator", they will get 'Access Denied' copying the two files and writing the system variable.

I would like to use a command to automatically restart the batch as elevated if the user is in fact an administrator. Otherwise, if they are not an administrator, I want to tell them that they need administrator privileges to run the batch file. I'm using xcopy to copy the files and REG ADD to write the system variable. I'm using those commands to deal with possible Windows XP machines. I've found similar questions on this topic, but nothing that deals with relaunching a batch file as elevated.


*) UAC was introduced with Windows 7 / Windows Vista. Older Windows versions don't have this feature.

8
  • 2
    Check out what I've posted - you don't need any external tool, the script automatically checks for admin rights and auto-elevates itself if required. Commented Sep 4, 2012 at 13:34
  • 2
    Please consider if Matt's answer would be the ticked one? Seems so to me. Commented Mar 31, 2015 at 4:56
  • 2
    possible duplicate of How to request Administrator access inside a batch file Commented Aug 11, 2015 at 18:24
  • 1
    Please regard the new Windows 10 hints in the comments section of the batch script I have posted. Commented Oct 4, 2015 at 14:54
  • 5
    From cmd: @powershell Start-Process cmd -Verb runas. From Powershell just drop @powershell. This starts cmd with elevated rights. Commented Nov 1, 2015 at 18:42

19 Answers 19

382

There is an easy way without the need to use an external tool - it runs fine with Windows 7, 8, 8.1, 10 and 11 and is backwards-compatible too (Windows XP doesn't have any UAC, thus elevation is not needed).

Check out this code (I was inspired by the code by NIronwolf posted in the thread Batch File - "Access Denied" On Windows 7? 1), but I've improved it - in my version there isn't any directory created and removed to check for administrator privileges):

@echo off
REM :::::::::::::::::::::::::::::::::::::::::
REM Elevate.cmd - Version 9
REM Automatically check & get admin rights
REM see "https://stackoverflow.com/a/12264592/1016343" for description
REM :::::::::::::::::::::::::::::::::::::::::
 
 CLS
 ECHO.
 ECHO =============================
 ECHO Running Admin shell
 ECHO =============================

:init
 setlocal DisableDelayedExpansion
 set cmdInvoke=1
 set winSysFolder=System32
 set "batchPath=%~dpnx0"
 rem this works also from cmd shell, other than %~0
 for %%k in (%0) do set batchName=%%~nk
 set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
 setlocal EnableDelayedExpansion

:checkPrivileges
  whoami /groups /nh | find "S-1-16-12288" > nul
  if '%errorlevel%' == '0' ( goto checkPrivileges2 ) else ( goto getPrivileges )


:checkPrivileges2
  net session 1>nul 2>NUL
  if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )

:getPrivileges
  if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
  ECHO.
  ECHO **************************************
  ECHO Invoking UAC for Privilege Escalation
  ECHO **************************************

  ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
  ECHO args = "ELEV " >> "%vbsGetPrivileges%"
  ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
  ECHO args = args ^& strArg ^& " "  >> "%vbsGetPrivileges%"
  ECHO Next >> "%vbsGetPrivileges%"
  
  if '%cmdInvoke%'=='1' goto InvokeCmd 

  ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
  goto ExecElevation

:InvokeCmd
  ECHO args = "/c """ + "!batchPath!" + """ " + args >> "%vbsGetPrivileges%"
  ECHO UAC.ShellExecute "%SystemRoot%\%winSysFolder%\cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"

:ExecElevation
 "%SystemRoot%\%winSysFolder%\WScript.exe" "%vbsGetPrivileges%" %*
 exit /B

:gotPrivileges
 setlocal & cd /d %~dp0
 if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul  &  shift /1)

 REM :::::::::::::::::::::::::
 REM START
 REM :::::::::::::::::::::::::
 REM Run shell as admin (example) - put here code as you like
 ECHO %batchName% Arguments: P1=%1 P2=%2 P3=%3 P4=%4 P5=%5 P6=%6 P7=%7 P8=%8 P9=%9
 cmd /k %1 %2 %3 %4 %5 %6 %7 %8 %9

The script takes advantage of the fact that whoami combined with find checks for administrator membership (built in group S-1-16-12288 in Windows) and returns errorlevel 1 if you don't have it. If the group is found, a 2nd check is done by using net session, which also returns errorlevel 1 in case of missing privileges (to cover some edge cases).

If necessary, the elevation is achieved by creating a script which re-launches the batch file to obtain privileges. This causes Windows to present the UAC dialog and asks you for the administrator account and password.

I have tested it with Windows 7, 8, 8.1, 10, 11 - it works fine for all. The advantage is, after the start point you can place anything that requires system administrator privileges, for example, if you intend to re-install and re-run a Windows service for debugging purposes (assumed that mypackage.msi is a service installer package):

msiexec /passive /x mypackage.msi
msiexec /passive /i mypackage.msi
net start myservice

Without this privilege elevating script, UAC would ask you three times for your administrator user and password - now you're asked only once at the beginning, and only if required.


If your script just needs to show an error message and exit if there aren't any administrator privileges instead of auto-elevating, this is even simpler: You can achieve this by adding the following at the beginning of your script:

@ECHO OFF & CLS & ECHO.
whoami /groups /nh | find "S-1-16-12288" > nul & 
  IF ERRORLEVEL 1 (ECHO You must right-click and select &
  ECHO "RUN AS ADMINISTRATOR"  to run this batch. Exiting... & ECHO. &
  TIMEOUT /t 10 & EXIT /D)
net session 1>nul 2>nul & 
  IF ERRORLEVEL 1 (ECHO You must right-click and select &
  ECHO "RUN AS ADMINISTRATOR"  to run this batch. Exiting... & ECHO. &
  TIMEOUT /t 10 & EXIT /D)
REM ... proceed here with admin rights ...

This way, the user has to right-click and select "Run as administrator". The script will proceed after the REM statement if it detects administrator rights, otherwise exit with an error. If you don't require the PAUSE, just remove it. Important: whoami [...] EXIT /D) and also net session [...] EXIT /D) must be on the same line. It is displayed here in multiple lines for better readability!


On some machines, I've encountered issues, which are solved in the new version above already. One was due to different double quote handling, and the other issue was due to the fact that UAC was disabled (set to lowest level) on a Windows 7 machine, hence the script calls itself again and again.

I have fixed this now by stripping the quotes in the path and re-adding them later, and I've added an extra parameter which is added when the script re-launches with elevated rights.

The double quotes are removed by the following (details are here):

setlocal DisableDelayedExpansion
set "batchPath=%~0"
setlocal EnableDelayedExpansion

You can then access the path by using !batchPath!. It doesn't contain any double quotes, so it is safe to say "!batchPath!" later in the script.

The line

if '%1'=='ELEV' (shift & goto gotPrivileges)

checks if the script has already been called by the VBScript script to elevate rights, hence avoiding endless recursions. It removes the parameter using shift.


Update:

  • To avoid having to register the .vbs extension in Windows 10, I have replaced the line
    "%temp%\OEgetPrivileges.vbs"
    by
    "%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs"
    in the script above; also added cd /d %~dp0 as suggested by Stephen (separate answer) and by Tomáš Zato (comment) to set script directory as default.

  • Now the script honors command line parameters being passed to it. Thanks to jxmallet, TanisDLJ and Peter Mortensen for observations and inspirations.

  • According to Artjom B.'s hint, I analyzed it and have replaced SHIFT by SHIFT /1, which preserves the file name for the %0 parameter

  • Added del "%temp%\OEgetPrivileges_%batchName%.vbs" to the :gotPrivileges section to clean up (as mlt suggested). Added %batchName% to avoid impact if you run different batches in parallel. Note that you need to use for to be able to take advantage of the advanced string functions, such as %%~nk, which extracts just the filename.

  • Optimized script structure, improvements (added variable vbsGetPrivileges which is now referenced everywhere allowing to change the path or name of the file easily, only delete .vbs file if batch needed to be elevated)

  • In some cases, a different calling syntax was required for elevation. If the script does not work, check the following parameters:
    set cmdInvoke=0
    set winSysFolder=System32
    Either change the 1st parameter to set cmdInvoke=1 and check if that already fixes the issue. It will add cmd.exe to the script performing the elevation.
    Or try to change the 2nd parameter to winSysFolder=Sysnative, this might help (but is in most cases not required) on 64 bit systems. (ADBailey has reported this). "Sysnative" is only required for launching 64-bit applications from a 32-bit script host (e.g. a Visual Studio build process, or script invocation from another 32-bit application).

  • To make it more clear how the parameters are interpreted, I am displaying it now like P1=value1 P2=value2 ... P9=value9. This is especially useful if you need to enclose parameters like paths in double quotes, e.g. "C:\Program Files".

  • If you want to debug the VBS script, you can add the //X parameter to WScript.exe as first parameter, as suggested here (it is described for CScript.exe, but works for WScript.exe too).

  • Bugfix provided by MiguelAngelo: batchPath is now returned correctly on cmd shell. This little script test.cmd shows the difference, for those interested in the details (run it in cmd.exe, then run it via double click from Windows Explorer):

    @echo off
    setlocal
    set a="%~0"
    set b="%~dpnx0"
    if %a% EQU %b% echo running shell execute
    if not %a% EQU %b% echo running cmd shell
    echo a=%a%, b=%b% 
    pause
    
  • I have updated elevate.cmd a little bit, you can now use it like elevate ./script.cmd (feel free to rename elevate.cmd to sudo.cmd if you like and then you have it in Windows too ;-) - but be aware it is not exactly the same as Linux SUDO).
    Also, an executable being accessible via path can be elevated as well, for example elevate regedit.exe (or sudo regedit.exe if you want) can elevate regedit.
    Note that it will keep the command shell open.

  • Improved checking for elevation (thanks to miroxlav). Now in version 6 the script is checking for S-1-16-12288 by using whoami, which is more reliable than running net file.

  • Due to further hints I got from miroxlav, I made the check now two staged: If the group test succeeded, it is now checking net session additionally.

  • Suggested by Verity Freedom: Sometimes when @echo off is inside the script itself, this may cause it not to run correctly in a situation where elevate is part of an if-else variable. Therefore, it was decided to leave @echo off at the top and then the script will work correctly.

  • To have the name of the calling script show up correctly in the title follow this tip: Without this the title of the window will always be elevate.cmd, not the script you want to run elevated.

Create a helper batch script GetCaller.cmd (suggested by Jolly):

@echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
    goto %func%
)

REM *** STEP1
REM *** Get the filename of the caller of this script, needed for later restart that
(
    (goto) 2>nul
    setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%
    set "_returnVar=%~1"
    call set "_lastCaller=%%~f0"
    call set "_argToLastCaller=%%*"
    call "%~d0\:Step2\..%~pnx0" %*
)
exit /b %= This is never reached =%

:Step2
REM *** STEP2
REM *** Get the filename/cmd-line of the caller of the script
(
    (goto) 2>nul
    (goto) 2>nul
    setlocal DisableDelayedExpansion %= it could be reenabled by the GOTO =%    
    set "_returnVar=%_returnVar%"
    set "_lastCaller=%_lastCaller%"
    set "_argToLastCaller=%_argToLastCaller%"
    call set "caller=%%~f0"
    call set _caller=%%caller:*%%~f0=%%
    if defined _caller (
        set "callType=batch"
        call "%~d0\:Step3batch\..%~pnx0"
    ) ELSE (
        set "callType=cmd-line"
        cmd /c "call "%~d0\:Step3batch\..%~pnx0" "
    )
    endlocal
)
exit /b %= This is never reached =%

:Step3batch
REM *** STEP3 Restart the requester batch, but jump to the label :GetCaller
call :GetCaller
exit /b %= This is never reached =%

:GetCaller
REM *** This uses the trick, that starting a batch without CALL will jump to the last used label
if "%_returnVar%" NEQ "" set "%_returnVar%=%_caller%"
%_lastCaller% %_argToLastCaller%

echo #6 never reached

Add this to the end of elevate.cmd

<:GetCaller <nul call GetCaller.cmd myCallerVar
Title %myCallerVar%

Useful links:


1) Note that the link on tomshardware.com no longer exists, it is now showing a 404 error. The original link was: tomshardware.co.uk: Batch File - "Access Denied" On Windows 7?


Sign up to request clarification or add additional context in comments.

79 Comments

Great answer, although it amazes me slightly that you have to do all that to do something that is clearly necessary in some cases.
Indeed, a command such as ELEVATE is clearly missing in the Windows batch language.
Perhaps it's easier with powershell which seems to be the approved scripting lanaguge for more complex things, but I never bothered to learn it so far :(
You might want to add cd %~dp0 before your own code. Otherwise, you'll find out that your working directory is something like C:\Windows\System32. Imagine your script accidentally changes something there...
@Matt Can you check whether there is some truth behind this rejected edit on your answer?
|
70

As jcoder and Matt mentioned, PowerShell made it easy, and it could even be embedded in the batch script without creating a new script.

I modified Matt's script:

:: Check privileges 
net file 1>NUL 2>NUL
if not '%errorlevel%' == '0' (
    powershell Start-Process -FilePath "%0" -ArgumentList "%cd%" -verb runas >NUL 2>&1
    exit /b
)

:: Change directory with passed argument. Processes started with
:: "runas" start with forced C:\Windows\System32 workdir
cd /d %1

:: Actual work

7 Comments

You are right, if PowerShell is installed, you can use it to run the batch file with elevation (thank you for the code snippet!). And yes, the label is is not needed. Thank you for the hints, it's worth a +1 ... :-)
When invoked from cmd Powershell.exe does not have -verb runas option. It does exist if you are already in PowerShell.
I really like this solution, works great for me. On Windows 8.1 I did require the :gotPrivileges label for it to work.
I'm hitting a problem if this batch file is remote on a UNC path.
@braggPeaks I think that's incorrect. The point of the powershell Start-Process line is to re-run the current script with elevated privileges, in a new process. That process would not enter the if statement, so if the cd line were in there, it wouldn't be hit like it needs to be.
|
49

I do it this way:

NET SESSION
IF %ERRORLEVEL% NEQ 0 GOTO ELEVATE
GOTO ADMINTASKS

:ELEVATE
CD /d %~dp0
MSHTA "javascript: var shell = new ActiveXObject('shell.application'); shell.ShellExecute('%~nx0', '', '', 'runas', 1);close();"
EXIT

:ADMINTASKS
(Do whatever you need to do here)
EXIT

This way it's simple and use only windows default commands. It's great if you need to redistribute you batch file.

CD /d %~dp0 Sets the current directory to the file's current directory (if it is not already, regardless of the drive the file is in, thanks to the /d option).

%~nx0 Returns the current filename with extension (If you don't include the extension and there is an exe with the same name on the folder, it will call the exe).

There are so many replies on this post I don't even know if my reply will be seen.

Anyway, I find this way simpler than the other solutions proposed on the other answers, I hope it helps someone.

12 Comments

cd %~dp0 will not work on Network drives, use pushd %~dp0 instead.
I love the simplicity of this answer. And finally a reason to use Jscript!
@Wolf I typed a huge answer only to realize I mis-understood you... I got what you mean now. I'm gonna edit it to include the /d. Thank you pal :) (P.S.: Pianist here too!)
I tried Stavm's comment, and pushd wasn't helping much. What did is replacing the MSHTA line of the batch file (which I placed between pushd %~dp0% and popd) with these 3 lines: SET DPZERO=%~dp0% and SET NXZERO=%~nx0% and (the rest of this, up through the quotation mark, is one long line) MSHTA "javascript: var oShellApp = new ActiveXObject('Shell.Application'); var oWSH = new ActiveXObject('WScript.Shell'); oShellApp.ShellExecute('CMD', '/C ' + oWSH.Environment("Process")("DPZERO") + oWSH.Environment("Process")("NXZERO"), '', 'runas', 1);close();"
Funny, but Windows Defender detects this as a trojan and quarantines it on launch...
|
32

I am using Matt's excellent answer, but I am seeing a difference between my Windows 7 and Windows 8 systems when running elevated scripts.

Once the script is elevated on Windows 8, the current directory is set to C:\Windows\system32. Fortunately, there is an easy workaround by changing the current directory to the path of the current script:

cd /d %~dp0

Note: Use cd /d to make sure drive letter is also changed.

To test this, you can copy the following to a script. Run normally on either version to see the same result. Run as Admin and see the difference in Windows 8:

@echo off
echo Current path is %cd%
echo Changing directory to the path of the current script
cd %~dp0
echo Current path is %cd%
pause

3 Comments

Good hint, Stephen. So the script should end with cd %~dp0 to retain its current path (I assume this works in Win7 as well, so the same command can be used although only needed for Win8+). +1 for this!
Of note this was also required on my system running Windows 7.
or use pushd %~dp0 instead... why? because popd
28

Matt has a great answer, but it strips away any arguments passed to the script. Here is my modification that keeps arguments. I also incorporated Stephen's fix for the working directory problem in Windows 8.

@ECHO OFF
setlocal EnableDelayedExpansion

::net file to test privileges, 1>NUL redirects output, 2>NUL redirects errors
NET FILE 1>NUL 2>NUL
if '%errorlevel%' == '0' ( goto START ) else ( goto getPrivileges ) 

:getPrivileges
if '%1'=='ELEV' ( goto START )

set "batchPath=%~f0"
set "batchArgs=ELEV"

::Add quotes to the batch path, if needed
set "script=%0"
set script=%script:"=%
IF '%0'=='!script!' ( GOTO PathQuotesDone )
    set "batchPath=""%batchPath%"""
:PathQuotesDone

::Add quotes to the arguments, if needed.
:ArgLoop
IF '%1'=='' ( GOTO EndArgLoop ) else ( GOTO AddArg )
    :AddArg
    set "arg=%1"
    set arg=%arg:"=%
    IF '%1'=='!arg!' ( GOTO NoQuotes )
        set "batchArgs=%batchArgs% "%1""
        GOTO QuotesDone
        :NoQuotes
        set "batchArgs=%batchArgs% %1"
    :QuotesDone
    shift
    GOTO ArgLoop
:EndArgLoop

::Create and run the vb script to elevate the batch file
ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\OEgetPrivileges.vbs"
ECHO UAC.ShellExecute "cmd", "/c ""!batchPath! !batchArgs!""", "", "runas", 1 >> "%temp%\OEgetPrivileges.vbs"
"%temp%\OEgetPrivileges.vbs" 
exit /B

:START
::Remove the elevation tag and set the correct working directory
IF '%1'=='ELEV' ( shift /1 )
cd /d %~dp0

::Do your adminy thing here...

5 Comments

This is a useful answer. However in ECHO UAC.ShellExecute.... line, "batchArgs!" will not expand variable. Use "!batchArgs!". My edit was rejected, so I comment.
@fliedonion well spotted! I'm not sure why your edit was rejected because it was definitely a typo and your fix works. Made the change myself and tested it on Win 8.1. Now to find all the scripts where I use this code....
The script broke when using quoted arguments, like test.bat "a thing" or "test script.bat" arg1 arg2. All fixed now.
I managed to break it (due to my fault: running script from mapped network drive, since admin and normal user dont have same mapping). Still: is there a way to see the output? For me, I had to find the .vbs and change the /c to a /K and then saw it manually.
@jxmallet - just replace '!arg!' to '%arg%' and you can use double quoted arguments. this work for me...
26

You can have the script call itself with psexec's -h option to run elevated.

I'm not sure how you would detect if it's already running as elevated or not... maybe re-try with elevated perms only if there's an Access Denied error?

Or, you could simply have the commands for the xcopy and reg.exe always be run with psexec -h, but it would be annoying for the end-user if they need to input their password each time (or insecure if you included the password in the script)...

6 Comments

Thanks for the response. Unfortunately, I don't think I can use anything outside of stock Windows Vista/7 tools because this will be going out to customers outside of my office. I don't think I can legally distribute PSExec.
Yup, I think you are right about that--even though PSExec is now a Microsoft tool (since they bought out the Sysinternals guys!) the EULA does forbid distribution :(
I think my options are pretty limited. If I knew how to code in VB, I could make it an exe with an admin manifest, but I wouldn't even know where to start. I guess I'll just warn at the beginning of the batch to run as admin if they're running Windows Vista/7. Thanks all.
Another 3rd-party tool that might be freely redistributable and easy to integrate and learn is AutoIt; this page demonstrates how the script requests elevated privileges.
psexec -h doesn't work: 'Couldn't install PSEXESVC service: Access is denied.'. You need to already have the administrator rights to run psexec.
|
25

I use PowerShell to re-launch the script elevated if it's not. Put these lines at the very top of your script.

net file 1>nul 2>nul && goto :run || powershell -ex unrestricted -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c %~fnx0 %*'"
goto :eof
:run
:: TODO: Put code here that needs elevation

I copied the 'net name' method from @Matt's answer. His answer is much better documented and has error messages and the like. This one has the advantage that PowerShell is already installed and available on Windows 7 and up. No temporary VBScript (*.vbs) files, and you don't have to download tools.

This method should work without any configuration or setup, as long as your PowerShell execution permissions aren't locked down.

6 Comments

when providing a list whitespace seperated arguments surrounded by quotes to get it treated as one, the /c %~fnx0 %*' part seems to leave every part besides the first. Eg from test.bat "arg1 arg2 arg3" only arg1 is passed forward
It seems that no matter what, the Start-Process removes all double quotes in the argumentlist..
Works for me! I'm using it for netsh int set int %wifiname% Enable/Disable.
I had the same problem as Nicolas Mommaerts above. I don't understand why it works (the best kind of fix), but the following works like it should (note all the extra quotation marks at the end): net file 1>nul 2>nul && goto :run || powershell -ex unrestricted -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c ""%~fnx0""""'"
Another problem (inconvenience) is that the script pauses (waits for user input) if the server service isn't running (i have it disabled for reasons). I usuallly test for admin perms by trying to write to the HOSTS file location, but it's perhaps better to invoke the powershell command -Verb runas, instead of relying on enabled windows services or attempting file writes.
|
17

For some programs setting the super secret __COMPAT_LAYER environment variable to RunAsInvoker will work.Check this :

set "__COMPAT_LAYER=RunAsInvoker"
start regedit.exe

Though like this there will be no UAC prompting the user will continue without admin permissions.

3 Comments

The link to the Microsoft site no longer worked, so I changed it to the content on archive.org. If anyone can find a working link to the content on Microsoft's site, but all means, please update it to that.
it's still in medium integrity not elevated, so you can't edit HKLM in regedit. It just skipped the uac.
set "__COMPAT_LAYER=RunAsAdminitrator" --- will enable UAC dialog;
16

I wrote gsudo, a sudo for windows: that elevates in the current console (no context switching to a new window), with a credentials cache (reduced UAC popups), and also elevates PowerShell commands.

It allows to elevate commands that require admin privileges, or the whole batch, if you want. Just prepend gsudo before anything that needs to run elevated.

Example batch file that elevates itself using gsudo:

EDIT: New one liner version that works with any windows language and avoids whoami issues:

net session >nul 2>nul & net session >nul 2>nul || gsudo "%~f0" && exit /b || exit /b
:: This will run as admin ::

Alternative (original version):

@echo off
  rem Test if current context is already elevated:
  whoami /groups | findstr /b BUILTIN\Administrators | findstr /c:"Enabled group" 1> nul 2>nul && goto :isadministrator
  echo You are not admin. (yet)
  :: Use gsudo to launch this batch file elevated.
  gsudo "%~f0"
  goto end
:isadministrator
  echo You are admin.
  echo (Do admin stuff now).
:end

Install:

See gsudo in action: gsudo demo

6 Comments

Cool. Thanks. On which versions of Windows does it function?
The tests suite runs on Server 2019 and my local dev box is Windows 10 1909. Sorry I haven't tested compatibility with other older OS.
Thanks Gerardo. If anyone tries it on other versions of Windows, please post your results.
I've just tested gsudo v0.7 on Windows 8.1 and worked. It's not trivial to install .Net Framework 4.6 there. Luckily choco install dotnet4.6.2 brought some hard-to-get dependencies. Then gsudo config Prompt "$p# " fixed an issue with ConHost showing a wrong prompt (escape sequence chars instead of the red sharp). @RockPaperLizard
Thanks so much Gerardo. Could it be recompiled to work with any versions of .Net Framework before 4.6, or does it depend on some functionality specific to 4.6?
|
12

I recently needed a user-friendly approach and I came up with this, based on valuable insights from contributors here and elsewhere. Simply put this line at the top of your .bat script. Feedback welcome.

@pushd %~dp0 & fltmc | find "." && (powershell start '%~f0' ' %*' -verb runas 2>nul) && (popd & exit /b)

Intrepretation:

  • @pushd %~dp0 ensures a consistent working directory; supports UNC paths

  • & fltmc runs a native windows command that outputs an error when run unelevated

  • | find "." makes that error prettier, and causes nothing to output when elevated

  • && ( if we successfully got an error because we're not elevated, do this...

  • powershell start invoke PowerShell and call the Start-Process cmdlet (start is an alias)

  • '%~f0' pass in the full path and name of this .bat file. Single quotes allow for spaces

  • ' %*' pass in any and all arguments to this .bat file. Funky quoting and escape sequences probably won't work, but simple quoted strings should. The leading space is needed to prevent breaking things if no arguments are present

  • -verb runas don't just start the process... RunAs Administrator!

  • 2>nul) discard PowerShell's unsightly error output if the UAC prompt is canceled/ignored

  • && if we successfully invoked ourself with PowerShell, then...

    NOTE: in the event we don't obtain elevation (user cancels UAC) then the && here allows the .bat to continue running without elevation, such that any commands that require it will fail but others will work just fine. If you want the script to simply exit instead of running unelevated, make this a single ampersand: &

  • (popd & exit /b) returns to the initial working directory on the command line and exits the initial .bat processing, because we don't need it anymore; we already have an elevated process running this .bat. The /b switch allows cmd.exe to remain open if the .bat was started from the command line – this has no effect if the .bat was double-clicked

3 Comments

Nicely done! Glad to see good answers to this question are still flowing in.
doesnt seem to work with me as is, but I could change the . to a : in the find, because apparently the error message in german didnt have a dot.
I would suggest changing ... && exit /b) to ... && popd && exit /b). that way the current directory keeps unchanged after running the script.
10

When a CMD script needs Administrator rights and you know it, add this line to the very top of the script (right after any @ECHO OFF):

NET FILE > NUL 2>&1 || POWERSHELL -ex Unrestricted -Command "Start-Process -Verb RunAs -FilePath '%ComSpec%' -ArgumentList '/c \"%~fnx0\" %*'" && EXIT /b

The NET FILE checks for existing Administrator rights. If there are none, PowerShell starts the current script (with its arguments) in an elevated shell, and the non-elevated script closes.

3 Comments

Nice technique! Upvoted. If admin rights aren't granted, will this generate an infinite loop?
No, the script will just (give an error and) try without elevation.
Perfect. No temp file, no error message, no delay. This is the best from where I stand.
8

If you don’t care about arguments then here’s a compact UAC prompting script that’s a single line long. It doesn’t pass arguments through since there’s no foolproof way to do that that handles every possible combination of poison characters.

net sess>nul 2>&1||(echo(CreateObject("Shell.Application"^).ShellExecute"%~0",,,"RunAs",1:CreateObject("Scripting.FileSystemObject"^).DeleteFile(wsh.ScriptFullName^)>"%temp%\%~nx0.vbs"&start wscript.exe "%temp%\%~nx0.vbs"&exit)

Paste this line under the @echo off in your batch file.

Explanation

The net sess>nul 2>&1 part is what checks for elevation. net sess is just shorthand for net session which is a command that returns an error code when the script doesn’t have elevated rights. I got this idea from this SO answer. Most of the answers here feature net file instead though which works the same. This command is fast and compatible on many systems.

The error level is then checked with the || operator. If the check succeeds then it creates and executes a WScript which re-runs the original batch file but with elevated rights before deleting itself.


Alternatives

The WScript file is the best approach being fast and reliable, although it uses a temporary file. Here are some other variations and their dis/ad-vantages.

PowerShell

net sess>nul 2>&1||(powershell saps '%0'-Verb RunAs&exit)

Pros:

  • Very short.
  • No temporary files.

Cons:

  • Slow. PowerShell can be slow to start up.
  • Spews red text when the user declines the UAC prompt. The PowerShell command could be wrapped in a try{...}catch{} to prevent this though.

Mshta WSH script

net sess>nul 2>&1||(start mshta.exe vbscript:code(close(Execute("CreateObject(""Shell.Application"").ShellExecute""%~0"",,,""RunAs"",1"^)^)^)&exit)

Pros:

  • Fast.
  • No temporary files.

Cons:

  • Not reliable. Some Windows 10 systems will block the script from running due to Windows Defender intercepting it as a potential trojan.

4 Comments

this line is magical, can you explain the first section net sess>nul 2>&1?
Nice solution but regarding the first method, when I try to pass any argument to my batch file, it doesn't reach to that batch file.... I tried to play around with ShellExecute"%fullPathArgs%" but didn't work either. It seems that I've to pass the batch file arguments in another parameter.
@Abdulhameed If the arguments are simple you could use ShellExecute"%~0","%~*",,"RunAs" instead of ShellExecute"%~0",,,"RunAs", but complicated arguments involving quoting, escape sequences, or poison characters will break it. Notice that other answers will have similar caveats. Preserving the arguments reliably is not an easy task.
@yww325 It just checks for admin rights (NET SESS), silently: output is ignored (>NUL), as is the error stream by redirecting it to standard output (2>&1).
6

I pasted this in the beginning of the script:

:: BatchGotAdmin
:-------------------------------------
REM  --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\icacls.exe" "%SYSTEMROOT%\system32\config\system"

REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
    echo Requesting administrative privileges...
    goto UACPrompt
) else ( goto gotAdmin )

:UACPrompt
    echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
    echo args = "" >> "%temp%\getadmin.vbs"
    echo For Each strArg in WScript.Arguments >> "%temp%\getadmin.vbs"
    echo args = args ^& strArg ^& " "  >> "%temp%\getadmin.vbs"
    echo Next >> "%temp%\getadmin.vbs"
    echo UAC.ShellExecute "%~s0", args, "", "runas", 1 >> "%temp%\getadmin.vbs"

    "%temp%\getadmin.vbs" %*
    exit /B

:gotAdmin
    if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
    pushd "%CD%"
    CD /D "%~dp0"
:--------------------------------------

3 Comments

I like the arg processing in your script. But note that cacls is deprecated in Windows 7 and newer windows versions.
Fsutil dirty is even better
Do you know that the line pushd "%CD%" saves the current working directory and changes to the current working directory?
4

Although not directly applicable to this question, because it wants some information for the user, google brought me here when I wanted to run my .bat file elevated from task scheduler.

The simplest approach was to create a shortcut to the .bat file, because for a shortcut you can set Run as administrator directly from the advanced properties.

Running the shortcut from task scheduler, runs the .bat file elevated.

Comments

4

Using powershell.

If the cmd file is long I use a first one to require elevation and then call the one doing the actual work.

If the script is a simple command everything may fit on one cmd file. Do not forget to include the path on the script files.

Template:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c " comands or another script.cmd go here "'"

Example 1:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\BIN\x.ps1"'"

Example 2:

@echo off
powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c "c:\bin\myScript.cmd"'"

1 Comment

Side note: you don't need to proxy through cmd. You can place the executable there and only the arguments at the end, as in - powershell -Command "Start-Process 'docker-compose' -Verb RunAs -ArgumentList '"-f ./build/postgres.yaml up"'"
4

One-liner batch user elevation (with arguments)

Here is my one-liner version for this age-old question of batch user elevation which is still relevant today.
Simply add the code to the top of your batch script and you're good to go.

Silent

This version does not output anything nor pause execution on error.

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" ((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul)))& exit /b)
Verbose

A verbose version which tells the user that admin privileges are being requested and pauses on error before exiting.

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" (echo Requesting administrator privileges...&((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul))) else (echo This script requires administrator privileges.& pause))& exit /b)

echo Has admin permissions
echo Working dir: "%cd%"
echo Script dir: "%~dp0"
echo Script path: "%~f0"
echo Args: %*

pause

Method of operation

  1. Uses fltmc to check for administrator privileges. (system component, included in Windows 2000+)
  2. If user already has administrator privileges, continues operation normally.
  3. If not, spawns an elevated version of itself using either:
    1. powershell (optional Windows feature, included in Windows 7+ by default, can be uninstalled/otherwise not available, can be installed on Windows XP/Vista)
    2. mshta (system component, included in Windows 2000+)
  4. If fails to acquire elevation, stops execution (instead of looping endlessly).

What sets this solution apart from others?

There are literally hundreds of variations around for solving this issue but everything I've found so far have their shortcomings and this is an attempt of solving most of them.

  • Compatibility. Using fltmc as the means of checking for privileges and either powershell or mshta for elevation works with every Windows version since 2000 and should cover most system configurations.
  • Does not write any extra files.
  • Preserves current working directory. Most of the solutions found conflate "script directory" with "working directory" which are totally different concepts. If you want to use "script directory" instead, replace %cd% with %~dp0. Some people advocate using pushd "%~dp0" instead so paths inside networked UNC paths like "\\SOMEONES-PC\share" will work but that will also automagically map that location to a drive letter (like Y:) which might or might not be what you want.
  • Stops if unable to acquire elevation. This can happen because of several reasons, like user clicking "No" on the UAC prompt, UAC being disabled, group policy settings, etc. Many other solutions enter an endless loop on this point, spawning millions of command prompts until the heat death of the universe.
  • Supports (most of) command-line arguments and weird paths. Stuff like ampersands &, percent signs %, carets ^ and mismatching amount of quotes """'. You still definitely CAN break this by passing a sufficiently weird combinations of those, but that is an inherent flaw of Windows' batch processing and cannot really be worked around to always work with any combination. Most typical use-cases should be covered though and arguments work as they would without the elevation script.

Known issues

If you enter a command-line argument that has a mismatched amount of double-quotes (i.e. not divisible by 2), an extra space and a caret ^ will be added as a last argument. For example "arg1" arg2" """" "arg3" will become "arg1" arg2" """" "arg3" ^. If that matters for your script, you can add logic to fix it, f.ex. check if _ELEV=1 (meaning that elevation was required) and then check if the last character of argument list is ^ and/or amount of quotes is mismatched and remove the misbehaving caret.

Example script for logging output to file

You cannot easily use > for stdout logging because on elevation a new cmd window is spawned and execution context switched.

You can achieve it by passing increasingly weird combinations of escape characters, like elevate.bat testarg ^^^> test.txt but then you would need to make it always spawn the new cmd window or add logic to strip out the carets, all of which increases complexity and it would still break in many scenarios.

The best and easiest way would be simply adding the logging inside your batch script, instead of trying to redirect from command line. That'll save you a lot of headache.

Here is an example how you can easily implement logging for your script:

@setlocal disabledelayedexpansion enableextensions
@echo off

:: Admin check
fltmc >nul 2>nul || set _=^"set _ELEV=1^& cd /d """%cd%"""^& "%~f0" %* ^"&&((if "%_ELEV%"=="" (echo Requesting administrator privileges...&((powershell -nop -c start cmd -args '/d/x/s/v:off/r',$env:_ -verb runas >nul 2>nul) || (mshta vbscript:execute^("createobject(""shell.application"").shellexecute(""cmd"",""/d/x/s/v:off/r ""&createobject(""WScript.Shell"").Environment(""PROCESS"")(""_""),,""runas"",1)(window.close)"^) >nul 2>nul))) else (echo This script requires administrator privileges.& pause))& exit /b)

set _log=
set _args=%*
if not defined _args goto :noargs
set _args=%_args:"=%
set _args=%_args:(=%
set _args=%_args:)=%
for %%A in (%_args%) do (if /i "%%A"=="-log" (set "_log=>> %~n0.log"))
:noargs

if defined _log (echo Logging to file %~n0.log) else (echo Logging to stdout)
echo Has admin permissions %_log%
echo Working dir: "%cd%" %_log%
echo Script dir: "%~dp0" %_log%
echo Script path: "%~f0" %_log%
echo Args: %* %_log%

echo Hello World! %_log%

pause

Run: logtest.bat -log By adding argument -log , the output will be logged to a file instead of stdout.

Closing thoughts

It bewilders me how a simple "ELEVATE" instruction has not been introduced to batch even after 15 years of UAC existing. Maybe one day Microsoft will get their shit together. Until then, we have to resort to using these hacks.

1 Comment

Concur with the struggle to understand universal ELEVATE. Though it does exist in various powertoys and 3rd party tools. Why M$ never did a SETGID flag similar to Unix like systems (chmod u=rwx,g+x,ug+s <file> or chmod 6710 <file>) - Which would be simple and logical to implement through Windows extended file attributes. (BTW that chmod translates to: "If user is a member of the <file> group then adopt admin's group on execution") - Linux also has extended attributes to allow more complex permissions.
2

Just one line will be enough, place it on the first line of your script to make the script ran with administrator privileges:

net file 1>NUL 2>NUL || start "" mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c pushd ""%~dp0"" && ""%~s0"" %*","","runas",0)(window.close) && exit

If you are using Win 10 and above, or if VBScript got removed in the future releases of Windows, then you can use:

net file 1>NUL 2>NUL || powershell Start-Process -FilePath cmd.exe -ArgumentList """/c pushd %~dp0 && %~s0 %*""" -Verb RunAs && exit

================================================

Can support:

  • Script path that has spaces
  • Automatically switch to the path where the script is located
  • Provide script arguments
  • Hidden window (see below)

If you do not need to hide the window, for the mshta version, you only need to change 0 to 1:

net file 1>NUL 2>NUL || start "" mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c pushd ""%~dp0"" && ""%~s0"" %*","","runas",1)(window.close) && exit

For the powershell version, you can simply add -WindowStyle Hidden in it:

net file 1>NUL 2>NUL || powershell Start-Process -FilePath cmd.exe -ArgumentList """/c pushd %~dp0 && %~s0 %*""" -Verb RunAs -WindowStyle Hidden && exit

Refers:

2 Comments

This is clever. Can you add an explanation so everyone can understand what you are doing here?
Even though I upvoted this answer, there appears to be something wrong with it. If I escape out of the password-prompt, the rest of the script runs anyways. But i love how simple and succinct, and there are no weird side-effects as i experienced with the VBS solution and the JS solution.
1

I redacted original Matt solution a bit.

@echo off
chcp 1251
REM :::::::::::::::::::::::::::::::::::::::::
REM Elevate.cmd - Version 10 custom
REM Automatically check & get admin rights
REM see "https://stackoverflow.com/a/12264592/1016343" for description
REM :::::::::::::::::::::::::::::::::::::::::
 CLS
 ECHO.
 ECHO =============================
 ECHO Running Admin shell
 ECHO =============================

:init
 setlocal DisableDelayedExpansion
 set cmdInvoke=1
 set winSysFolder=System32
 set "batchPath=%~dpnx0"
 rem this works also from cmd shell, other than %~0
 for %%k in (%0) do set batchName=%%~nk
 set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
 setlocal EnableDelayedExpansion

:checkPrivileges
  whoami /groups /nh | find "S-1-16-12288" > nul
  if '%errorlevel%' == '0' ( goto checkPrivileges2 ) else ( goto getPrivileges )


:checkPrivileges2
  net session 1>nul 2>NUL
  if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )

:getPrivileges
  if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
  ECHO.
  ECHO **************************************
  ECHO Invoking UAC for Privilege Escalation
  ECHO **************************************

  ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
  ECHO args = "ELEV " >> "%vbsGetPrivileges%"
  ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
  ECHO args = args ^& strArg ^& " "  >> "%vbsGetPrivileges%"
  ECHO Next >> "%vbsGetPrivileges%"
  
  if '%cmdInvoke%'=='1' goto InvokeCmd 

  ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
  goto ExecElevation

:InvokeCmd
  ECHO args = "/c """ + "!batchPath!" + """ " + args >> "%vbsGetPrivileges%"
  ECHO UAC.ShellExecute "%SystemRoot%\%winSysFolder%\cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%"

:ExecElevation
 "%SystemRoot%\%winSysFolder%\WScript.exe" "%vbsGetPrivileges%" %*
 exit /B

:gotPrivileges
 setlocal & cd /d %~dp0
 if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul  &  shift /1)

 REM :::::::::::::::::::::::::
 REM START
 REM :::::::::::::::::::::::::
 
REM - your code -

Now its much less buggy and properly support both cyrillic and if/else parameters.

12 Comments

Just for curiosity - if you say "less buggy", which bugs do you mean?
ECHO. is a bug because of cmd.exe does first not recognize it as internal command ECHO. The dot after the command lets cmd search in current directory for a file with name ECHO. This file is executed if it really exists instead of printing an empty line to STDOUT. Better and fail-safe would be echo/ or echo( which results in an output of the empty line without accessing the file system at all as cmd immediately recognizes now ECHO as internal command. set "batchPath=%~dpnx0" can be optimized to set "batchPath=%~f0" whereby batchPath is not a good variable name.
The variable batchPath does not hold the directory path of the batch file but the fully qualified file name of the batch file. fullBatchFileName would be a much better variable name. Open a command prompt window, run if /? and read the output help. if '%errorlevel%' == '0' is not described in the output help as syntax to evaluate the exit code of the last executed command or executable. There should be read Accessing the values of dynamic variables of Difference between Dynamic variables and Environment variables in CMD.
All if '%errorlevel%' == '0' should be replaced by if not errorlevel 1 for a fail-safe code. Even better would be the replacement of if '%errorlevel%' == '0' ( goto checkPrivileges2 ) else ( goto getPrivileges ) by if errorlevel 1 goto getPrivileges and the deletion of :checkPrivileges2 and the replacement of if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) by if not errorlevel 1 goto gotPrivileges. Why are two elevation runtime checks used in the code instead of just one?
@Matt thank you, I changed my code and not using overly complex if-else variable anymore (just calling another batch file instead), but I checked the old version of the file with your new elevate and seems like the bug is gone. I still will use my chcp though its really critical in my country.
|
-5

Following solution is clean and works perfectly.

  1. Download Elevate zip file from https://www.winability.com/download/Elevate.zip

  2. Inside zip you should find two files: Elevate.exe and Elevate64.exe. (The latter is a native 64-bit compilation, if you require that, although the regular 32-bit version, Elevate.exe, should work fine with both the 32- and 64-bit versions of Windows)

  3. Copy the file Elevate.exe into a folder where Windows can always find it (such as C:/Windows). Or you better you can copy in same folder where you are planning to keep your bat file.

  4. To use it in a batch file, just prepend the command you want to execute as administrator with the elevate command, like this:

 elevate net start service ...

2 Comments

That command only opens a dialogwindow what asks the user if this command is allowed to be executed. So you cant use this command in a batchfile what should run WITHOUT user interactions.
If it were possible to elevate WITHOUT user interaction it would be a huge security hole, wouldn't it? Every virus would start using that method to elevate itself without user approval.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.