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?
@powershell Start-Process cmd -Verb runas. From Powershell just drop@powershell. This starts cmd with elevated rights.