Is there a way to hide output from the Taskkill command in a batch file for installing patches? - batch-file

Supposedly, stopping the trustedinstaller.exe task after each Microsoft patch installation speeds up the install process a little. Is there a way to hide the output that comes from killing that task? Right now it outputs to the cmd window "SUCCESS: Task TrustedInstaller.exe terminated..." and I'd rather not see that as the end user doesn't need to be confused by it. It doesn't seem to have a /quiet or /q switch available to it.
Is there a better way in install updates manually in general? I like to buy coffee!! Thank you in advance...
Here is the short script I'm using:
#echo off
echo Microsoft security updates being installed!!!
:=================================================================
::sc config wuauserv start= demand
::sc start wuauserv
:=================================================================
echo.
echo Installation may take several minutes...
Pushd %~dp0
:=================================================================
:----------------------- Required Patches ------------------------
echo.
echo Installing KB5017396 Servicing Stack Update...
wusa.exe windows10.0-kb5017396-x64_8db1a993e6115cf3b92d0159f562861ab16ee88d.msu /quiet /norestart
cmd.exe /C TaskKill /IM TrustedInstaller.exe /F
echo.
echo Installing KB5018411 Cumulative Update...(this one takes the longest)
wusa.exe windows10.0-kb5018411-x64_0ce64987a6675227941fadd950019e447d60ba03.msu /quiet /norestart
cmd.exe /C TaskKill /IM TrustedInstaller.exe /F
echo.
echo Installing KB890830 Windows Malicious Software Removal Tool...
wusa.exe windows-kb890830-x64-v5.109_7bafbdf130e5ccd23c002984c7481286dc173072.exe /quiet /norestart
echo Restart this computer in order to apply updates.
echo.
PAUSE
:=================================================================
::sc stop wuauserv
::sc config wuauserv start= disabled
:=================================================================
:=================================================================
exit

Put >NUL 2>NUL at the very beginning/end (the latter depends on the command itself sometimes) it should work on nearly every operating windows system.
There might exist others or short forms, but some of them threw me errors (e.g. batch is closed unexpected).
The command means that all output caused by current command line should be hidden in cmd/batch.

Related

Redirect output for "Run as administrator" program? [duplicate]

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 7/Windows Vista 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.
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 - in that case the script just proceeds).
Check out this code (I was inspired by the code by NIronwolf posted in the thread Batch File - "Access Denied" On Windows 7?), but I've improved it - in my version there isn't any directory created and removed to check for administrator privileges):
::::::::::::::::::::::::::::::::::::::::::::
:: Elevate.cmd - Version 4
:: Automatically check & get admin rights
:: see "https://stackoverflow.com/a/12264592/1016343" for description
::::::::::::::::::::::::::::::::::::::::::::
#echo off
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
NET FILE 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)
::::::::::::::::::::::::::::
::START
::::::::::::::::::::::::::::
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
The script takes advantage of the fact that NET FILE requires administrator privilege and returns errorlevel 1 if you don't have it. 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 and with Windows XP - 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.
NET FILE 1>NUL 2>NUL & IF ERRORLEVEL 1 (ECHO You must right-click and select &
ECHO "RUN AS ADMINISTRATOR" to run this batch. Exiting... & ECHO. &
PAUSE & 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: NET FILE [...] 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
Useful links:
Meaning of special characters in batch file:Quotes ("), Bang (!), Caret (^), Ampersand (&), Other special characters
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
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.
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
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...
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)...
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.
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.
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:
via chocolatey: choco install gsudo
or scoop: scoop install gsudo
or grab it from github: https://github.com/gerardog/gsudo
See gsudo in action:
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
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.
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.
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"
:--------------------------------------
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.
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"'"
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
Uses fltmc to check for administrator privileges. (system component, included in Windows 2000+)
If user already has administrator privileges, continues operation normally.
If not, spawns an elevated version of itself using either:
powershell (optional Windows feature, included in Windows 7+ by default, can be uninstalled/otherwise not available, can be installed on Windows XP/Vista)
mshta (system component, included in Windows 2000+)
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.
Try this:
#echo off
CLS
:init
setlocal DisableDelayedExpansion
set cmdInvoke=1
set winSysFolder=System32
set "batchPath=%~0"
for %%k in (%0) do set batchName=%%~nk
set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
setlocal EnableDelayedExpansion
:checkPrivileges
NET FILE 1>NUL 2>NUL
if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )
:getPrivileges
if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
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 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
If you need information on that batch file, run the HTML/JS/CSS Snippet:
document.getElementsByTagName("data")[0].innerHTML="ElevateBatch, version 4, release<br>Required Commands:<ul><li>CLS</li><li>SETLOCAL</li><li>SET</li><li>FOR</li><li>NET</li><li>IF</li><li>ECHO</li><li>GOTO</li><li>EXIT</li><li>DEL</li></ul>It auto-elevates the system and if the user presses No, it just doesn't do anything.<br>This CANNOT be used to create an Elevated Explorer.";
data{font-family:arial;text-decoration:none}
<data></data>
%1 start "" mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c pushd ""%~dp0"" && ""%~s0"" ::","","runas",1)(window.close)&&exit
Following solution is clean and works perfectly.
Download Elevate zip file from https://www.winability.com/download/Elevate.zip
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)
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.
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 ...

