5

I am checking for baseboard information in a batch file with the following code.

BaseboardCheck.cmd:

@echo off
setlocal EnableDelayedExpansion

for /f "tokens=1,2* delims==" %%a in ('wmic baseboard get /format:list') do ( 
    
    if ["%%a"] EQU ["Product"] (
        set PlatformInfo=%%b

        if defined PlatformInfo (
            echo.!PlatformInfo!
            echo.!PlatformInfo!This overwrites the variable
        )
    )

    if ["%%a"] EQU ["Version"] (
        set BaseboardVersion=%%b

        if defined BaseboardVersion (
            echo.!BaseboardVersion!
            echo.!BaseboardVersion!This overwrites the variable
        )
    )   
)

The above problem: The variables get overwritten rather than appended to when echo'd out.

Output:

DX79SI
This overwrites the variable
AAG28808-600
This overwrites the variable

What I'd like to get is:

DX79SI
DX79SIThis overwrites the variable
AAG28808-600
AAG28808-600This overwrites the variable

I have spent a few hours on this (and will continue to do so), but I am hoping someone else has run into this issue. And I am hoping anyone else who runs into this parsing problem can avoid it in the future.

The additional problem that comes out of this is that it seems to break conditional logic.

Update:

After all of the assistance, I came up with this solution:

for /f "skip=2 tokens=1,2 delims=," %%a in ('wmic baseboard get Product^,Version^,Width /format:csv') do (
    set Platform=%%a
    set BaseboardVersion=%%b
)
echo.Platform: %Platform% 
echo.Version %BaseboardVersion%
Thumper
  • 518
  • 5
  • 19
  • Good question. setlocal enabledelayedexpansion is so complicated. I always end up using passing the parameters to another function using call. – gbabu Jul 25 '14 at 20:36

3 Answers3

8

Wow, it was really hard to find out what is going on here.

First, I could not believe what happens on batch file execution.

I executed on Windows XP SP3 x86 after many trials the command line

wmic.exe baseboard get /format:list > Output.txt

and looked on file Output.txt with viewer of file manager Total Commander.
I saw two empty lines at top, but that does not really matter. So I continued with other trials.

Later I opened Output.txt in text editor UltraEdit and saw immediately on status bar U-DOS indicating that the output file is encoded in UTF-16 Little Endian having DOS line terminators. I switched to hex edit mode and could see:

00000000h: FF FE 0D 00 0A 00 0D 00 0A 00 43 00 61 00 70 00 ; ÿþ........C.a.p.
00000010h: 74 00 69 00 6F 00 6E 00 3D 00                   ; t.i.o.n.=.

So the output file is indeed a Unicode file encoded with UTF-16 LE with BOM (byte order mark). There is no CR CR LF. All line terminations are correct the CR LF pair (carriage return + line-feed).

Now I searched on Stack Overflow for questions with [batch-file] containing the words wmic Unicode and found cmd is somehow writing Chinese text as output.

The accepted answer of dbenham is not good as it creates an ANSI version of the Unicode output of wmic.exe, but the ANSI file contains now indeed 0D 0D 0A (= CR CR LF).

Better is the answer by Dharma Leonardi as the solution with using command type results in a correct conversion of the Unicode output to ANSI output as long as the output does not contain characters not available in ANSI code page.

Note: The term ANSI is used here by me not really 100% precise for single byte per character encoding. See the Wikipedia articles about character encoding, code page and Windows code page. In a Windows command processor console is used by default an OEM code page like 437 (North American countries) or 850 (Western European countries). See also Why are Danish characters not displayed as in text editor on executing batch file? There is explained on an example how the same small Danish text can be encoded with which bytes using various character encodings used usually in North American and Western European countries.

However, after changing the batch code to process output of wmic.exe with ANSI encoding, the line with if defined BaseboardVersion evaluated always to true although I could not see that variable BaseboardVersion contained any data and therefore next line resulted in showing echo status.

