I have to execute the ewfmgr.exe which can be executed only when the Command window is opened as an Admin.
If I go to Start->type cmd.exe->Right click->Run as Administrator then the following Comand prompt windows appear. In this window, if I write ewfmgr.exe (which the EXE file that I have to execute), then the command is executed without any problem.
MY REQUIREMENT: My requirement is to execute the command through a script file (RunasAdmin.cmd file) and this script file will be executed through an NSIS Installer.
Since, the ewfmgr.exe can be executed only with Admin, so I have to escalate my script to get Admin rights. For this, I have the following script:
Script to Get Admin Right: "(RunasAdmin.cmd)"
::::::::::::::::::::::::::::::::::::::::::::
:: Elevate.cmd - Version 4
:: Automatically check & get admin rights
::::::::::::::::::::::::::::::::::::::::::::
#echo off
CLS
ECHO.
ECHO =============================
ECHO Running Admin shell
ECHO =============================
:init
setlocal DisableDelayedExpansion
set cmdInvoke=0
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 **************************************
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 & pushd .
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
ewfmgr c: -enable
pause
cmd /k
PROBLEM: If I execute the script (RunasAdmin.cmd) just by double clicking on it, the script is executed and get the task done without any error. But if I execute the script (RunasAdmin.cmd) through my NSIS installer (EWFMGR_Run.exe) then, I get an error that ewfmgr is not recognised as an internal or external command
NSIS Installer Code: (EWFMGR_Run.exe)
OutFile "EWFMGR_Run.exe"
section
ExecWait "D:\Disk\RunasAdmin.cmd"
sectionEnd
The Command window opened by NSIS installer after running the script (RunasAdmin.cmd) is following:
Queston: Both the command windows are opened as Admin and are into the same working directory. But how come the window opened by NSIS installer is not able to find ewfmgr.exe?
UPDATE-1: Bascially the problem is in the CMD window opened by the NSIS installer. Even if I move to the path C:\Windows\System32 manually using cd /D C:\Windows\System32 and try to execute ewfmgr.exe (which is available in that path), CMD does not recognize it.
References: The script file to elevate to Admin has been taken from Matt's answer given here.
What must be at least taken into account on elevating a command script (batch file) to administrator level?
The current directory changes in any case to %SystemRoot%\System32.
The environment could change completely if the current user is not in administrator group and therefore the user has to use a different user account to run the batch file with elevated privileges of an administrator, for example the local administrator account must be used instead of current user account. This affects environment variables and permissions on network resources.
The script is started initially always in environment of parent process which is on 64-bit Windows the 32-bit environment instead of the 64-bit environment in case of parent process is a 32-bit application.
The script could be executed with one or more arguments enclosed in double quotes which should be passed right to the script on execution with elevated privileges.
How to handle those 4 points?
1. Current directory
Many command line scripts (batch files) are coded to work with current directory and assume that the current directory is the same directory as the batch file. That the current directory is the same directory in which the batch file is stored is true on double clicking on a batch file stored on a local drive or a network drive, except the execution of batch files from network drives is disabled by security settings.
But Windows sets %SystemRoot%\System32 as current directory on running a cmd script as scheduled task using system account.
And Windows sets %SystemRoot%\System32 as current directory on using RunAs to run a cmd script with elevated administrator privileges.
And Windows sets %SystemRoot% as current directory after printing into console window the message below on executing a batch file with a double click which is stored on a network share opened using UNC path.
'\server\share\directory'
CMD.EXE was started with the above path as the current directory.
UNC paths are not supported. Defaulting to Windows directory.
Using UNC paths as current directory could be enabled as described for example by an answer on How to run batch file from network share without "UNC path are not supported" message?
The best would be to write the entire script code to work independent on which directory is the current directory.
That means not using just the file name of a referenced file, but "Full path to\FileName.exe", i.e. the file name with file extension and with full path enclosed in double quotes.
In case of all files to run or referenced from within a cmd script are stored in an unknown folder, but are always in same folder as the cmd script, the simple method to get path for all files is using the command line:
set "SourceFolder=%~dp0"
%~dp0 expands to path of the batch file always ending with a backslash and never being enclosed in double quotes even if the folder path contains a space character or other command line syntax critical characters like an ampersand.
Then all files are referenced with using
"%SourceFolder%FileName.exe"
Note: There is no backslash (directory separator on Windows) as the environment variable SourceFolder holds the folder path already with a backslash at end.
Of course it is also possible to use cd /D "%~dp0" to set current directory to the directory of the cmd script, but this does not work for UNC paths.
But there is also the command pushd "%~dp0" working also with UNC paths if command extensions are enabled as by default.
For details on the commands CD and PUSHD run in a command prompt window cd /? and pushd /? and read the output help.
2. Environment variables
Windows creates a copy of the currently active environment table of current process whenever a new process is created.
But this is not the case when a batch file elevates itself to administrator level. Therefore it is not possible to define environment variables on initial run of a batch file, then elevate to administrator level, and access now the environment variables as defined before in initial environment. It could even happen that the batch file was initially executed in 32-bit environment on 64-bit Windows on initial execution, but runs in 64-bit environment after elevation to administrator level.
So everything which needs to be passed from initial execution to elevated execution must be parsed via command line arguments or via a file on a local drive fully accessible in all environments, i.e. for everyone.
3. 32-bit versus 64-bit environment
Sometimes a 32-bit installer is used for installing either a 32-bit or a 64-bit application depending on bit width of Windows because of running on all Windows. The batch file is processed by 32-bit cmd.exe in 32-bit environment on using a 32-bit installer even when executed on a 64-bit Windows.
At least the following three Microsoft articles should be studied carefully before reading further:
File System Redirector
WOW64 Implementation Details
Registry Keys Affected by WOW64
It is definitely no good idea to depend on value of environment variable PROCESSOR_ARCHITECTURE as its value is x86 when a 32-bit process is executed on 64-bit Windows in 32-bit environment.
It is also not good to query the architecture of the processor directly from Windows registry. It is not guaranteed that there is a 64-bit Windows running on a computer with a 64-bit CPU. It is not often done, but nevertheless possible to use 32-bit Windows on a computer with a 64-bit processor on main board.
The environment variable ProgramFiles(x86) is not defined by default on 32-bit Windows as it is on 64-bit Windows which can be used to determine if a command file script is running on 32-bit or 64-bit Windows.
And the file %SystemRoot%\Sysnative\cmd.exe exists only for 32-bit processes running in 32-bit environment on 64-bit Windows because of special alias Sysnative existing only for a 32-bit process in 32-bit environment on 64-bit Windows which can be used to determine in which environment the batch file is currently running.
4. Passing arguments
It is easy to elevate a batch file executed without any arguments to elevated administrator level.
It is also no problem to pass simple arguments which do not need to be enclosed in double quotes to batch file running elevated.
But passing one or more arguments containing at least one space character or one of these characters &()[]{}^=;!'+,`~<|> which require enclosing the argument string in double quotes is really not easy, especially on creating a Visual Basic script from within a batch file to elevate to administrator level.
It is a real nightmare to try to encode double quotes in batch file correct to be passed via the VB script to the same batch file executed with elevated privileges. Most solutions provided in World Wide Web simply don't support double quoted parameters. Matt's Elevate.cmd - Version 4 is no exception. Running a batch file using this code with "%ProgramFiles%\Installation Folder" as first argument results on initial execution in "C:\Program Files\Installation Folder" being the first and only argument and on elevated execution after removing argument ELEV in the three arguments C:\Program, Files\Installation and Folder.
5. Possible solution for this task
For this task a 32-bit NSIS installer is calling a command line script which must elevate itself to administrator level and should run on 64-bit Windows in 64-bit environment instead of 32-bit environment as on initial run.
I have once analyzed the batch and VB script code of Matt's Elevate.cmd - Version 4, have removed all useless code, have enhanced it to support also arguments enclosed in double quotes using a much easier method than other posted, and have commented the code completely so that others can also understand it for answering UNC paths as current directories in batch file where admin rights are requested.
The batch script posted there is written to work independent on what is the current directory for working also with batch file being executed from a network share using UNC path which of course works only if the network share is still accessible according to permission settings of the share after elevation to administrator level. I found out today after a comment by Richard on his answer on Open Command Window in Windows x64 mode that the web page SS64 - Run with elevated permissions contains nearly the same code as I developed without having ever read the code there.
The adapted batch file code below should work for this task. It expects the executable ewfmgr.exe in same directory as the cmd script or ewfmgr.exe is specified with full path as first argument on executing the script in case of being in a different directory.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
rem Define as application to run by default the file ewfmgr.exe in folder
rem of the batch file which can be a folder on a local drive or on a
rem network drive or even a UNC path.
set "AppToRun=%~dp0ewfmgr.exe"
set "vbsGetPrivileges=%TEMP%\OEgetPriv_%~n0.vbs"
rem The console application NET with parameter FILE can be executed
rem successfully only if the account used for running this batch file
rem has local administrator privileges. See the Microsoft documentation
rem https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490702(v=technet.10)
rem for details about NET FILE.
rem The output written to handle STDOUT on successful execution is redirected
rem to device NUL to suppress it. The exit code of NET assigned to ERRORLEVEL
rem is in this case 0 indicating a successful execution.
rem But on a failed execution because of not having administrator
rem privileges NET outputs to handle STDERR the two error messages
rem "System error 5 has occurred." and "Access is denied." which
rem are redirected from handle STDERR to device NUL to suppress them.
rem And exit/return code of NET is 1 indicating a failed execution.
rem Read https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490982(v=technet.10))
rem for details about using command redirection operators.
%SystemRoot%\System32\net.exe FILE >nul 2>nul
if not errorlevel 1 goto RunMainCode
if "%~1" == "ELEV" (
rem This condition is just for safety. If the batch file was started
rem already a second time with ELEV as first parameter and execution
rem of NET FILE failed nevertheless because of missing permissions,
rem the batch file outputs an error message, waits for any key press
rem by the user to make sure that the user had the chance to read the
rem error message and then exits the batch file processing without
rem doing anything at all.
echo %~nx0 should run already with elevated privileges, but it isn't.
echo/
echo Press any key to exit %~nx0 without doing anything ...
pause >nul
goto :EOF
)
rem This batch file can be started without any parameter resulting in %* being
rem expanded to nothing which results in environment variable BatchArgs being
rem deleted if already existing or with ewfmgr.exe with full path as parameter
rem which must be enclosed in double quotes in case of path containing
rem one or more spaces.
rem As the batch file needs to be executed once again in a separate command
rem process running as local administrator for full access at least on local
rem machine it is necessary to prepare the parameters/arguments list. Each
rem double quote in the arguments list must be doubled to be correct escaped
rem in the VB script file.
rem This is necessary as otherwise running this batch file with
rem "Full path to\ewfmgr.exe"
rem as first parameter would result in execution of the batch file by the
rem Windows Scripting Host as Full path to\ewfmgr.exe without the double
rem quotes as arguments for the batch file and therefore the first parameter
rem is on elevated execution "Full" instead of "Full path to\ewfmgr.exe" as
rem it was initially.
rem Many "run as administrator" solutions which can be found in World Wide Web
rem don't handle parameter strings correct which are enclosed in double quotes
rem because the parameter string has one or more spaces or other critical
rem characters requiring enclosing the parameter string in double quotes.
set "BatchArgs=%*"
setlocal EnableDelayedExpansion
if defined BatchArgs set "BatchArgs= !BatchArgs:"=""!"
rem Everything output by the ECHO command lines within the next command block
rem is redirected into the VB script file created in the folder for temporary
rem files of current user with name of batch file in VB script file name. This
rem makes it possible that multiple batch files with different names can run
rem at the same time using same code for creating a VB script file to run the
rem batch file once again as administrator with elevated privileges.
rem For details on ShellExecute parameters see the Microsoft documentation
rem https://learn.microsoft.com/en-us/windows/win32/shell/shell-shellexecute
rem The tricky part is quoting the arguments list correct which should be
rem finally passed to cmd.exe executed from the VB script. The command process
rem running the batch file with elevated privileges of local administrator
rem should automatically close after execution of batch file finished which
rem is the reason for first argument /C.
rem The second argument is the command to execute by `cmd.exe` which is
rem the batch file name with complete path which must be enclosed in double
rem quotes for safety in case of batch file name or path contains one or more
rem spaces. But additionally the batch file itself must be started with at
rem least two more arguments.
rem The first argument for the batch file is ELEV which is used as indication
rem to detect if this batch file is already started a second time via the
rem VB script using local built-in administrator account.
rem The second argument for the batch file is the application to
rem run with full default path which is the batch file folder.
rem And last all parameters passed to this batch file on initial run should
rem be also passed to second execution of this batch file under the different
rem environment of local built-in administrator account.
rem This nesting of batch file arguments in command processor arguments written
rem into a VB script file which requires additionally escaping each double quote
rem within a string with one more double quote results in a strange syntax for
rem the line to write into the VB script file.
(
echo Set UAC = CreateObject^("Shell.Application"^)
echo UAC.ShellExecute "%SystemRoot%\System32\cmd.exe", "/C """"%~f0"" ELEV ""!AppToRun!""!BatchArgs!""", , "runas", 1
)>"%vbsGetPrivileges%"
endlocal
rem Now the created VB script file can be executed with Windows Script Host.
rem Then the VB script file can be deleted as no longer needed and processing
rem of this batch file under current user account ends resulting in returning
rem to command process which results in closing the console window if not
rem executed by cmd.exe started with option /K to keep the console window
rem opened like on opening a command prompt window and running this batch
rem file from within the command prompt window.
%SystemRoot%\System32\WScript.exe "%vbsGetPrivileges%"
del "%vbsGetPrivileges%"
endlocal
goto :EOF
rem Here starts the main code of the batch file which needs to be
rem executed with elevated privileges of a local administrator.
rem First is checked if the first parameter of the batch file is ELEV
rem which indicates that this batch file was started a second time
rem using administrator privileges or local administrator account.
:RunMainCode
if "%~1" == "ELEV" (
rem In this case the second argument is the application to run with
rem batch file folder passed from initial run to this second run of
rem the batch file. The current directory is now not anymore the initial
rem current directory, but %SystemRoot%\System32 as set by Windows on
rem starting a command process using RunAs and administrator account.
rem This must be taken into account on further batch file processing.
rem For this batch file it does not matter what is the current directory
rem as it is written to work with path of the application to run defined
rem on starting the batch file (initially). So there is no need to use
rem CD /D "%~dp0" or PUSHD "%~dp0" as many "run as administrator"
rem solutions use to change the current directory to directory of the
rem batch file. There is also no need for CD /D "%~2" or PUSHD "%~2"
rem here which of course could be also used.
rem The two additionally added arguments ELEV and the application to
rem run are removed from the arguments lists by using twice the
rem command SHIFT to restore the initial arguments list.
set "AppToRun=%~2"
shift /1
shift /1
)
if "%ProgramFiles(x86)%" == "" goto RunApp
if not exist %SystemRoot%\Sysnative\cmd.exe goto RunApp
%SystemRoot%\Sysnative\cmd.exe /C ""%~f0" %*"
endlocal
goto :EOF
rem If this batch file was started (initially) with a parameter string,
rem interpret the first parameter string as application to run with
rem full path if the specified executable file exists at all.
rem Then run the application with full path and its parameters.
:RunApp
if not "%~1" == "" (
if exist "%~1" set "AppToRun=%~1"
)
"%AppToRun%" c: -enable
endlocal
6. Best solution for this task
But it turned out after I finished writing and testing the code above, writing this long answer and before posting it, reading the comment written by Richard on his answer on Open Command Window in Windows x64 mode, the best solution is most likely using the NSIS code as posted at
How do you request administrator permissions using NSIS?
And use in the command script just the few lines at bottom also posted as my answer on Open Command Window in Windows x64 mode to switch from 32-bit to 64-bit environment.
Try modifying RunasAdmin.cmd to use Sysnative instead of System32:
set winSysFolder=Sysnative
I am guessing that EWFMGR_Run.exe is launching a 32 bit cmd window and windows is forcing the c:\windows\syswow64 override on your attempted override of set winSysFolder=System32
According to this article, you should be using the Sysnative virtual folder instead.
The 'Sysnative' folder
As explained above, accessing the 64-bit System32 folder from a 32-bit application by simply inserting "\System32" in the folder path is not possible. A redirection to the SysWOW64 folder is made automatically by the system if you try that. But there is another folder name that can be used instead: Sysnative.
Sysnative is a virtual folder, a special alias, that can be used to access the 64-bit System32 folder from a 32-bit application or script. If you for example specify this folder path in your application's source code:
C:\Windows\Sysnative
the following folder path is actually used:
C:\Windows\System32
I'd like to point to an NSIS specific way about dealing with UAC and elevated rights.
If your NSIS installer needs to run anything with elevated permissions, you have to indicate that in your NSIS script like so:
RequestExecutionLevel admin
Once you do that, when you start the installer, it will pop up the UAC prompt and in succession won't have any problems running external scripts or programs which need elevated permissions.
This is pretty much in line with #5 of Mofi's answer - I still post this one as I think it boils it down to the need-to-know. NSIS seems to be the show-stopper here for you.
For reference:
NSIS - Could not write updated PATH to HKLM
Related
The following illustrates a problem I found using for /f %l in ('<command>') do #(echo %l). (/f is the for command's parameter for "iterating and file parsing.") This works as expected when <command> is cd or chdir, but not when <command> is pushd:
C:\>cd
C:\
C:\>for /f %l in ('cd') do #(echo %l)
C:\
C:\>chdir
C:\
C:\>for /f %l in ('chdir') do #(echo %l)
C:\
C:\>pushd Windows
C:\Windows>pushd
C:\
C:\Windows>for /f %l in ('pushd') do #(echo %l)
C:\Windows>pushd > nul
C:\Windows>
I would expect the last command to print C:\ but it does not execute the do block at all. I added pushd > nul to check that pushd prints to stdout.
How can I process the output of pushd in a for /f loop?
Information about command processing by FOR /F
The usage of a for /F loop with a command enclosed in ' or in ` on using usebackq results in starting in background one more command process using %ComSpec% /c and the command appended as additional argument(s).
The usage of for /f %l in ('pushd') do #(echo %l) results in background execution of:
C:\Windows\System32\cmd.exe /c pushd
That can be seen by downloading, extracting and running the free Windows Sysinternals tool Process Monitor as administrator which logs the execution of two cmd.exe processes with different process identifiers on running the commands as posted in the question. There must be double clicked on any line in log of Process Monitor of second cmd.exe in the middle of the log to open the Event Properties window and selected the second tab Process to see the Command Line which was used by first cmd.exe to start the second cmd.exe for the execution of the command pushd.
There cannot be executed just a single command with a for /F loop. There can be executed an entire command line which can even have multiple commands. But it is necessary to take into account all the information given by the usage help of cmd output on running cmd /? in a command prompt window on using a complex command line with for /F and processing its output as written to handle STDOUT of in background started cmd.exe.
Command operators like &, && and || as well as redirection operators like |, 2> and 2>&1 in the command line to execute by for /F are processed by two cmd.exe, first the one executing the entire for /F loop and another one started in background with the command line of which output is of interest. That is the reason why many for /F loops with a complex command line are with the escape character ^ left to each & and | and > in the command line to get these characters interpreted literally by cmd.exe parsing and executing the entire for /F loop while being interpreted as command/redirection operators by the second cmd.exe started in background which is really executing the command line.
Information about output of PUSHD
The Windows command PUSHD outputs on execution without any directory path the list of directory paths pushed on stack of current command process.
There can be executed in a command prompt window following commands:
cd /D %SystemDrive%\
pushd %SystemRoot%
pushd inf
pushd
popd
popd
The fifth command pushd results usually for typical Windows installations in the output:
C:\Windows
C:\
But there is nothing output by using as fifth command instead of pushd the command line:
for /F "delims=" %I in ('pushd') do #echo %I
The reason is the execution of pushd by one more cmd.exe started in background which has no directory paths pushed on its stack. The internal command PUSHD does not output anything at all for that reason to handle STDOUT of in background started cmd.exe.
The command process running for /F cannot capture any output text. The for /F loop cannot process therefore any line after cmd.exe started in background finished the execution of pushd and closed itself.
How to process the directory paths pushed on stack?
It would be necessary to use the following command lines in the command prompt window to process the directory paths pushed on stack of the current command process:
pushd >"%TEMP%\DirectoryList.tmp"
if exist "%TEMP%\DirectoryList.tmp" for /F "usebackq delims=" %I in ("%TEMP%\DirectoryList.txt") do #echo %I
del "%TEMP%\DirectoryList.tmp" 2>nul
The redirection of the output of PUSHD executed by the command process which executed also the two pushd command lines before into a temporary file makes it possible to process the directory paths pushed on stack of current command process.
The for /F option usebackq is necessary to get the string inside " interpreted as file name of which lines to process by the FOR loop and not as string to process.
The for /F option delims= is necessary to define an empty list of string delimiters as a directory path can contain one or more spaces. There is by default split up a line into substrings using normal space and horizontal tab as string delimiters and assigned to the loop variable is just the first space/tab delimited string instead of the entire directory path. The line splitting is turned off by the definition of an empty list of delimiters. The entire directory path is assigned therefore always to the loop variable in this case with processing always full directory paths never starting with the default end of line character ; as the first character is always either a drive letter or a backslash in case of a UNC directory path.
Why does the command CD work with a FOR /F loop?
The command CD works also with for /F "delims=" %I in ('cd') do #echo %I because of cmd.exe calls the Windows kernel library function CreateProcess on starting the additional command process for execution of a command specified as set of a for /F loop with value NULL for the function parameter lpCurrentDirectory. The current directory of in background started cmd.exe is set by CreateProcess for that reason with the current directory of the command process executing the for /F command line. Both running command processes have the same current directory during the execution of the command of a for /F loop.
Information about environment variable ComSpec
ComSpec is an environment variable defined by default with %SystemRoot%\system32\cmd.exe (with s at beginning of System32 instead of S as the folder name is in real by default) as system environment variable stored in Windows registry under the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment.
It is really not advisable to ever modify or even delete the environment variable ComSpec neither in local environment of a running process nor in the Windows registry. That would cause a lot of programs to stop working normally as lots of executables depend internally on the correct definition of this environment variable including cmd.exe itself.
The reason is that many applications and scripts use internally the function system which uses the environment variable ComSpec to start on Windows cmd.exe for execution of a command line.
There are many batch files running internal command ver of cmd.exe to get the Windows version like:
for /F "tokens=2 delims=[]" %%G in ('ver') do for /F "tokens=2" %%H in ("%%G") do echo %%H
That works only for Windows NT based Windows versions using cmd.exe as command processor and not for older Windows versions using COMMAND.COM like Windows 95/98/ME for processing a batch file. It works only with enabled command extensions which are enabled by Windows default, but can be disabled by command setlocal DisableExtension in a batch file, on starting cmd.exe with /E:OFF or by a registry value which should be really never used and therefore not written here. The command ver must be really executed by %SystemRoot%\System32\cmd.exe because of in real is output the version of cmd.exe and not the version of Windows.
However, that command line is very good to demonstrate what happens on usage of for /F if the environment variable ComSpec is not defined in environment of cmd.exe on running the batch file with the two for /F loops.
ComSpec usage by cmd.exe on Windows XP
On Windows XP is always called C:\WINDOWS\system32\cmd.exe /c ver even on doing following:
Copying cmd.exe to the directory F:\Temp\system32.
Starting F:\Temp\system32\cmd.exe with a double click in Windows Explorer.
Running set ComSpec=F:\Temp\system32\cmd.exe and set SystemRoot=F:\Temp and set SystemDrive=F: and set windir=F:\Temp.
Verifying with echo %__APPDIR__% that F:\Temp\system32\ is output.
Running the batch file with the command line as posted above.
That can be seen with Process Monitor v3.61 on Windows XP x86.
There can be even modified the local environment variable PATH to begin with F:\Temp\system32 instead of C:\WINDOWS\system32 or the local environment variable ComSpec is deleted with set ComSpec=. The Windows Command Processor of Windows XP in directory F:\Temp\system32 calls nevertheless always C:\WINDOWS\system32\cmd.exe with the option /c and the command ver on running the batch file immediately after closing the batch file containing just the single command line in the text editor.
There cannot be seen in Process Monitor even a Windows registry access by cmd.exe of Windows XP to get any directory path.
Please read further why cmd.exe of Windows XP in a different directory than %SystemRoot%\System32\cmd.exe calls nevertheless the command processor executable in Windows system directory even after modification of local environment variable ComSpec or its deletion.
ComSpec usage of cmd.exe on Windows Vista/7/8/8.1/10/11
The same procedure as described above for Windows XP can be executed also on Windows 7 x64 and newer 64-bit Windows versions.
The double clicked 64-bit F:\Temp\system32\cmd.exe copied from C:\Windows\System32 outputs on Windows 7 and currently latest Windows 11 22H2 first:
The system cannot find message text for message number 0x2350 in the message file for Application.
(c) Microsoft Corporation. All rights reserved.
Note: The copyright message line depends on version of cmd.exe. The output above is from cmd.exe of version 10.0.22621.963 (Windows 11 22H2).
The Windows Command Processor cmd.exe of version 6.1.7601 of Windows 7 outputs additionally the line:
Not enough storage is available to process this command.
The execution of the internal command ver executed in the command prompt window opened with a double click on F:\Temp\System32\cmd.exe fails on Windows 7 and all later Windows versions up to currently latest Windows 11 22H2 which is the reason for the strange output on starting F:\Temp\System32\cmd.exe with a double click.
There can be redefined also the environment variables as described above and done on German Windows XP by me on my tests. A verification of the output of echo %__APPDIR__% works and shows F:\Temp\system32\ as expected. So, it works to access the string value of the internal dynamic variable of cmd.exe even on executed cmd.exe is not in the directory %SystemRoot%\System32.
But the batch file execution from within the command prompt of F:\Temp\system32\cmd.exe as on Windows XP results in no output at all.
In log of Process Monitor v3.70 or a newer Process Monitor version can be seen that on Windows 7 and Windows 11 22H2 is executed F:\Temp\System32\cmd.exe /c ver. That means it is indeed possible that another cmd.exe is executed instead of C:\Windows\System32\cmd.exe on Windows 7 and newer Windows versions.
But why is no version output?
Well, the execution of ver results in the output of the error message:
The system cannot find message text for message number 0x2350 in the message file for Application.
64-bit cmd.exe is not working in F:\Temp\system32 at all.
The usage of 32-bit cmd.exe copied from C:\Windows\SysWOW64 to F:\Temp\system32 makes no difference. The same error messages are output already on starting F:\Temp\system32\cmd.exe and on running next ver or the batch file after modification of local environment variable ComSpec.
Conclusion: cmd.exe of Windows Vista and newer versions are not fully working on being stored outside the appropriate system directory %SystemRoot%\System32 or %SystemRoot%\SysWOW64 while cmd.exe of Windows XP works fine in any directory.
Caching of ComSpec value
I found out with lots of further tests that the string value of the environment variable ComSpec is indeed used to find cmd.exe to run command ver on execution of the batch file with the for /F loop. But there is a caching mechanism on Windows Vista and newer Windows versions.
There must be run immediately set ComSpec=F:\Temp\system32\cmd.exe after starting F:\Temp\system32\cmd.exe before running the batch file. This results in calling F:\Temp\system32\cmd.exe /c ver. If there is next executed set ComSpec=C:\Windows\System32\cmd.exe to redefine the variable with correct value and run the batch file once again, there is nevertheless run now not working F:\Temp\system32\cmd.exe /c ver.
The same caching mechanism can be seen on starting F:\Temp\system32\cmd.exe, then running the batch file resulting in calling in background C:\Windows\System32\cmd.exe /c ver, next running set ComSpec=F:\Temp\system32\cmd.exe and running now the batch file again. There is executed once again C:\Windows\System32\cmd.exe /c ver although the value of the environment variable ComSpec is now F:\Temp\system32\cmd.exe.
It looks like cmd.exe of Windows Vista/7/8/8.1/10/11 reads the value of ComSpec only once and keeps its value in memory for further usage without reading the environment variable a second time on string value for the default command interpreter being already in its internal memory.
That is quite clever in my opinion. The string value of ComSpec is read only once from the environment variable on first usage and then is used that string internally on further usage because of the value of the environment variable ComSpec changes usually never as long as cmd.exe is running.
It looks like cmd.exe of Windows XP has nearly the same caching mechanism for the string value of the environment variable ComSpec. The difference is that cmd.exe of Windows XP reads the value of ComSpec already on starting it and not on first usage and so all changes done on local environment variable ComSpec of an already running cmd.exe have no effect on the execution of one more cmd.exe on processing a for /F loop with a command line to execute, capturing the output and processing it as I could find out with further tests on German Windows XP.
I have a batch file that is in the same directory as the file I want to xcopy. But for some reason the file is not being found.
I thought that current directory was always where the batch file was located.
I run batch file as administrator. This occurs on a Windows 7 64-bit desktop computer.
Batch file:
#ECHO OFF
XCOPY /y "File1.txt" "File2.txt"
PAUSE
Error:
File not found - File1.txt
0 File(s) copied
Which directory is current working directory on starting a batch file with context menu item Run as administrator depends on User Account Control (UAC) setting for the current user.
This can be demonstrated with following small batch file C:\Temp\Test.bat:
#echo Current directory is: %CD%
#pause
With having selected in User Account Control Settings
Default - Notify me only when programs try to make changes to my computer
Don't notify me when I make changes to Windows settings
and using Run as administrator, Windows uses registry key
HKEY_CLASSES_ROOT\batfile\shell\runasuser\command
This registry key does not contain a default string for executing the batch file. Instead there is the string value DelegateExecute with the CLSID {ea72d00e-4960-42fa-ba92-7792a7944c1d}.
The result is opening a dialog window with title User Account Control and text:
Do you want to allow the following program to make changes to this computer?
Program name: Windows Command Processor
Verified publisher: Microsoft Windows
After confirmation by the user, Windows opens temporarily a new user session like when using on command line RunAs.
In this new user session the current working directory is %SystemRoot%\System32 on executing now the command defined in Windows registry with default string of key
HKEY_CLASSES_ROOT\batfile\shell\runas\command
which is:
%SystemRoot%\System32\cmd.exe /C "%1" %*
Therefore a console window is opened with title C:\Windows\System32\cmd.exe and the 2 lines:
Current directory is: C:\Windows\System32
Press any key to continue . . .
After hitting any key, batch execution finishes which results in closing cmd.exe which results in closing the user session.
But with having selected in User Account Control Settings
Never notify me when
Programs try to install software or make changes to my computer
I make changes to Windows settings
the behavior is different as the user has already elevated privileges.
Now Windows uses directly the command
%SystemRoot%\System32\cmd.exe /C "%1" %*
according to default string of key
HKEY_CLASSES_ROOT\batfile\shell\runas\command
in current user session.
The result is opening a console window also with title C:\Windows\System32\cmd.exe, but displayed in window is:
Current directory is: C:\Temp
Press any key to continue . . .
The current working directory of the parent process (Windows Explorer as desktop) is used for executing of the batch file because no switch to a different user session was necessary in this case.
PA has posted already 2 possible solutions in his answer which I replicate here with a small improvement (pushd with directory in double quotes) and with adding a third one.
Change current directory to directory of batch file using pushd and popd:
pushd "%~dp0"
%SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
popd
This works also for UNC paths. Run in a command prompt window pushd /? for an explanation why this also works for UNC paths.
Use directory of batch file in source and destination specifications:
%SystemRoot%\System32\xcopy.exe "%~dp0File1.txt" "%~dp0File2.txt" /Y
Change working directory to directory of batch file using cd:
cd /D "%~dp0"
%SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
This does not work for UNC paths because command interpreter cmd does not support a UNC path as current directory by default, see for example CMD does not support UNC paths as current directories for details.
The error message is very self explanatory. The file file1.txt is not found.
Because the file name does not include an absolute path, the system tries to find it on the current directory. Your current directory does not contain this file.
Your misconception is that the current directory is not the directory that contains the bat file. Those are two unrelated concepts.
You can easily check by adding this two commands in your bat file
echo BAT directory is %~dp0
echo Current directory is %CD%
you can notice they are different, and that there is a subtle difference in the way the last backslash is appended or not.
So, there are esentially two ways to cope with this problem
either change the current directory to match the expected one
pushd %~dp0
XCOPY /y "File1.txt" "File2.txt"
popd
or specify the full path in the command
XCOPY /y "%~dp0File1.txt" "%~dp0File2.txt"
For the sake of completeness and obscurity, I add another workaround, confirmed as working under Windows 8.1 and expected to work elsewhere, as it relies on documented functionality:
You can change the runas command definition keys
HKEY_CLASSES_ROOT\batfile\shell\runas\command and
HKEY_CLASSES_ROOT\cmdfile\shell\runas\command into
%SystemRoot%\System32\cmd.exe /S /C "(for %%G in (%1) do cd /D "%%~dpG") & "%1"" %*
Which results in the bat or cmd file starting in its containing directory when started using the runas verb, respectively the "Run as Administrator" menu entry.
What the additions to the original command exactly do:
cmd /S strips away first and last (double) quote in command string after /C
for %%G in (%1) do enumerates its single entry, the %1 argument,
making it available for expansion as %%G in the loop body; the letter is arbitrary but some may be "reserved"
%%~dpG expands to the drive and path of %%G, the ~ tilde stripping away quotes if present, which is why we add them back explicitly
cd /D changes both the drive and directory to its argument, and finally
& runs the second command "%1" %* regardless of success of the first one.
You can use pushd which will even support UNC paths, but a stray popd would land any script in the system32 directory, not a behavior I would be fond of.
It should be possible to do this for the exefile entry as well, but frankly, I'd rather live with the inconsistency than to attempt this on my system, as any error there could break a lot.
Enjoy defeating the security mechanics of your operating system :)
I am trying to convert a bash script to batch, but I am having a trouble for this one issue. The script runs a java server in the background, waits for 5 seconds, then exit with exitcode. But in batch, I am unable...
runserver.sh
java -jar java_server.jar &
pid=$!
sleep 5
kill -0 $pid
cmdStatus=$?
if [ $cmdStatus -ne 0 ]
then
exit 1
fi
exit 0
runserver.bat
#echo off
set EXITCODE=0
start /b java -jar java_server.jar
timeout /t 5
for /f "tokens=1 delims= " %%i in ('jps -m ^| findstr java_server') do set PID=%%i
IF "%PID" == "" (
set EXITCODE=1
)
EXIT EXITCODE
but if I run the above batchscript, I am never able to disown the java process and it never exists
The first mistake in batch file code is missing % in line IF "%PID" == "" to really compare the value of the environment variable PID enclosed in double quotes with the string "". So correct would be IF "%PID%" == "". For more details on string comparisons see Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files.
The second mistake is not using two times % on last command line EXIT EXITCODE around the environment variable EXITCODE to reference its value which would be correct written as EXIT %EXITCODE%.
But the batch file should be better written as follows:
#echo off
cd /D "%~dp0"
start "" /B javaw.exe -jar "%~dp0java_server.jar"
%SystemRoot%\System32\timeout.exe /T 5 /NOBREAK >nul
jps -m 2>nul | %SystemRoot%\System32\findstr.exe /L "java_server" >nul
The batch file first makes the directory of the batch file the current directory because of java_server.jar is most likely in directory of the batch file. %~dp0 expands to drive and path of argument 0 which is the batch file. The file path referenced with %~dp0 always ends with a backslash which is the directory separator on Windows which should be taken into account on concatenating this string with a file or folder name. The command CD fails only if the batch file is stored on a network resource accessed with a UNC path because of Windows prevents by default that a directory referenced with a UNC path instead of a drive letter becomes the current directory for downwards compatibility reasons because of many console applications work not correct with current directory not being on a drive with a drive letter.
The command START interprets first double quoted string as title for the console window opened on running a Windows console application in a separate command process. In this case no console window is opened because of using option /B although java.exe is a Windows console application. For that reason explicitly specifying an empty string with "" as window title like on starting a Windows GUI application is advisable to avoid that any other double quoted argument string is interpreted as optional window title.
The option /B is interpreted as background. Most people think that the started application is detached from current command process. This is true for Windows GUI applications, but not for console applications like java.exe. The handle STDIN of current command process is bind with STDIN of started console application. Also the output to the handles STDOUT and STDERR of started application are redirected to console window of the running command process which nevertheless continues immediately with execution of batch script. The option /B means just running the console application parallel to current command process without opening a new console window, but not in running the application completely detached from running command process.
The solution is quite simple in this case because there is also javaw.exe, the Windows version of Java designed for running a Java application completely in background detached from the process starting it.
A batch file for general usage works best on specifying all files with full qualified file name which means with full path, file name and file extension. Java executable can be installed everywhere and so the folder path for this executable can't be specified in the batch file. Windows command processor has to find the executable javaw in current directory or in any folder in list of folders of local environment variable PATH. But it is at least possible to specify the executable javaw with its file extension .exe as this file extension is well known.
The JAR file java_server.jar is specified with full path on assuming that this file is stored in same directory as the batch file. The batch file directory should be already the current directory and so %~dp0 would not be needed at all, but it does not matter to specify the file for safety with full path.
Next the standard Windows console application TIMEOUT is called with full qualified file name with options to wait 5 seconds unbreakable (requires Windows 7 or newer) and redirecting its output to device NUL.
I don't know anything about the file jps and have not even installed Java. For that reason I assume jps is an executable or script of which file extension listed in local environment variable PATHEXT and stored in directory of the batch file or any other directory of which path is listed in local environment variable PATH. It would be of course better to specify this file with file extension and if possible also with full path.
The standard output of jps is redirected to standard input of Windows standard console application FINDSTR and the error output to device NUL to suppress it.
FINDSTR runs a case-sensitive, literal string search for java_server on standard output of jps. The output of FINDSTR is of no interest and therefore redirected also to device NUL to suppress it.
FINDSTR exits with 0 on searched string really found and with 1 on nothing found. The batch file should exit with 1 on not successfully starting Java server and with 0 on Java server running. This matches exactly with exit code of FINDSTR and so nothing else must be done.
cmd.exe exits the execution of a batch file always with last exit code set by an application or command during batch file execution. This can be verified on commenting out second command line with rem and save it, running this batch file from within a command prompt window and running next in command prompt window if errorlevel 1 echo Java server not running! resulting in expected output Java server not running!. Then rem needs to be removed from second command line of batch file before saving the batch file which is run once again from within the command prompt window. After second batch file execution finished, running once again if errorlevel 1 echo Java server not running! results in no output while running if errorlevel 0 echo Java server is running. results in output Java server is running. as expected.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... explains %~dp0
cd /?
echo /?
findstr /?
if /?
start /?
See also:
Microsoft article about Using command redirection operators
What are the ERRORLEVEL values set by internal cmd.exe commands?
Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?
My target is to create a folder named _Misc in the current folder using shortcut Ctrl+Alt+M
I created a shortcut in desktop.
In the properties of the shortcut, went to the Shortcut tab and put the following values
Target: C:\Windows\System32\cmd.exe /k mkdir _Misc
Start in: %CD%
Shortcut Key: Ctrl+Alt+M
Now, when I press Ctrl+Alt+M in any folder, for example: D:\Test it says
Access is denied.
C:\Windows\system32>
So I think the the command mkdir _Misc is being invoked from C:\Windows\system32 instead of D:\Test
What do I need to do?
That approach can never work. The shortcut key of a shortcut file can be used anywhere. For example Windows executes the Target command line even when Ctrl+Alt+M is pressed while viewing this page in browser. Which directory should be the current directory on browser having input focus?
A solution is using the Send to context menu which is customizable by adding shortcuts to folder %APPDATA%\Microsoft\Windows\SendTo on Windows Vista / Server 2008 or any later Windows version. The directory is %UserProfile%\SendTo on Windows XP / Server 2003.
Create a batch file in a directory you want with following lines:
#echo off
if "%~1" == "" exit
if not exist "%~1" exit
setlocal EnableExtensions DisableDelayedExpansion
set "Directory=%~1"
if "%Directory:~-1%" == "\" goto MakeDirectory
if not exist "%~1\" for %%I in ("%~1") do set "Directory=%%~dpI" & goto MakeDirectory
set "Directory=%Directory%\"
:MakeDirectory
if not exist "%Directory%_Misc\" md "%Directory%_Misc"
endlocal & if exist "%Directory%_Misc\" cd /D "%Directory%_Misc"
Line two and three result in exiting batch file and started command process if the batch file is called without any argument or with a string which is not an existing directory or file.
The next lines make it possible to Send to a full qualified directory or file name to the batch file. Directory or file names with relative paths are not really supported by this batch file.
Windows Explorer passes a full qualified directory name without a backslash at end to the batch file. But in case of argument one is a string ending with a backslash, the batch file knows at this execution step that first argument references a directory which really exists and so can immediately continue with creating the subdirectory.
The argument can reference an existing directory or an existing file in case of string of argument one is not ending with a backslash as on being invoked from Windows Explorer context menu Send to. For that reason the batch file next checks if the argument string references a file in which case it uses the file path as directory path.
Then the subdirectory _Misc is created if that directory does not already exist. This works even with a UNC path starting with \\ServerName\ShareName\ passed to the batch file.
Last the temporary environment is deleted using endlocal resulting in deletion of environment variable Directory and restoring initial current directory as it was pushed on stack on execution of command setlocal. For that reason the last command line contains two commands: endlocal to restore default environment and an if for checking existence of subdirectory _Misc and making this directory the current directory after execution of endlocal.
Please note that cd /D fails by default if a UNC path was passed to the batch file.
This batch file must be called from a shortcut file created in %APPDATA%\Microsoft\Windows\SendTo on Windows Vista and later versions of Windows with a name you like to see in Send to context menu and with an icon you prefer for this context menu item.
The Target in shortcut properties must be:
%SystemRoot%\System32\cmd.exe /K "Path to batch file\BatchFileName.bat"
Shortcut property Start in can be empty in which case %USERPROFILE% is used by Windows Explorer, or there is an existing directory specified which is used as default current directory in case of last line fails because of subdirectory _Misc could not be created because of missing required permissions.
The started command process keeps running because of option /K after finishing execution of the batch file. The command process would be closed on using option /C instead of /K.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... explains %~1
cd /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
md /?
set /?
setlocal /?
I am trying to start the default application for a file, wait for it to complete, and then continue with my batch file. The problem is that start, when used below simply creates another command prompt window with the example.doc in the title bar. I can use call instead of start, but then call does not wait for the program to finish before going to the next line. It appears that start needs to have an executable name and will not work with the default application system in windows.
Any ideas how I can make this happen without having to hardcode the windows application in the batch file as well?
set filename=example.doc
start /wait %filename%
copy %filename% %filename%.bak
How do I start the default application for a file, wait for completion, then continue?
It appears that start needs to have an executable name and will not work with the default application system in windows.
start, when used below simply creates another command prompt window with the example.doc in the title bar
start /wait %filename%
The above command won't work because %filename% is used as the window title instead of a command to run.
Always include a TITLE this can be a simple string like "My Script" or just a pair of empty quotes ""
According to the Microsoft documentation, the title is optional, but depending on the other options chosen you can have problems if it is omitted.
Source start
Try the following command instead:
start "" /wait %filename%
Alternative solution using the default open command
Any ideas how I can make this happen without having to hardcode the
windows application in the batch file as well?
One way is to use assoc and ftype to get the default open command used for the file then execute that command.
The following batch file does that for you (so no hard coding of windows applications is needed).
Open.cmd:
#echo off
setlocal enabledelayedexpansion
set _file=example.doc
rem get the extension
for %%a in (%_file%) do (
set _ext=%%~xa
)
rem get the filetype associated with the extension
for /f "usebackq tokens=2 delims==" %%b in (`assoc %_ext%`) do (
set _assoc=%%b
)
rem get the open command used for files of type filetype
for /f "usebackq tokens=2 delims==" %%c in (`ftype %_assoc%`) do (
set _command=%%c
rem replace %1 in the open command with the filename
set _command=!_command:%%1=%_file%!
)
rem run the command and wait for it to finish.
start "" /wait %_command%
copy %_file% %_file%.bak 1>nul
endlocal
Further Reading
An A-Z Index of the Windows CMD command line - An excellent reference for all things Windows cmd line related.
assoc - Display or change the association between a file extension and a fileType
enabledelayedexpansion - Delayed Expansion will cause variables to be expanded at execution time rather than at parse time.
for - Conditionally perform a command several times.
for /f - Loop command against the results of another command.
ftype - Display or change the link between a FileType and an executable program.
start - Start a program, command or batch script (opens in a new window).
variable edit/replace - Edit and replace the characters assigned to a string variable.
Simply use the filename directly as command, unless that filename is a batch file, in which case use call.
In a batch file invocation of a GUI subsystem executable is blocking, unlike for an interactive command.
Use the start command when you don't want blocking execution.
There is a subtle point about “default application”, namely that a file type can have a registered default application for the graphical shell, e.g. its “Open with…”, without having an assoc/ftype association, or different from that association.
I'm not entirely sure of which registry entries are used for this. I've always had to look it up and research it each time. As I recall it's not well-documented.
But hopefully you're OK with just the assoc/ftype scheme.
A further subtle point about “default application”: on the laptop I'm writing this on the ftype association for text files is to open them in Notepad:
[H:\forums\so]
> assoc .txt
.txt=txtfile
[H:\forums\so]
> ftype txtfile
txtfile=%SystemRoot%\system32\NOTEPAD.EXE %1
[H:\forums\so]
> _
And this is what the graphical shell (Windows Explorer) will do.
But cmd.exe looks inside files, and if it finds an executable signature then it tries to run the text file as an executable, even in Windows 10:
[H:\forums\so]
> echo MZ bah! >oops.txt
[H:\forums\so]
> oops.txt
This version of H:\forums\so\oops.txt is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher.
[H:\forums\so]
> _