Search code examples
windowsbatch-filewmic

Batch program with WMIC commands not working in Windows 10 Education but OK with Professional release or in CMD line?


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 !


Solution

  • 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:

    1. The Windows Command Processor 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.
    2. The batch file does not depend anymore on the values of the environment variables 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.
    3. There are installers adding one or more directory paths to system environment variable 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:

    1. It works even on PATH environment variable is corrupted on the user’s Windows machine with an absolute minimum of file system accesses.
    2. It works even on language and country configured for the user account is different to the language of Windows and how wmic.exe handles this use case.
    3. It handles the quirks of cmd.exe on processing the UTF-16 LE encoded output of wmic.exe.
    4. There is not executed find without fully qualify file name which on some Windows PCs results on running a different find program than %SystemRoot%\System32\find.exe.
      The system environment variable 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.