英文:
Batch program with WMIC commands not working in Windows 10 Education but OK with Professional release or in CMD line?
问题
我在批处理中制作了一个简单的检查程序,用于检查笔记本配置:是否连接到主电源,电池电量是否超过25%(需要刷新BIOS),是否有Internet连接等。在Windows 10专业版下运行得非常完美,但在W10教育版下在第一个检查时卡住了...(两种情况下都以管理员身份启动)。
一个例子是:
echo ***************************************
echo * 1.测试是否连接到主电源
echo * 刷新BIOS时必须连接主电源
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
...
如果我在CMD中运行wmic path Win32_Battery get BatteryStatus /format:list这一行,我会得到:BatteryStatus=2,这是正确的预期答案?
在按下几次Ctrl-C后,我收到一个错误消息:"进程试图写入一个不存在的管道"。这似乎在"do"之前卡住了,就好像我添加了:do(echo something..),什么都不会显示。
同样对于:
for /f "tokens=2 delims==" %%a in ('wmic path win32_battery get estimatedchargeremaining /format:value ^|find "="') do (
IF %%a GTR %batlvl% (
...
它会卡住,但在W10专业版中可以正常工作。但是如果我在CMD中运行以下命令:
wmic path win32_battery get estimatedchargeremaining /format:value
我会得到正确的答案:
EstimatedChargeRemaining=97
我知道WMIC将被弃用,但我已经花了一些时间编写了这个300+行的批处理程序,只是为了帮助一个非营利组织,与性能程序无关,也没有硬件测试...
我只是想知道为什么它在W10教育版下不能运行,以及如何解决这个问题?
感谢您的帮助!
英文:
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 !
答案1
得分: 1
以下是翻译好的代码部分:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
echo ***************************************
echo * 1.测试主电源连接
echo * BIOS更新必须
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 电池状态不是2,而是:
set bstate
goto EndBatch
:charg
echo 系统可以访问交流电源,因此电池不会被放电。
echo 但是,电池不一定正在充电。
:EndBatch
echo(
pause
endlocal
请注意,我已将文本中的HTML实体引用(如"
)替换为双引号("
)以确保代码的可读性。
英文:
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:
- 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 variablePATH
and list of file extensions of local environment variablePATHEXT
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. - The batch file does not depend anymore on the values of the environment variables
PATH
andPATHEXT
. Too many people corrupt daily theirPATH
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. - 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 bycmd.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 variablePath
.
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:
- It works even on PATH environment variable is corrupted on the user’s Windows machine with an absolute minimum of file system accesses.
- 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. - It handles the quirks of
cmd.exe
on processing the UTF-16 LE encoded output ofwmic.exe
. - There is not executed
find
without fully qualify file name which on some Windows PCs results on running a differentfind
program than%SystemRoot%\System32\find.exe
.
The system environment variablePATH
on a machine of a user who has installed AVRStudio or just WinAVR has as first directory not%SystemRoot%\System32
, but thebin
directory of WinAVR. This directory contains also afind.exe
ported from Linux to Windows which works completely different to the Windows FIND and has other options than%SystemRoot%\System32\find.exe
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论