It took me again some time to find out with inserting set > Variables.txt above the condition and looking on this file that on my computer the version string is just a single space character. The value of Version was the only value of all keys without a string right of the equal sign consisting only of a single space.

Here is the batch file which finally worked on my computer and which produces the expected output:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
%SystemRoot%\System32\wbem\wmic.exe /OUTPUT:"%TEMP%\UnicodeData.tmp" baseboard get /format:list
for /f "tokens=1,2* delims==" %%I in ('type "%TEMP%\UnicodeData.tmp"') do (
    if "%%I" == "Product" (
        if not "%%J" == "" if not "%%J" == " " (
            set "PlatformInfo=%%J"
            echo !PlatformInfo!
            echo !PlatformInfo!This overwrites the variable
        )
    ) else if "%%I" == "Version" (
        if not "%%J" == "" if not "%%J" == " " (
            set "BaseboardVersion=%%J"
            echo !BaseboardVersion!
            echo !BaseboardVersion!This overwrites the variable
        )
    )
)
del "%TEMP%\UnicodeData.tmp"
endlocal
Mofi
  • 38,783
  • 14
  • 62
  • 115
  • The depth you went into is incredible! MC ND had a more-elegant, solution, however. I am glad to know it wasn't completely trivial. – Thumper Jul 25 '14 at 22:49
  • 4
    CRCRLF only occurs when wmic uses C runtime standard I/O to write to a *console* or *pipe*, not a disk file. It writes strings that end in CRLF to a text-mode CRT file descriptor, which in turn converts the LF to CRLF, resulting in CRCRLF. Writing to a disk file takes a different path that calls a custom class method `wmic!CFileOutputStream::Write` that writes the strings without text-mode conversion of LF to CRLF. – Eryk Sun Feb 26 '18 at 14:52
  • @eryksun That's interesting. But running on Windows 7 the command line `wmic baseboard get /format:list >List.txt` results in producing `List.txt` encoded in UTF-16 LE only containing `0D 00 0A 00`. There is no `0D 00 0D 00 0A 00` in entire file. And I quickly wrote a small C++ console application reading from `stdin` with `fgetc` and `feof` and writing in ANSI to a file `List.txt` in binary mode called `IOTest.exe` and used it in command line `wmic baseboard get /format:list | IOTest.exe`. The file `List.txt` created by `IOTest` contains only `0D 0A`. There is no `0D 0D 0A` in entire file. – Mofi Feb 26 '18 at 15:58
  • 3
    As I said, CRLF => CRCRLF doesn't happen when output is directed to a disk file (i.e. `>List.txt`) because that takes a separate path in the code that doesn't use C runtime standard I/O. Commonly people call wmic.exe without output directed to a pipe, e.g. in a CMD `for /f` loop or out to more.com. – Eryk Sun Feb 26 '18 at 16:04
  • @eryksun The statement *console or pipe* is still unclear for me. On my second test I redirected the output to a console application using `|` operator and did not use `>`. I have never seen since writing this answer that any other line than last line of __WMIC__ output executed with `for /F` and processed directly ends with CRCRLF. Only the last two blank lines of __WMIC__ are interpreted wrong on processing the output directly with `for /F` as single line with CR as character of line and CRLF as line ending. I would be thankful if you can more enlighten me on this specific quirks. – Mofi Feb 26 '18 at 16:23
  • 2
    I mean a file that's not [`FILE_TYPE_DISK`](https://msdn.microsoft.com/en-us/library/aa364960), which could be `FILE_TYPE_CHAR` (e.g. a console) or `FILE_TYPE_PIPE`. Attach a debugger when wmic is writing to a console or pipe. You'll see that it passes strings to `WriteFile` that end with CRCRLF. This is from passing a string that ends in CRLF to `fprintf` (C standard I/O) using a file descriptor that's in text mode. The console and more.com are both resilient in this case. CMD's `for /f` loop strips the final CRLF, but a CR remains on every line. – Eryk Sun Feb 26 '18 at 16:49
  • @eryksun Many thanks. Indeed the file type makes the difference. Very good to know for the future. – Mofi Feb 26 '18 at 21:03
  • 2
    BTW, wmic actually writes OEM (e.g. codepage 437 or 850) to a pipe or console, not ANSI (e.g. codepage 1252). In some locales those are the same, but I think in all Western locales they're different codepages. – Eryk Sun Feb 27 '18 at 13:28
