I was reading an article on ss64 about Environment variables in Command Prompt.
Later in the article there exists a table which states environment variables found commonly in Command Prompt. Some of the variables listed there are termed as Volatile (Read Only). A sentence found in the article states:-
Dynamic environment variables are read-only and are computed each time the variable is expanded. When all variables are listed with SET, these will not appear in the list. Do not attempt to directly SET a dynamic variable.
I understood the later two statements. But i can't understand the first one.
Doubts:-
%userprofile% is an non-volatile variable, which resolves to %SystemDrive%\Users\{username}, and %homepath% is a volatile variable which resolves to Users\{Username}. Both commands are quite similar (except for the systemdrive). Then why is one volatile and the other one non-volatile?
What is the criteria for a variable to be dynamic? what makes %appdata% (just an ex.) an non volatile variable?
Dynamic Variables are computed each time the variable is expanded, this makes sense for variables like %CD% %DATE% %TIME% %RANDOM% etc. as they would lose their functionality if they would be non-volatile. But how would it effect %homepath%?
Some non-volatile variables have sort-of dynamic components in them. Ex. %userprofile% has %SystemDrive% and {username} in it's path. Then how are those variables not dynamic?
There are three types of variables of which value is accessed using the syntax %variable% or !variable! on having enabled delayed environment variable expansion with using the option EnableDelayedExpansion of the command setlocal from within a Windows command prompt window or a batch file, i.e. using %SystemRoot%\System32\cmd.exe.
1. Persistent stored variables
There are environment variables stored persistent in Windows registry.
User variables stored in Windows registry under the key:
HKEY_CURRENT_USER\Environment
System variables stored in Windows registry under the key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
The user variables are defined for just the account in which user registry hive they are stored (file %UserProfile%\ntuser.dat). The system variables are defined for all accounts used on a Windows machine (file %SystemRoot%\System32\config\SYSTEM).
The persistent stored variables can be viewed, edited and deleted by opening the Windows Control Panel, clicking on System, clicking next (on left side) on Advanced system settings and clicking on button Environment Variables. The upper half is for the user variables of the current user account and the lower half is for the system variables since Windows XP.
There are defined by default as user variables only TEMP and TMP since Windows XP.
The list of predefined system variables is since Windows XP:
ComSpec
NUMBER_OF_PROCESSORS
OS
PATH
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
TEMP
TMP
windir
There is on Windows Vista and newer Windows versions defined by default also the system variable PSModulePath.
None of the predefined system variables with exception of PATH and PATHEXT should be deleted or modified ever as this could cause lots of troubles which can even result in Windows not starting anymore. I would strongly recommend using a virtual machine on experimenting with the predefined system variables for which a backup of the entire virtual machine image exists before starting the experiments.
1.1 Backup of persistent stored variables
It is advisable to make a backup of the user and system variables before playing around with them by opening a command prompt window and running for example:
md C:\VariablesBackup 2>nul
%SystemRoot%\System32\reg.exe EXPORT HKCU\Environment "C:\VariablesBackup\UserVariables.reg"
%SystemRoot%\System32\reg.exe EXPORT "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "C:\VariablesBackup\SystemVariables.reg"
1.2 Restore of persistent stored variables
Restoring the user variables can be done from within a command prompt window on having made a backup before with:
%SystemRoot%\System32\reg.exe DELETE "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /f
%SystemRoot%\System32\reg.exe IMPORT "C:\VariablesBackup\UserVariables.reg"
Restoring the system variables can be done from within a command prompt window opened as administrator on having made a backup before with:
%SystemRoot%\System32\reg.exe DELETE HKCU\Environment /f
%SystemRoot%\System32\reg.exe IMPORT "C:\VariablesBackup\SystemVariables.reg"
It is advisable to restart Windows on having restored user and system variables from a backup to make sure that really all processes use the restored variables.
1.3 Modification of PATH with a batch file
Batch file programmers thinking about modification of user or system PATH with a batch file should read first:
What is the reason for "X is not recognized as an internal or external command, operable program or batch file"?
Why are other folder paths also added to system PATH with SetX and not only the specified folder path?
How can I use a .bat file to remove specific tokens from the PATH environment variable?
How to search and replace a string in environment variable PATH?
Adding the current directory to Windows path permanently
ATTENTION: It is an absolute NO GO - NEVER EVER to use command SETX with %PATH% in a batch file to modify user or system PATH variable.
The only reason to modify user or system PATH with a batch file on installation of a program (executable or script) is that this program is designed for being mainly used by users from the Windows command line. A program is bad designed if it requires that its directory or one of its subdirectories is in PATH to work at all. A program is very bad designed if it adds a folder path to system PATH left to the folder paths defined by default by Windows.
The system PATH variable should start always with:
%SystemRoot%\System32;%SystemRoot%;%SystemRoot%\System32\Wbem
The Windows system directory is the directory containing most executables and dynamic linked libraries. For that reason it should be always the first directory searched for executables and libraries after the current directory.
2. Windows shell variables
There are a lot more Windows environment variables predefined as it can be seen on:
SS64: How-to: Windows Environment Variables
Wikipedia: Environment variable
These variables are defined by Windows shell which is by default explorer.exe being started on Windows start as Windows shell according to registry value Shell under registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
The Window shell elements noticed most by users are the Windows desktop, the Windows Start menu and the Windows taskbar with the system tray.
The Windows shell defines in its memory lots of environment variables depending on various values in Windows registry for the current user account not stored persistent in Windows registry as described above. The current list of environment variables is copied whenever a new process is created like starting an executable from Windows shell.
The list of environment variables defined by Windows shell consisting of the persistent stored user and system variables and the shell variables for current user account can be seen by opening a command prompt window and running the command SET without any additional argument.
Most of the shell variables are defined from the registry strings under
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
Most of the registry string values exist in User Shell Folders of type REG_EXPAND_SZ and in Shell Folders of type REG_SZ. But there are some registry string values existing only under one of the two registry keys.
See also: How to create a directory in the user's desktop directory?
In this answer is explained in detail how Windows Explorer evaluates these registry string values and handles them on manual modification by a user using regedit.exe or reg.exe on the example of the shell folder Desktop.
The function CreateEnvironmentBlock and the private shell32 function RegenerateUserEnvironment are used by explorer.exe with GetUserNameExW and GetComputerNameExW to create the environment variables list copied by Windows Explorer on starting an executable from Windows shell using the Windows kernel library function CreateProcess.
See also the comments written by Eryk Sun on question Where are the environment variables for cmd.exe stored?
There are some environment variables on 64-bit Windows which depend on starting a 64-bit or a 32-bit executable. Microsoft documented them on WOW64 Implementation Details as follows:
64-bit process:
PROCESSOR_ARCHITECTURE=AMD64 or PROCESSOR_ARCHITECTURE=IA64 or PROCESSOR_ARCHITECTURE=ARM64
ProgramFiles=%ProgramFiles%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles%
CommonProgramW6432=%CommonProgramFiles%
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The ProgramW6432 and CommonProgramW6432 environment variables were added starting with Windows 7 and Windows Server 2008 R2.
32-bit process:
PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=%PROCESSOR_ARCHITECTURE%
ProgramFiles=%ProgramFiles(x86)%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles(x86)%
CommonProgramW6432=%CommonProgramFiles%
The value of the environment variable PROCESSOR_ARCHITECTURE cannot be used to find out from within a batch file if the installed Windows is a 32-bit (x86) or a 64-bit (AMD64) Windows. The value depends on processing of the batch file by either 64-bit %SystemRoot%\Sysem32\cmd.exe or by 32-bit %SystemRoot%\SysWOW64\cmd.exe on 64-bit Windows.
See also the Microsoft documentations:
File System Redirector
Registry Redirector
The usage of environment variables defined by the Windows shell must be done wisely on writing a batch file which should be designed for execution by other accounts on same or a different Windows machine. Many batch files working fine in environment of author of the batch file do not work in environment set up on running the same batch file as scheduled task with system account or on a different machine because of the differences in environment variables list.
The environment variables defined by a process on starting an executable with the Windows kernel library function CreateProcess determine the environment variables which the started executable can use.
Most applications use CreateProcess with value null for the parameter lpEnvironment. Therefore CreateProcess makes a copy of the current environment variables of the current process. For that reason every executable started from Windows desktop, start menu or taskbar gets the environment variables as defined by the explorer.exe instance running as Windows shell.
A really very good coded executable or script using environment variables which are defined by default on Windows verify explicitly that every used environment variable is really defined and use otherwise a suitable default value like C:\Windows on environment variable SystemRoot is not defined with checking if there is really a directory C:\Windows and exit with an appropriate error message on an important environment variable not defined before causing perhaps damage.
SystemRoot is an example for a Windows shell variable defined by explorer.exe as environment variable which is not determined by the registry string values of the shell folders. Some environment variable values should not be modified by a user at any time independent on its real source which no script author ever needs to know as being a Windows implementation detail.
3. Dynamic variables of Windows command processor
There are some variables listed in help of command SET output on running in a command prompt window set /? which cannot be found in the list of environment variables on running just set.
These variables are:
CD
DATE
TIME
RANDOM
ERRORLEVEL
CMDEXTVERSION
CMDCMDLINE
HIGHESTNUMANODENUMBER
The dynamic variables are internal variables of cmd.exe. So these variables are available only in a Windows command prompt window which is a running cmd.exe process or a batch file processed by cmd.exe. The dynamic variables are not available in other executables or scripts as these variables are not environment variables.
The most common used dynamic variables are:
CD
The current directory path not ending with a backlash, except the current directory is the root directory of the drive.
DATE
The current local date in format as defined for the account in region and language settings of Windows.
TIME
The current local time in format as defined for the account in region and language settings of Windows.
ERRORLEVEL
Exit value of previously executed command or program.
RANDOM
A random decimal number between 0 and 32767.
There are some more dynamic variables, but they are rarely used in batch files.
There is additionally the variable __AppDir__ containing the path of currently running cmd.exe always ending with a backslash which is not documented by Microsoft. I recommend not using this undocumented variable as there is no guarantee that a future version of cmd.exe still has this variable. __AppDir__ is on 64-bit Windows, for example, %SystemRoot%\System32\ on 64-bit %SystemRoot%\System32\cmd.exe currently running, or %SystemRoot%\SysWOW64\ on 32-bit %SystemRoot%\SysWOW64\cmd.exe currently running, or any other path on copying cmd.exe to any other folder and starting this copy of cmd.exe. There are some more undocumented dynamic variables listed on SS64 page How-to: Windows Environment Variables which should be used only with special care for the same reason.
The values of the most used dynamic variables are changed dynamically by the Windows command processor itself while the values of the environment variables change only if command SET is used during execution of a batch file to redefine an environment variable. This is an important difference between environment and dynamic variables.
4. Accessing the values of dynamic variables
Every environment variable can be deleted or redefined in the local environment of a processed batch file. There is no environment variable read-only.
cmd.exe contains internally in its code the file extension list .COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC which is used as value for PATHEXT if the environment variable PATHEXT does not exist in local environment variables list to be able to find nevertheless scripts and executables specified on command line or in a batch file without file extension. But cmd.exe does not contain a list of folder paths as fallback list if the environment variable PATH does not exist in local environment. So a batch file writer should be careful on modification of local environment variable PATH for whatever reason.
The values of the dynamic variables are referenced like the values of the environment variables with %variable% or !variable! on enabled delayed expansion. But the Windows command processor always searches first in the current list of environment variables if there is a variable with specified name. Only if there is no environment variable with that name, cmd searches next in its internal list of dynamic variables if there is one with the specified name.
The current value of a dynamic variable cannot be accessed anymore on definition of an environment variable with same name as a dynamic variable. For that reason a batch file writer should never use one of the dynamic variable names as name for an environment variable.
Here is a code which demonstrates what happens if a batch file writer has the really not good idea to define an environment variable with name ERRORLEVEL.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
echo/
echo Define environment variable ERRORLEVEL with string value "014".
echo/
set ERRORLEVEL=014
if %ERRORLEVEL% EQU 12 (echo EQU: ERRORLEVEL is 12.) else echo EQU: ERRORLEVEL is not 12.
if %ERRORLEVEL% == 014 (echo ==: ERRORLEVEL is 14.) else echo ==: ERRORLEVEL is not 14.
if errorlevel 0 (
if not errorlevel 1 (echo IF: ERRORLEVEL is 0.) else echo IF: ERRORLEVEL is greater than 0.
) else echo IF: ERRORLEVEL is less than 0.
echo/
echo Delete the environment variable ERRORLEVEL.
echo/
set ERRORLEVEL=
if %ERRORLEVEL% EQU 12 (echo EQU: ERRORLEVEL is 12.) else echo EQU: ERRORLEVEL is not 12.
if %ERRORLEVEL% == 014 (echo ==: ERRORLEVEL is 14.) else echo ==: ERRORLEVEL is not 14.
if errorlevel 0 (
if not errorlevel 1 (echo IF: ERRORLEVEL is 0.) else echo IF: ERRORLEVEL is greater than 0.
) else echo IF: ERRORLEVEL is less than 0.
echo/
echo In all test cases the value of dynamic variable ERRORLEVEL was 0.
echo/
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal
The output of this batch file is:
Define environment variable ERRORLEVEL with string value "014".
EQU: ERRORLEVEL is 12.
==: ERRORLEVEL is 14.
IF: ERRORLEVEL is 0.
Delete the environment variable ERRORLEVEL.
EQU: ERRORLEVEL is not 12.
==: ERRORLEVEL is not 14.
IF: ERRORLEVEL is 0.
In all test cases the value of dynamic variable ERRORLEVEL was 0.
It can be seen on debugging the batch file that the first IF condition if %ERRORLEVEL% EQU 12 results in replacing %ERRORLEVEL% by the string 014 because of there is the environment variable ERRORLEVEL defined with this string value. The function wcstol is used by command IF because of operator EQU to convert the string 014 being interpreted as octal number because of the leading 0 and the string 12 to 32-bit signed integer values and compare them on equality. Therefore the first condition is true.
The second IF condition if %ERRORLEVEL% == 014 results also in replacing %ERRORLEVEL% by the string 014 because of there is the environment variable ERRORLEVEL defined with this string value. But now the function lstrcmpW is used by command IF because of operator ==. Therefore the second condition is also true.
The third IF condition uses the recommended syntax explained by help of command IF output on running if /? in a command prompt window. It can be seen that using the recommended syntax results in evaluation of the value of the internal dynamic variable ERRORLEVEL of Windows command processor even if an environment variable is defined with name ERRORLEVEL. The recommended syntax to evaluate the exit code of a previously executed command or program works always and anywhere within a batch file as demonstrated here.
See also:
What are the ERRORLEVEL values set by internal cmd.exe commands?
Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?
Further it must be taken into account that accessing current value of a dynamic variable is always on variable reference being expanded by the Windows command processor and not real execution of the command or executable.
A code to demonstrate that difference:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /L %%I in (1,1,3) do (
echo %%DATE%% %%TIME%% is: %DATE% %TIME%
echo ^^!DATA^^! ^^!TIME^^! is: !DATE! !TIME!
echo %%RANDOM%%/^^!RANDOM^^!: %RANDOM%/!RANDOM!
if exist %SystemRoot%\System32\timeout.exe (
%SystemRoot%\System32\timeout.exe /T 3 /NOBREAK >nul
) else (
%SystemRoot%\System32\ping.exe 127.0.0.1 -n 4 >nul
)
)
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal
The output is for example:
%DATE% %TIME% is: 31.01.2021 13:54:30,67
!DATA! !TIME! is: 31.01.2021 13:54:30,68
%RANDOM%/!RANDOM!: 18841/27537
%DATE% %TIME% is: 31.01.2021 13:54:30,67
!DATA! !TIME! is: 31.01.2021 13:54:33,12
%RANDOM%/!RANDOM!: 18841/16705
%DATE% %TIME% is: 31.01.2021 13:54:30,67
!DATA! !TIME! is: 31.01.2021 13:54:36,16
%RANDOM%/!RANDOM!: 18841/32668
%DATE% %TIME% results in printing to console window three times the same date/time because of these two variable references are expanded already by the Windows command processor on parsing the entire command block before command FOR is executed at all. %RANDOM% results in printing three times the same number for the same reason while !RANDOM! prints usually three different numbers.
See also:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Variables are not behaving as expected
if errorlevel number and if not errorlevel number work also within a command block!
The dynamic environment variables can be accessed only with command extensions enabled as by default. Otherwise Windows command processor emulates COMMAND.COM behavior (more or less) of MS-DOS and Windows 95/98/ME not supporting dynamic variables at all as demonstrated by this code:
#echo off
setlocal DisableExtensions DisableDelayedExpansion
echo/
echo With command extensions disabled:
echo/
echo Date/time is: %DATE% %TIME%
echo Current dir: "%CD%"
endlocal
setlocal EnableExtensions DisableDelayedExpansion
echo/
echo With command extensions enabled:
echo/
echo Date/time is: %DATE% %TIME%
echo Current dir: "%CD%"
echo/
for %%I in (%CMDCMDLINE%) do if /I "%%~I" == "/c" pause & goto EndBatch
:EndBatch
endlocal
The output is for example:
With command extensions disabled:
Date/time is:
Current dir: ""
With command extensions enabled:
Date/time is: 31.01.2021 14:17:42,92
Current dir: "C:\Temp\Development & Test!"
The values of dynamic variables can be only read. It is not possible to modify the value of a dynamic variable with command SET as it results in the definition of an environment variable with the name of a dynamic variable which takes precedence over the dynamic variable.
Related
With starting command prompt with cmd /e:off or using setlocal disableExtensions (in a batch file) or excluding them through the registry value HKEY_CURRENT_USER\Software\Microsoft\Command Processor\EnableExtensions (by setting the value to 0) the feature called "extensions" is disabled (turned on by default).
What are the changes that this causes?
Turning off extensions affects some built-in environment variables and some internal for the command prompt commands. I've never seen full list of non-working variables ,though the internal commands are more or less well documented - but never put in a list together.
I. I'll start with the variables:
%CD% is not working. But surprisingly %__CD__% works though it sets additional backslash at the end.
%CMDEXTVERSION% - logical at some degree as the command prompt is reduced only to its earlier versions
%CMDCMDLINE% - command line could be retrieved with some external tools that will give you information about the current PID.
%ERRORLEVEL% - as alternative %=ExitCode% can be used. Also %=ExitCodeAscii% if the exit code is above 32. IF ERRORLEVEL also still works.
%DATE% and %TIME% - though in disabled extensions mode you won't be able to assign the current date to a variable there are at least a lot of ways to display it.
%RANDOM%
%HighestNumaNodeNumber% - this was the biggest surprise for me as this variable was introduced in the newer versions of Windows when the support for multicore processors was added. It is highly related to the /NODE switch of the START command which is still working.
II. And the commands:
TAB is no longer autocomplete file names.Only in command prompt.
Starting a file by its extension - in disabled extensions mode the cmd will not get into account the HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts and //HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext\OpenWithList and will start directly only a "programs" - i.e. extensions described in %PATHEXT% variable.
Variable manipulation - replacements and substrings will not work with disabled extensions. Which means only with disabled extensions you can print the value of %=::% - undocumented variable that is defined only when the command prompt is not started with admin privileges.
ASSOC and FTYPE - not available under disabled extensions - related to the first point here.
CALL - no longer able to call labels but only other scripts.Argument modifiers are no longer supported (also in FOR command) - %~0, %~dp0, %~n0 will no longer working. Only plain access with %0,%1 ..
COLOR - no longer available. Though colors can be changed with other scripts.
IF - /I is not working. Three letter comparison operators (that can be used for integer comparisons) are not available - only ==. CMDEXTVERSION is not part of the options as well as DEFINED.
EXIT - Exit /b works ,but prints an error message as it tries to find the :EOF label. The printed error is not 'officially' documented.Only observable in batch files.
FOR - reduced to only basic for loops - the additional switches /F , /D , /R , /L are not available. Token modifiers will no longer working in a similar fashion of CALL arguments.
SHIFT - the /N switch is not working ,though is not 'officially' documented.
GOTO - :EOF label is not available anymore.
SET - switches /P and /A are no more working. Does not accept double quotes as part of the command (e.g. set "variable=value" will result in an error). Listing variables starting with a string is no longer possible (e.g. set path will cause an error). Only plain setting a variable value.
PUSHD - does no longer map temporary drives to UNC paths, and POPD does not delete such drives. Not officially documented.
PROMPT - does not support $+ and $M special codes
The HELP command deserves a note as it will not detect if the extensions are on or off and will print help messages as the extensions are available.
As COLOR , ASSOC , FTYPE (and IF DEFINED is not working) are the only commands turned off with disabled extensions are one of the best option to check if they are enabled (though COLOR will change the colour scheme).
But their output will be too big without the help message and help message is also not so small - which means even the redirection to null will slow down the check.
Eventually this also can be used to detect if the extensions are enabled:
call ::eof >nul 2>&1 && (
echo extensions are enabled
)||(
echo extensions are disabled
)
Or using assoc or ftype with an extension that will be available by default:
assoc .bat >nul 2>&1 && (
echo extensions are enabled
)||(
echo extensions are disabled
)
with ftype:
ftype batfile >nul 2>&1 && (
echo extensions are enabled
)||(
echo extensions are disabled
)
In a post build step i want check if OpenSSH.Client and OpenSSH.Server is installed and install it if it is not there. Checking the installed features with Powershell needs administrative privileges.
Therefore i test the existence of the relevant OpenSSH commands with the following code (extract of long script)
SET /a res=0
SET /a three=3
%windir%\system32\OpenSSH\ssh-keygen.exe /?
echo Errorlevel %ERRORLEVEL%
IF %ERRORLEVEL%==%three% (SET /a res=%res%+1)
%windir%\system32\OpenSSH\ssh-keyscan.exe /?
echo Errorlevel %ERRORLEVEL%
IF %ERRORLEVEL%==%three% (SET /a res=%res%+1)
SET /a check=0
IF %res% == %check% (
echo already installed
goto skipopenSSH
)
echo installation
:skipopenSSH
By checking the existence of the commands no admin privileges are necessary for the check so a normal build will not cause a administrative popup.
On cmd.exe it works fine, but as a post build step in Visual Studio both commands in %windir%\systrem32\OpenSSH are not found, although the variable is expanded to the same c:\Windows\System32\OpenSSH\*.exe as if executed on commandline.
After trying the different find mechanisms which all fail i made a test batch file C:\Windows\System32\OpenSSH\ssh-keyscan.exe /?
which leads to a file not found error if executed as a post build step. So the real question should be: Modifies the visual studio build step commandline the path?
The directory OpenSSH exists in directory %SystemRoot%\System32 with the files ssh-keygen.exe and ssh-keyscan.exe depending on version of Windows 10. The directory %SystemRoot%\System32 is for 64-bit applications on 64-bit Windows. But Visual Studio is a 32-bit application and for that reason 32-bit Windows command processor in directory %SystemRoot%\SysWOW64 is executed to process the batch file with the commands to execute as post build step.
Microsoft documented with WOW64 Implementation Details, File System Redirector and Registry Keys Affected by WOW64 and other documentation pages how Windows on Windows 64 works.
All file system accesses to %windir%\system32\OpenSSH in the batch file processed by 32-bit %SystemRoot%\SysWOW64\cmd.exe being started by 32-bit Visual Studio results in the approach to access %SystemRoot%\SysWOW64\OpenSSH which does not exist at all. There is no subdirectory OpenSSH in Windows system directory for 32-bit applications.
One solution would be using the following code for the batch file executed as post build step.
rem This simple check is for 32-bit Windows and for 64-bit Windows with batch
rem file executed in 64-bit environment by 64-bit Windows command processor.
set FolderSSH=%SystemRoot%\System32\OpenSSH
if exist %FolderSSH%\ssh-keygen.exe if exist %FolderSSH%\ssh-keyscan.exe goto UseOpenSSH
rem This check is for 64-bit Windows with batch file executed
rem in 32-bit environment by 32-bit Windows command processor.
if exist %SystemRoot%\Sysnative\cmd.exe set FolderSSH=%SystemRoot%\Sysnative\OpenSSH
if exist %FolderSSH%\ssh-keygen.exe if exist %FolderSSH%\ssh-keyscan.exe goto UseOpenSSH
rem Search entire system drive on machines without Windows 10 or with older
rem versions of Windows 10 with OpenSSH not installed at all by default.
for /F "delims=" %%I in ('%SystemRoot%\System32\where.exe /R %SystemDrive%\ ssh-keygen.exe 2^>nul') do (
if exist "%%~dpIssh-keyscan.exe" for %%J in ("%%~dpI.") do set "FolderSSH=%%~fJ" & goto UseOpenSSH
)
echo ERROR: ssh-keygen.exe AND ssh-keyscan.exe not found.
rem More code to handle this use case.
goto :EOF
:UseOpenSSH
echo Found ssh-keygen and ssh-keyscan in: "%FolderSSH%"
rem More code to handle this use case with existing SSH tools.
The remarks explain most of the code. The inner FOR loop is used to get the full qualified name of the directory containing ssh-keygen.exe and ssh-keyscan.exe without backslash at end of the folder path to have FolderSSH defined always without a backlash at end for further usage in the batch file.
Please note that it is safe to use %FolderSSH%\ssh-keygen.exe and %FolderSSH%\ssh-keyscan.exe without surrounding " at beginning of the batch script as it is impossible that %FolderSSH% expands to a folder path containing a space or one of these characters &()[]{}^=;!'+,`~.
But "%FolderSSH%\ssh-keygen.exe" and "%FolderSSH%\ssh-keyscan.exe" must be used on the command lines below the label UseOpenSSH because it could happen that WHERE was used to find the two executables anywhere on system drive and for that reason %FolderSSH% could expand now to a folder path containing a space or a character with a special meaning for Windows command processor outside a double quoted argument string.
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.
echo /?
for /?
goto /?
if /?
rem /?
set /?
where /?
Read also the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded where command line with using a separate command process started in background with %ComSpec% /c and the command line between ' appended as additional arguments on which 2^>nul became already 2>nul.
The following idea also uses the where.exe command, as mentioned in the comments. This one will search the system drive for ssh-keyscan.exe, and if found will ensure that ssh-keygen.exe is also located there. If both are located in the same place, then it is considered as already installed:
#Set "dirOpenSSH="
#For /F Delims^= %%G In (
'%__AppDir__%where.exe /R %SystemDrive%\ ssh-keyscan.exe 2^>NUL'
)Do #For /F Delims^= %%H In (
'%__AppDir__%where.exe "%%~dpG.":ssh-keygen.exe 2^>NUL'
)Do #Set "dirOpenSSH=%%~dpH"
#If Defined dirOpenSSH (
Echo OpenSSH is available at %dirOpenSSH%.
%__AppDir__%timeout.exe 3 /NoBreak>NUL
GoTo skipopenSSH
)
#Echo OpenSSH is not available, beginning installation.
#%__AppDir__%timeout.exe 3 >NUL
:skipopenSSH
Please note that where.exe and timeout.exe require a minimum OS version of Windows NT 6.x
As your application is currently a 32-bit one, the console session it is using is not accessing the 64-bit locations. To fix that you could replace %__AppDir__%where.exe, with %SystemRoot%\SysNative\where.exe
It is probably worth mentioning that in the versions of windows-10 with OpenSSH part of the base OS, the location, %SYSTEMROOT%\System32\OpenSSH\, should exist in %PATH%, so you should be able to find your files directly using them:
%__AppDir__%where.exe ssh-keygen.exe
%__AppDir__%where.exe ssh-keyscan.exe
For %I In (ssh-keygen.exe) Do #Echo %~$PATH:I
For %I In (ssh-keyscan.exe) Do #Echo %~$PATH:I
I have found on the systems which have upgraded from an earlier version of Windows, that many of the default locations using C:\Windows were replicated, but using %SYSTEMROOT%/%SystemRoot% instead.
In that state, the Where command and For's %~$PATH: failed to locate some items which did exist and should have been found.
Removing the duplicated items, I removed those which were carried over, (those not using variables), although removing either format seems to have worked fine.
Both where and %~$PATH: were then functioning as intended.
I'm trying to append four directories to %pythonpath%.
The directories are:
C:\src\tensorflow\models\research
C:\src\tensorflow\models\research\object_detection
..\utils
..\mail
When user is User42, %pythonpath% is always set to:
;C:\src\tensorflow\models\research\object_detection;..\utils;..\mail
Why is the first path ignored/overwritten?
#echo off
if "%username%"=="User42" (
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research\object_detection
) else (
:: Other path
)
:: This is common to all users
set pythonpath=%pythonpath%;..\utils;..\mail
echo %pythonpath%
Windows command processor replaces all environment variable references with syntax %VariableName% in a command block starting with ( and ending with matching ) during parsing phase of the next command line to execute on which a command block begins. In this case this means all %pythonpath% in both branches of the IF condition are substituted already by the current value of environment variable pythonpath before the IF condition is executed at all. This behavior can be seen by running the batch file without #echo off from within a command prompt window as in this case Windows command processor outputs the command lines after being parsed before execution.
The solution is using delayed expansion as also explained by help of command SET output on running in a command prompt window set /? on an IF and a FOR example, or avoiding the definition or modification of an environment variable and referencing it once again in same command block.
Here is a solution which works without usage of delayed expansion:
#echo off
set "Separator="
if defined pythonpath if not "%pythonpath:~-1%" == ";" set "Separator=;"
if /I "%username%" == "User42" (
set "pythonpath=%pythonpath%%Separator%C:\src\tensorflow\models\research;C:\src\tensorflow\models\research\object_detection"
) else (
rem Other path is added here to environment variable pythonpath.
)
rem This is common to all users. Variable pythonpath is defined definitely now.
for %%I in ("%CD%") do set "ParentPath=%%~dpI"
set "pythonpath=%pythonpath%;%ParentPath%utils;%ParentPath%mail"
echo %pythonpath%
It additionally makes sure there is not ;; in value of pythonpath in case of there is already a semicolon at end. And it makes sure pythonpath is not defined with a ; at beginning if this environment variable does not exist at all before first IF condition.
Further there are no relative paths added to pythonpath because of determining absolute path for ..\utils and ..\mails before appending them to pythonpath.
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.
echo /?
if /?
rem /?
set /?
BTW: Invalid labels :: as comments should not be used in command blocks. That can result in undefined behavior on execution. It is safer to use command REM for comments.
%pythonpath% used twice between parentheses will be evaluated on read before execution begins.
This is why %pythonpath% has the same value on the 2nd set.
You can use call set to force evaluation on the variable with doubled %s.
This has a similar effect to enabledelayedexpansion seen in setlocal /?
and if /?.
#echo off
if "%username%"=="User42" (
set "pythonpath=%pythonpath%;C:\src\tensorflow\models\research"
call set "pythonpath=%%pythonpath%%;C:\src\tensorflow\models\research\object_detection"
) else (
:: Other path
)
:: This is common to all users
set "pythonpath=%pythonpath%;..\utils;..\mail"
echo %pythonpath%
I have two batch files. One is the main (caller) file and one is a function.
The function takes one parameter, does some stuff and then i wanna return the string to the caller (main.bat). %ERRORLEVEL% is not an option, as it can only return integers.
main.bat:
call function.bat hello
function.bat:
REM some code.......
[HERE THE CODE TO RETURN A STRING TO CALLER FILE]
Seems like a basic operation, so there must be a way. Also, i prefer not making files with the output and reading it in the main.bat, because i want to make it work as a easy-to-use function.
The two batch files are executed both by same Windows command processor process and share therefore all environment variables.
Main.bat:
#echo off
set "MyVariable="
call Function.bat hello
echo MyVariable=%MyVariable%
Function.bat:
#echo off
rem Some code ...
set "MyVariable=%~1"
It could be that Function.bat uses for some reason command SETLOCAL. In this case all environment variables defined as well as all modifications made on environment variables after the command SETLOCAL are lost after corresponding ENDLOCAL. This command is implicitly called by Windows command processor on exiting execution of a batch file for each SETLOCAL not yet completed with explicit execution of corresponding ENDLOCAL. Read this answer for details about the commands SETLOCAL and ENDLOCAL.
It is necessary to explicitly set an environment variable on same command line as the command ENDLOCAL with using immediate environment variable expansion to pass value of an environment variable from current environment variables list to previous environment variables list.
Function.bat:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Some code ...
set "MyVariable=%~1"
endlocal & set "MyVariable=%MyVariable%"
The last command line is preprocessed first by Windows command interpreter to
endlocal & set "MyVariable=hello"
So the command line after preprocessing does not contain anymore any variable reference. The command ENDLOCAL restores previous environment which results in deletion of environment variable MyVariable. But the second command SET specified on same command line sets this variable once again with the value hello in restored environment.
See also Single line with multiple commands using Windows batch file.
The project I'm working on requires the GDAL library, with skipping all the stuff, has a bit of complicated installation as they're C-bindings for Java. Anyways I did find a relatively easy precompiled version of it, the only thing is it requires the user to set some environment variables.
That's easy enough to do, but the process can still confuse some people so to keep it simple I want like a simple "setup.bat" that will do that for you in Windows. This is what I got so far:
set "root=%~dp0"
set "gdalpath=%root%gdal-x64-11.1\"
setx PATH "%PATH%;%gdalpath%"
setx GDAL_DATA "%gdalpath%gdal-x64-11.1\gdal-data\"
setx GDAL_DRIVER_PATH "%gdalpath%gdal-x64-11.1\gdalplugins\"
setx PROJ_LIB "%gdalpath%gdal-x64-11.1\projlib\"
It works mostly pretty well except for PATH. There's two PATHs it seems, one for system and one for user environment variables. Simply calling straight %PATH% gives all of them combined, but writing with setx PATH writes it all to the user PATH.
For example if the variables looked like this before:
After running the batch file they look like this:
Which in this example does conserve the "C:\PHP\" and add the GDAL path at the end, but it just looks messy. I mean I think it more or less works and does what it's supposed to, but I'd rather have it more cleanly. So is there a way to grab only the user PATH variables and append to that?
Did you type setx /? for pages of help before posting (like /k switch)? Plus setx doesn't do the current console window.
To test if file is already in the path
#echo off
echo.
echo PathFind - Finds the first file in in a path
echo ======== = ===== === ===== ==== == == = ====
echo.
echo Searching for %1 in %path%
echo.
set a=%~$PATH:1
If "%a%"=="" (Echo %1 not found) else (echo %1 found at %a%)
To read the registry.
#for /f "skip=2 tokens=3" %%A in ('Reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v AlwaysShowMenus') do #if "%%A"=="0x1" echo Matches
Pause
User permanent variables are stored here.
HKEY_CURRENT_USER\Environment
VBScript WSH gives far more fine grained access to the environment.
From Help.
The Environment property contains the WshEnvironment object (a collection of environment variables). If strType is supplied, it specifies where the environment variable resides with possible values of System, User, Volatile, or Process.
The following code retrieves the system environment variable NUMBER_OF_PROCESSORS.
Visual Basic Script Copy Code
Set WshShell = WScript.CreateObject("WScript.Shell")
Set WshSysEnv = WshShell.Environment("SYSTEM")
WScript.Echo WshSysEnv("NUMBER_OF_PROCESSORS")