bugfixer.bat batch file ask end user which option they want to run

fun stuff!!
Manager has just bought a humdinger of a utility. It does everything. It even has command line functionality. If you run BugFixer /a the application will automatically scan the entire drive for cooties. If you run the Bugfixer /b it will scan all of your files on the Windows\system 32 directory and if you run Bugfixer /c the program will scan and repair your Registry. Need to write a batch file that will allow your users to avoid that pesky GUI and efficiently run the Bug fixer through the command line, by asking them which option they would like to initialize.
:TOP
ECHO WHICH BUG FIXER DO YOU NEED TO RUN? A=ALL B=SOME c=REPAIR
SET /P = %USERSPEC%
IF "%1"=="A=ALL" GO TO :FIRST
IF "%1"=="A=all" GO TO :FIRST
:FIRST
CHKDSK C:
ECHO CHECKING ALL FILES ARE COMPLETE
IF "%2"=="B=SOME" GO TO :NEXT
IF "%2"=="b=some" GO TO :NEXT
:NEXT
CHKDSK /F /R C:\WINDOWS/SYSTEM32
ECHO CHECKING SOME FILES ARE COMPLETE
IF "%3"=="REPAIR" GO TO :LAST
IF "%3"=="repair" GO TO :LAST
:LAST
CHKDSK /c
ECHO REPAIR FILES ARE COMPLETED
Like I said fun stuff. Anyone wanna help?
Next script logic could become helpful:
#echo OFF >NUL
setlocal enableextensions
:TOP
echo(
set "USERSPEC="
set /P "USERSPEC=Which bug fixer do you need to run? A=all B=some C=repair? "
if /I "%USERSPEC%"=="A" goto :FIRST
if /I "%USERSPEC%"=="B" goto :NEXT
if /I "%USERSPEC%"=="C" goto :LAST
echo NO CHECK CHOOSEN, BATCH ENDS
goto :ENDSCRIPT
:FIRST
chkdsk C:
echo CHECKING ALL FILES ARE COMPLETE
goto :TOP
:NEXT
chkdsk /F /R C:\WINDOWS/SYSTEM32
echo CHECKING SOME FILES ARE COMPLETE
goto :TOP
:LAST
chkdsk /c
echo REPAIR FILES ARE COMPLETED
goto :TOP
:ENDSCRIPT
endlocal
goto :eof
However, running it:
==>30019117.bat
Which bug fixer do you need to run? A=all B=some C=repair? a
Access Denied as you do not have sufficient privileges.
You have to invoke this utility running in elevated mode.
CHECKING ALL FILES ARE COMPLETE
Which bug fixer do you need to run? A=all B=some C=repair?
NO CHECK CHOOSEN, BATCH ENDS
==>
Resources (advised reading):
An A-Z Index of the Windows CMD command line (command reference)
Windows CMD Shell Command Line Syntax (additional particularities)
Script resources for IT professionals (a huge Script repository)

Batch script to install MSI

I am trying to write a .bat for the first time.
I am trying to install .msi using script, currently we are installing manually by double clicking on it.
Path from: d:/installed sw/$folder/.msi
Path to: D:/program files/app/
$folder means, it is different every time, as we are getting new msi to install which are provided in folder created by current date.
Here is the script I am trying:
#echo off
Title HOST: Installing Updates on %computername%
echo %computername%
set server=\\SERVERNAME or PATH\msifolder
:select
cls
echo Select one of the Following MSI Install Folders for installation task.
echo.
dir %server% /A:D /B
SET /P MSI=Please enter the MSI Folder to install:
SET source=%server%\%MSI%
echo Selected Installation %MSI%
echo.
echo.
:verify
ECHO Is This Correct?
echo.
echo.
ECHO 0: ABORT INSTALL
ECHO 1: YES
ECHO 2: NO,RE-SELECT
SET /p choice=Select YES, NO or ABORT? [0,1,2]:
if /i [%choice%]==[0] endlocal&goto end
if [%choice%]==[] goto BCurrentlocal
if [%choice%]==[1] goto yes
if [%choice%]==[2] goto no
endlocal
:no
goto select
:yes
set FILENAME=%MSI%
call %source%\%FILENAME%.msi
echo beginning %MSI% installation
pause
echo Exiting Install Script....
PING -n 4 127.0.0.1 >nul
exit
In line with set server I am adding the path to.
SET /P MSI is for path from.
However, it is not working.
Can anyone guide me what mistake I am doing?
This is how to install a normal MSI file silently:
msiexec.exe /i c:\setup.msi /QN /L*V "C:\Temp\msilog.log"
Quick explanation:
/L*V "C:\Temp\msilog.log"= verbose logging at indicated path
/QN = run completely silently
/i = run install sequence
The msiexec.exe command line is extensive with support for a variety of options. Here is another overview of the same command line interface. Here is an annotated versions (was broken, resurrected via way back machine).
It is also possible to make a batch file a lot shorter with constructs such as for loops as illustrated here for Windows Updates.
If there are check boxes that must be checked during the setup, you must find the appropriate PUBLIC PROPERTIES attached to the check box and set it at the command line like this:
msiexec.exe /i c:\setup.msi /QN /L*V "C:\Temp\msilog.log" STARTAPP=1 SHOWHELP=Yes
These properties are different in each MSI. You can find them via the verbose log file or by opening the MSI in Orca, or another appropriate tool. You must look either in the dialog control section or in the Property table for what the property name is. Try running the setup and create a verbose log file first and then search the log for messages ala "Setting property..." and then see what the property name is there. Then add this property with the value from the log file to the command line.
Also have a look at how to use transforms to customize the MSI beyond setting command line parameters: How to make better use of MSI files
Here is the batch file which should work for you:
#echo off
Title HOST: Installing updates on %computername%
echo %computername%
set Server=\\SERVERNAME or PATH\msifolder
:select
cls
echo Select one of the following MSI install folders for installation task.
echo.
dir "%Server%" /AD /ON /B
echo.
set /P "MSI=Please enter the MSI folder to install: "
set "Package=%Server%\%MSI%\%MSI%.msi"
if not exist "%Package%" (
echo.
echo The entered folder/MSI file does not exist ^(typing mistake^).
echo.
setlocal EnableDelayedExpansion
set /P "Retry=Try again [Y/N]: "
if /I "!Retry!"=="Y" endlocal & goto select
endlocal
goto :EOF
)
echo.
echo Selected installation: %MSI%
echo.
echo.
:verify
echo Is This Correct?
echo.
echo.
echo 0: ABORT INSTALL
echo 1: YES
echo 2: NO, RE-SELECT
echo.
set /p "choice=Select YES, NO or ABORT? [0,1,2]: "
if [%choice%]==[0] goto :EOF
if [%choice%]==[1] goto yes
goto select
:yes
echo.
echo Running %MSI% installation ...
start "Install MSI" /wait "%SystemRoot%\system32\msiexec.exe" /i /quiet "%Package%"
The characters listed on last page output on entering in a command prompt window either help cmd or cmd /? have special meanings in batch files. Here are used parentheses and square brackets also in strings where those characters should be interpreted literally. Therefore it is necessary to either enclose the string in double quotes or escape those characters with character ^ as it can be seen in code above, otherwise command line interpreter exits batch execution because of a syntax error.
And it is not possible to call a file with extension MSI. A *.msi file is not an executable. On double clicking on a MSI file, Windows looks in registry which application is associated with this file extension for opening action. And the application to use is msiexec with the command line option /i to install the application inside MSI package.
Run msiexec.exe /? to get in a GUI window the available options or look at Msiexec (command-line options).
I have added already /quiet additionally to required option /i for a silent installation.
In batch code above command start is used with option /wait to start Windows application msiexec.exe and hold execution of batch file until installation finished (or aborted).
Although it might look out of topic nobody bothered to check the ERRORLEVEL. When I used your suggestions I tried to check for errors straight after the MSI installation. I made it fail on purpose and noticed that on the command line all works beautifully whilst in a batch file msiexec dosn't seem to set errors. Tried different things there like
Using start /wait
Using !ERRORLEVEL! variable instead of %ERRORLEVEL%
Using SetLocal EnableDelayedExpansion
Nothing works and what mostly annoys me it's the fact that it works in the command line.

Stop Batch File from Beeping

For some reason my batch script beeps when pressing Space at a PAUSE event.
I currently have both:
start /wait %comspec% /Q /C "net stop beep" >nul 2>&1
start /wait %comspec% /Q /C "sc stop beep" >nul 2>&1
...at the start of my script, which has successfully disabled the system beep, but now it has been replaced by a Windows "themed" beep.
Also this happens when I get to a CHOICE event and the user presses Space.
Any ideas on how to make a batch script completely silent, even during input "errors"?
Thanks,
D
Try replacing your choice commands with something like this.
set /p choice="Please enter a choice: "
It'll require the user to hit enter but at least it won't beep.
If you really want to do it the hard way and turn off the default sound, I think you'll find it either in HKCU\AppEvents\Schemes\Apps\.Default\SystemAsterisk\.Current\(Default) or in HKCU\AppEvents\Schemes\Apps\.Default\AppGPFault\.Current\(Default).
Here's a brief proof of concept.
#echo off
setlocal
:: stop bleeping service
start /wait %comspec% /Q /C "sc stop beep" >nul 2>&1
:: store current event setting for Asterisk
set loc=HKCU\AppEvents\Schemes\Apps\.Default\SystemAsterisk\.Current
for /f "tokens=3*" %%I in ('reg query %loc%') do (
set asterisk=%%I %%J
)
:: choice with service stopped and Windows Asterisk sound event still active
choice
:: set Windows Asterisk event sound to ""
reg add "%loc%" /v "" /t REG_SZ /d "" /f >NUL
:: choice with asterisk set to ""
choice
:: restore the sound
reg add "%loc%" /v "" /t REG_SZ /d "%asterisk%" /f >NUL
If that doesn't work, try replacing SystemAsterisk with AppGPFault. To be honest, I'm unable to recreate your symptom. I hear silence after stopping the beep service. So I'm not entirely sure which event is getting triggered when a user hits the space bar and the beep service is stopped.
You could always record a moment of silence, then use that to replace one of the Windows themed beeps.
I recommend that you post a little more of your code so we can see what is causing the beeps, because PAUSE should not do that.

How to format my batch file correctly

I've been at this for a few hours now. I'm finally giving in and asking for help. What I'm trying to do is use a batch file to get my Truecrypt volume mounted, then open Dropbox. When both of these things are done, executing the batch file the second time will exit Dropbox, then dismount the Truecrypt volume. Everything works correctly except for the exiting of Dropbox. I can't for the life of me, get it to exit Dropbox then dismount the volume. It skips right over the taskkill and just dismounts the volume. I've tried it a variety of different ways which should be working, but for some reason are not. The command window is saying taskkill isn't recognized as a command, but that's obviously bull. =\
#echo off
Set Drive=Z
Set Path="C:\Users\DK\AppData\Roaming\Dropbox.bak"
Set TrueCrypt="C:\Program Files\TrueCrypt\TrueCrypt.exe"
IF EXIST %Drive%: GoTo Dismount
GOTO Mount
:Dismount
taskkill /F /IM "dropbox.exe"
%TrueCrypt% /d %Drive% /f /w /q /s
exit
:Mount
%TrueCrypt% /v /l %Drive% %Path% /q
echo Waiting for volume...
:keepwaiting
ping -n 1 127.0.0.1 > nul
if not exist Z:\ goto keepwaiting
start "Dropbox" "C:\Users\DK\AppData\Roaming\Dropbox\bin\Dropbox.exe"
exit
Never use Path as your own variable! Change Set Path="C:\Users\DK\AppData\Roaming\Dropbox.bak" to something like Set DropboxPath="C:\Users\DK\AppData\Roaming\Dropbox.bak"
Taskkill.exe is in system32 but windows can only find it if it is listed in %Path%.
Tips:
It is probably a good idea to call setlocal as the second line in your script so your variables don't leak out of the batch file.
Never set path, pathext, temp, tmp, windir, SystemRoot, SystemDrive, OS, PROCESSOR_* or NUMBER_OF_* unless you really know what you are doing.
Use exit /B or goto :EOF instead of exit if you don't want to close a console window while you are testing...

Resources