4

Yes, you have a problem, but it is not what you think.

wmic has a particular behaviour: at the end of each line output there is an aditional carriage return, that is, each line ends with 0x0d 0x0d 0x0a

This aditional carriage return is stored inside your variable and, when echoed to console, you get the data and the carriage return, so, if the variable is followed by more text, as the cursor has been positioned at the start of the line (the carriage return), this text is echoed over the previous echoed data.

How to solve?

@echo off
setlocal enabledelayedexpansion

for /f "tokens=1,* delims==" %%a in ('wmic baseboard get /format:list') DO ( 


    if ["%%a"] EQU ["Product"] (
        for /f "delims=" %%c in ("%%b") do set "PlatformInfo=%%c"

        if defined PlatformInfo (
            echo(!PlatformInfo!
            echo(!PlatformInfo!This does not overwrite the variable
        )
    )

    if ["%%a"] EQU ["Version"] (
        for /f "delims=" %%c in ("%%b") do set "BaseboardVersion=%%c"

        if defined BaseboardVersion (
            echo(!BaseboardVersion!
            echo(!BaseboardVersion!This does not overwrite the variable
        )
    )   
)

In this case, without changing your code logic, an aditional for /f command can be used to remove the aditional carriage return

MC ND
  • 65,671
  • 6
  • 67
  • 106
  • Actually it's not `wmic` that misbehaves, it's the Unicode-to-ASCII/ANSI conversion done by `for /F`... – aschipfl Mar 20 '19 at 21:26
  • 1
    @aschipfl, no, when **piped** the output of the `wmic` command is not unicode and the data stream includes the ending `CR CR LF` sequence. The `for` command retrieves all the output and the code that handles the input lines searchs for the first `LF` as line terminator. Once the `LF` is found, if the previous character is a `CR` it is removed and the rest of the data (from start) is the line to process. In this scenario, the first `CR` in the `wmic` line ending is included in the data being processed. – MC ND Mar 21 '19 at 17:50
  • Is it really caused by the `wmic` command? I was quite sure it's caused by the `cmd` instances invoked for either side of the pipe, both of which default to ANSI mode (`/A`), I didn't know that `wmic` distinguishes where its output goes and changes it. Sorry for making a wrong claim! – aschipfl Mar 21 '19 at 19:29
  • 1
    @aschipfl, read the comments from eryksun in the Mofi's answer about the `wmic`'s output. The "problem" is not in any of the commands (`wmic` and `for /f`) but in the interaction between them. – MC ND Mar 21 '19 at 19:55
1

As already answered, the problem is with the line-endings of wmic.

You can overcome this problem if you don't use the end of the line: wmic baseboard get Product,Version,Width uses three tokens: Product, Version and Width (which is empy in most cases). So the Output will be: DX79SI,AAG28808-600, We are using token 1 and token 2, ignoring token 3 (this would have the problem)

set Platform=undefined
set BaseboardVersion=undefined

for /f "tokens=1,2 delims=," %%a in ('wmic baseboard get Product^,Version^,Width^|findstr "."') do (
 set Platform=%%a
 set BaseboardVersion=%%b
)
echo it is a %Platform% with Version %BaseboardVersion%. No overwriting

I also added delims=, in case any of the strings contains a space (not unlikely with product)

Stephan
  • 47,723
  • 10
  • 50
  • 81
  • I like this one, as well. It didn't work quite the way I was hoping, but it inspired me to write something even more clean. – Thumper Aug 01 '14 at 16:40