I have made a simple checking program in batch for laptop configuration : is mains connected, is battery level over 25% (bios flashing request), is Internet available, aso. This works perfectly under Windows 10 Professional, but hangs at the first check with W10 Education... (In both cases started as Admin). One exemple is :
echo ***************************************
echo * 1.Test mains connected
echo * Mandatory for BIOS updates
echo ***************************************
set bstat=
for /f "tokens=2 delims==" %%a in ('wmic path Win32_Battery get BatteryStatus /format:list ^| find "="') do set bstat=%%a
if [%bstat%]==[2] goto :charg
...
If I run the line wmic path Win32_Battery get BatteryStatus /format:list in a CMD box, I get : BatteryStatus=2 which is the correct reply expected ?
After several Ctr-C, I get an error message "The process tried to write to a nonexistent pipe". This seems to hang before the "do", as if I add : do ( echo something.. ), nothing is displayed. Same with :
for /f "tokens=2 delims==" %%a in ('wmic path win32_battery get estimatedchargeremaining /format:value ^|find "="') do (
IF %%a GTR %batlvl% (
...
that hangs, but works OK under W10 Professional release. But if I run the command in a CMD box :
wmic path win32_battery get estimatedchargeremaining /format:value
I get the right answer : EstimatedChargeRemaining=97
I know that WMIC will be deprecated, but I have spend some time with these 300+ line batch program, it's just to help a non profit association, no performance program related nor hard testing... I just would like to understand why it doesn't run under W10 Education and how to cure this ? Thanks for your help !
There should be used the code:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
echo ***************************************
echo * 1.Test mains connected
echo * Mandatory for BIOS updates
echo ***************************************
set "bstat="
if exist %SystemRoot%\System32\wbem\wmic.exe for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE 2^>nul') do set /A bstate=%%I
if "%bstate%" == "2" goto charg
echo The battery status is not 2, it is:
set bstate
goto EndBatch
:charg
echo The system has access to AC so no battery is being discharged.
echo However, the battery is not necessarily charging.
:EndBatch
echo(
pause
endlocal
To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
echo /?
endlocal /?
for /?
goto /?
if /?
pause /?
set/?
setlocal /?
wmic /?
wmic path /?
wmic path Win32_Battery /?
wmic path Win32_Battery get /?
The batch file requires enabled command extensions which are enabled by Windows default. However, for BIOS updates it is advisable not depending on defaults and define the required execution environment completely in the batch file itself which is done here with the first two command lines. Read the issue chapters in this answer about general issues of many batch files working only under certain conditions instead of any environment.
The executable wmic.exe
is deprecated and could be not installed anymore by default. It is an optional Windows component and it might be necessary to first install it. The first IF condition checks the existence of wmic.exe
before running it.
It is advisable to reference all programs and scripts with well-known installation directory with their fully qualified file name because of two reasons:
cmd.exe
processing a batch file must not search for the executable/script file using the list of directory paths of local environment variable PATH
and list of file extensions of local environment variable PATHEXT
on its file name is specified in the batch file with full path and with file extension. That makes the batch file execution faster as lots of file system accesses to find the appropriate file are avoided by using the fully qualified file name in a batch file. The number of file system accesses is reduced therefore to an absolute minimum which is always good and never wrong.PATH
and PATHEXT
. Too many people corrupt daily their PATH
configuration because of following a wrong advice read somewhere or using a bad coded installer executable/script. A Windows batch file written for BIOS updates should work on any Windows computer independent on the user’s configuration of environment variables as much as possible.PATH
at the beginning instead of appending it at the end. If the added directory contains executables or scripts with same file name as a Windows command in the Windows system directory, this executable or script is executed by cmd.exe
instead of the Windows system executable on using just the file name of a Windows command instead of its fully qualified file name. The behavior of the batch file execution is unpredictable on such a configuration of system variable Path
.There is used the always available and working WMIC option /VALUE
instead of /format:list
. Most output format options require an additional file installed in language and country related subdirectory of %SystemRoot%\System32\wbem
like en-US
or de-AT
. The problem is that these files are on some Windows versions only installed in the language-country directory of the OS language like English (United States) while wmic.exe
searches for the additional file for the specified output format in the language-country directory according language and country configured for the used account like German (Austria). The WMIC execution fails for that reason just because of using an output format requiring an additional file not installed in the directory as expected by WMIC according to language and country configured by the user.
WMIC uses as character encoding the Unicode encoding UTF-16 Little Endian (short: UTF-16 LE) with byte order mark (BOM) for the output of the data.
The command line %SystemRoot%\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE
outputs to console:
BatteryStatus=2
There are two empty lines, next the line with the value of interest with value name and value separated with an equal sign, and two more empty lines. But how does that text look in memory which cmd.exe
has to process?
0000h: FF FE 0D 00 0A 00 0D 00 0A 00 42 00 61 00 74 00 ; ÿþ........B.a.t.
0010h: 74 00 65 00 72 00 79 00 53 00 74 00 61 00 74 00 ; t.e.r.y.S.t.a.t.
0020h: 75 00 73 00 3D 00 32 00 0D 00 0A 00 0D 00 0A 00 ; u.s.=.2.........
0030h: 0D 00 0A 00 ; ....
There is the BOM with two bytes with the hexadecimal values FF
and FE
. There are carriage returns also with two bytes with the hexadecimal values 0D
and 00
and line-feeds with two bytes with the hexadecimal values 0A
and 00
and of course the other text with two bytes per character whereby the second byte is always 00
.
The Windows Command Processor has problems processing the byte stream of that text output like other Windows commands. cmd
is written for processing with for /F
a text which is encoded with just one byte per character like:
0000h: 0D 0A 0D 0A 42 61 74 74 65 72 79 53 74 61 74 75 ; ....BatteryStatu
0010h: 73 3D 32 0D 0A 0D 0A 0D 0A ; s=2......
cmd
recognizes and ignores the two bytes of the byte order mark. It ignores also most null bytes, but it has a problem with interpreting the byte sequence 0D 00 0A 00
. There is usually read a line by searching for a line-feed with hexadecimal value 0A
and if there is before a carriage return with hexadecimal value 0D
, the carriage return is removed on processing further the text of the line. But in this case is a null byte before the line-feed. The Windows Command Processor interprets for that reason the carriage return as a text character of the line and does not ignore it.
A for /F
or for /f
as the case of an option does not matter for most Windows commands with a command line in '
results in execution of one more command process started in background with %ComSpec% /c
and the command line appended. There is executed therefore in background with Windows installed into C:\Windows
:
C:\Windows\System32\cmd.exe /c C:\Windows\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE 2>nul
Read the Microsoft documentation 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 command line with using a separate command process started in background.
cmd.exe
processing the batch file captures the Unicode output of wmic.exe
written to standard output stream of the background command process and waits for self-closing of started cmd.exe
before the captured byte stream is processed further by FOR.
There are ignored by FOR on using option /F
always empty lines. But are the five lines output by WMIC really empty? No, they are not because of wrong processing of UTF-16 LE encoded carriage return + line-feed. Each line output by WMIC is interpreted by cmd.exe
processing the batch file as a line which has at least a carriage return.
The value of interest is on third line after the word BatteryStatus
and the equal sign. That is the reason for using the FOR /F option delims==
to split up the current line into substrings using =
as string delimiter instead of normal space and horizontal tab. There is used also the FOR /F option tokens=2
to assign just the second equal sign delimited string to the specified loop variable I
instead of the first one as it is default. The default end of line character must not be redefined with an additional eol=
option because of the first substring after splitting up the line does in this case never being with ;
as that string is always either just a carriage return or BatteryStatus
.
The four empty lines output by WMIC interpreted as lines with just carriage return as only character of the line are ignored by FOR because of there is no second substring which can be assigned to the specified loop variable I
and so the command SET is not executed for the four empty lines.
But the line with the battery status as returned by the Win32_Battery class has a number and appended unfortunately also a carriage return which must be removed before further processing the value.
If you want to see the bad effect of the appended carriage return run first in a command prompt window following command line:
for /F "tokens=2 delims==" %I in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE 2^>nul') do @echo %I
There is output just the battery status number. So, what is the problem? Well, run the same command line again first without @
left to echo
and next with a small extension at the end.
for /F "tokens=2 delims==" %I in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE 2^>nul') do @echo %IWhere is the number?
There is displayed now in the console window just: Where is the number?
It was output like before, but also the carriage return resulting in moving the caret back to the beginning of the line before printing to console now the question. The carriage return after the number is really problematic on further processing the number as it can result in other command lines become of invalid syntax.
The solution used here is using an arithmetic expression to assign the number to the environment variable. There is called the function wcstol to convert the string assigned to the loop variable I
to a 32-bit signed integer which stops the conversion on reaching the first non-digit character which is usually the string terminating null byte but in this case the carriage return. The integer number is converted back to the string representation of the number before this string is assigned to the loop variable bstate
with just the one byte per character encoded number string.
The solution with set /A variable=value
cannot be used if the value is a string and not a number. In such use cases one more for /F
loop can help as demonstrated by the command line below executed directly in a command prompt window.
for /F "tokens=2 delims==" %I in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_Battery GET BatteryStatus /VALUE 2^>nul') do @for /F %J in ("%I") do @echo %J ^<- Here is the number!
There is now the number output and the additional text as the string assigned to the loop variable is without the carriage return. Run the command line without @
left to the inner for
and look on the output. That does not look nice, but is okay for the Windows Command Processor on parsing the command line during batch file execution.
The environment variable bstate
can be still undefined on wmic.exe
is not installed at all. It would be definitely better for a batch file written for BIOS updates depending on that executable to check the existence of the executable at beginning of the batch file and inform the user that WMIC must be installed first and how to do that before the update of the BIOS can be done safely.
It is possible with the posted batch code that the environment variable bstate
explicitly undefined first is still undefined and reaching the second IF condition. A string comparison is done best in this case with enclosing both strings to compare in double quotes. Please read my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files for a detailed explanation on how a string comparison is done by cmd.exe
.
Conclusion:
The slightly modified FOR command line has following advantages:
wmic.exe
handles this use case.cmd.exe
on processing the UTF-16 LE encoded output of wmic.exe
.find
without fully qualify file name which on some Windows PCs results on running a different find
program than %SystemRoot%\System32\find.exe
.PATH
on a machine of a user who has installed AVRStudio or just WinAVR has as first directory not %SystemRoot%\System32
, but the bin
directory of WinAVR. This directory contains also a find.exe
ported from Linux to Windows which works completely different to the Windows FIND and has other options than %SystemRoot%\System32\find.exe
.