Einstein1969
Einstein1969

Reputation: 307

Why the ouput of wmic piped in powershell it's different? how to correct?

I am making a batch dos code to execute commands and capture code in files, while also keeping the output on screen.

EDIT2: 3 apr 21, 15:36 The commands that I want execute are slow and fast and some commands can only be executed once. I have to execute for example commands like chkdsk, dism, sfc, wmic, dos (internal, external), powershell.

I thought the fastest way was to use the powershell "tee".

ex. (echo foo & echo bar) | powershell "$input | tee -a out.txt"

C:\WINDOWS\system32>(echo foo & echo bar) | powershell "$input | tee -a out.txt"
foo
bar

but when i use it with wmic i have different results:

C:\WINDOWS\system32>wmic recoveros get autoreboot
AutoReboot
FALSE


C:\WINDOWS\system32>wmic recoveros get autoreboot | powershell "$input | tee -a out.txt"
AutoReboot

FALSE




C:\WINDOWS\system32>


EDIT1: 3 apr 21, 14:46

I add some information how suggest JosefZ:

C:\WINDOWS\system32>del out.txt out2.txt

C:\WINDOWS\system32>wmic recoveros get autoreboot > out2.txt

C:\WINDOWS\system32>wmic recoveros get autoreboot | powershell "$input | tee -a out.txt"
AutoReboot

FALSE




C:\WINDOWS\system32>type out.txt
AutoReboot

FALSE




C:\WINDOWS\system32>type out2.txt
AutoReboot
FALSE

C:\WINDOWS\system32>powershell -nopro "(cat out.txt -encoding Byte) -join ' '"
255 254 65 0 117 0 116 0 111 0 82 0 101 0 98 0 111 0 111 0 116 0 32 0 32 0 13 0 10 0 13 0 10 0 70 0 65 0 76 0 83 0 69 0 32 0 32 0 32 0 32 0 32 0 32 0 32 0 13 0 10 0 13 0 10 0 13 0 10 0 13 0 10 0

C:\WINDOWS\system32>powershell -nopro "(cat out2.txt -encoding Byte) -join ' '"
255 254 65 0 117 0 116 0 111 0 82 0 101 0 98 0 111 0 111 0 116 0 32 0 32 0 13 0 10 0 70 0 65 0 76 0 83 0 69 0 32 0 32 0 32 0 32 0 32 0 32 0 32 0 13 0 10 0

C:\WINDOWS\system32>

EDIT3: 3 apr 21, 16:05 This is the code that I developed for the moment (some commands are commented for the moment with "rem"):

for %%C in (
"rem sfc /scannow"
"rem dism /online /cleanup-image /scanhealth"
"rem dism /online /cleanup-image /restorehealth"
"rem sfc /scannow"
"rem"
"rem chkdsk /scan"
"rem"
"wmic recoveros get autoreboot"
"wmic recoveros set autoreboot = false"
"wmic recoveros get autoreboot"
"wmic recoveros get DebugInfoType"
"wmic recoveros set DebugInfoType = 7"
"wmic recoveros get DebugInfoType"
"rem"
"wmic pagefile list /format:list"
"wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"
"wmic Computersystem where name="%COMPUTERNAME%" set AutomaticManagedPagefile=True"
"wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"
"rem"
"bcdedit /enum {badmemory}"
) do ( %ComSpec% /c "echo(%CD%^>%%~C & %%~C " | powershell -command " $input | tee -Append test.txt ")

EDIT4: 10 apr 2021, 8:21 I add this code to create a reference output to facilitate the comparison process between the various tests. This output is to be considered both on screen and on file.

@echo off

rem Warning! Run as administrator. 

rem go safe place for testing...
cd %temp%

for %%C in (
"rem sfc /scannow"
"rem dism /online /cleanup-image /scanhealth"
"rem dism /online /cleanup-image /restorehealth"
"rem sfc /scannow"

"rem chkdsk /scan"

"wmic recoveros get autoreboot"
"wmic recoveros set autoreboot = false"
"wmic recoveros get autoreboot"
"wmic recoveros get DebugInfoType"
"wmic recoveros set DebugInfoType = 7"
"wmic recoveros get DebugInfoType"

"wmic pagefile list /format:list"
"wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"
"wmic Computersystem where name="%COMPUTERNAME%" set AutomaticManagedPagefile=True"
"wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"

"bcdedit /enum {badmemory}"

) do ( 
       rem Partially simulates "echo on" on commands. 
       echo(%CD%^>%%~C
       rem Execute command.
       %%~C
       rem alternative, but opens another cmd process. 
       rem %ComSpec% /c "echo(%CD%^>%%~C & %%~C"
)

pause

goto :eof

Upvotes: 1

Views: 947

Answers (2)

mklement0
mklement0

Reputation: 437353

Bacon Bits' helpful answer contains good background information.

To solve your problem, I suggest you:

  • Redirect the wmic commands directly to a temporary file, as that at least partially avoids the CRCRLF problem (i.e., two instead of the usual one CR (Carriage Return, U+000D) character followed by a LF (Line Feed, U+000A) that surfaces otherwise.[1]

  • Then let PowerShell read that temporary file, fix any remaining CRCRLF newlines, and send it to Tee-Object, preceded by the simulated prompt string, which both prints it and appends it to the overall output file.

Here's a simplified batch-file example:

@echo off & setlocal

:: Delete a previous output file, if present.
del test.txt 2>NUL

for %%C in (
  "wmic recoveros get autoreboot"
  "wmic recoveros get DebugInfoType"
  "wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"
) do ( 
  %%~C > %TEMP%\tmp.txt
  echo "%CD%>%%~C"| powershell -noprofile -c "$($input).Trim([char] 0x22), ((Get-Content -Raw %TEMP%\tmp.txt -Encoding Oem) -replace '\r\r\n', [Environment]::NewLine) | tee -Append test.txt"
  del %TEMP%\tmp.txt
)

Note the required "..." around %COMPUTERNAME% in the name="%COMPUTERNAME%" WMI filter expression.

  • %%~C > %TEMP%\tmp.txt executes the command at hand and directly saves its output to a temporary file.

  • echo "%CD%>%%~C", as in your question, echoes a simulated prompt showing the current directory followed by the quote-stripped command string (%%~C); e.g.,
    "C:\Users\jdoe>wmic recoveros get autoreboot". This string is piped to PowerShell, albeit enclosed in double quotes, to protect it from unwanted interpretation of its content by cmd.exe itself, such as > and | characters.

  • In the PowerShell command, $($input).Trim([char] 0x22) reads stdin input via the automatic $input variable, trims the enclosing " chars. that echo included in its output (.Trim([char] 0x22]), where [char] 0x22 represents a " char., whose literal use is avoided here to prevent escaping headaches), and (implicitly) outputs the resulting string.

  • Get-Content then reads the temporary file and outputs the result. Note the use of -Raw, which speeds up processing, because the entire file is then read at once, as single, multi-line string, rather than line by line, which is also the prerequisite for replacing the broken CRCRLF newlines with the usual CRLF sequences, using -replace '\r\r\n', [Environment]::NewLine.

    • As a side effect, an empty line is effectively inserted between blocks of command-specific output (assuming each > operation resulted in a file with a trailing newlin); if that is not desired, you can append a second -replace operation: -replace '\r\n\z'

      echo "%CD%>%%~C"| powershell -noprofile -c "$($input).Trim([char] 0x22), ((Get-Content -Raw %TEMP%\tmp.txt -Encoding Oem) -replace '\r\r\n', [Environment]::NewLine -replace '\r\n\z') | tee -Append test.txt"
      
    • If you want to normalize empty lines between output blocks, you can trim any leading and trailing newlines first, and then add the desired, fixed number; in the following example, a single newline is ensured between blocks:

      echo "%CD%>%%~C"| powershell -noprofile -c "$($input).Trim([char] 0x22), (((Get-Content -Raw %TEMP%\tmp.txt -Encoding Oem) -replace '\r\r\n', [Environment]::NewLine).Trim[Environment]::NewLine) + [Environment]::NewLine) | tee -Append test.txt"
      
    • Character-encoding note: As discussed in the bottom section, wmic, when outputting directly to a file, uses "Unicode" encoding (UTF-16LE, with BOM), which PowerShell interprets correctly. Other, non-wmic commands may produce OEM code page-encoded output, which, if it contains non-ASCII characters (such as accented characters), will be misinterpreted by PowerShell. For that reason, -Encoding Oem is used with Get-Content, which PowerShell fortunately ignores if the input file has a BOM, as in the wmic output case. That is, -Encoding Oem is only applied if the input files has no BOM.

  • Both the simulated prompt string and the content of the temporary file are then piped to PowerShell's Tee-Object cmdlet (whose built-in alias (on Windows only) is tee), which passes its input through while also appending it to file test.txt (-FilePath text.txt -Append).

    • Note that In Windows PowerShell, Tee-Object uses "Unicode" encoding (UTF-16LE, with BOM) when writing to a file via -FilePath; In PowerShell (Core) 7+, it is BOM-less UTF-8 (the encoding used consistently in that edition - see this answer for more information).

If you really want to emulate the exact output you'd see on the screen if you ran your commands directly in cmd.exe only, you can try the following:

@echo off & setlocal

:: Delete a previous output file, if present.
del test.txt 2>NUL

for %%C in (
  "wmic recoveros get autoreboot"
  "wmic recoveros set autoreboot = true"
  "wmic recoveros get DebugInfoType"
  "wmic recoveros set DebugInfoType = 7"
  "bcdedit /enum {badmemory}"
  "wmic Computersystem where name="%COMPUTERNAME%" get AutomaticManagedPagefile"
) do ( 
  %%~C > %TEMP%\tmp.txt
  echo "%CD%>%%~C"| powershell -noprofile -c "$o = $($input).Trim([char] 0x22) + [Environment]::NewLine + (Get-Content -Raw %TEMP%\tmp.txt -Encoding Oem) -replace '\r\r\n', [Environment]::NewLine; if ([IO.File]::ReadAllBytes('%TEMP%\tmp.txt')[0..1] -join ' ' -ne '255 254') { $o = $o -replace '\r\n\z' }; $o | tee -Append test.txt"
  del %TEMP%\tmp.txt
)

Note:

  • This works with the commands given, but I don't know how it generalizes.
  • Consider not going for this emulation, because it represents display quirks presumably resulting from the nonstandard CRCRLF newline sequences.
  • Instead, consider using the approach that normalizes empty lines.

Optional reading: deprecation of wmic.exe, character-encoding quirks and limitations:

  • wmic (WMIC.exe) is now officially deprecated, as a warning in red tells you when run
    wmic /? - see this answer for more information.

  • The CRCRLF newline problem occurs only sometimes if you redirect wmic output directly to a file, seemingly dependent on whether the command is a get or a set command:

    • With a get command (e.g., cmd /c "wmic recoveros get DebugInfoType > out.txt"):

      • the CRCRLF problem does not occur,
      • but wmic then uses "Unicode" encoding (UTF-16LE, with BOM[2]), irrespective of the console's code page.
      • Also note that in PowerShell even > does not write directly to a file: PowerShell always converts output from external programs to .NET strings first, and then writes the result to a file; therefore, to see the direct-to-file behavior, you must call via cmd /c, as in the example abpve.
    • With a set command (e.g., cmd /c "wmic recoveros set DebugInfoType = 7 > out.txt"):

      • the CRCRLF problem does occur,
      • and wmic then seemingly uses the system's active OEM code page, which in Western languages is a BOM-less single-byte encoding, such as 437 (IBM437) on US-English systems.
  • When not writing to a directly to a file, wmic respects the current console window's (OEM) code page for its output, which makes PowerShell decode the output correctly, except if it is set to 65001, i.e. the UTF-8 code page: wmic then produces invalid-as-UTF-8 output for non-ASCII characters (e.g., é), which PowerShell decodes to (the REPLACEMENT CHARACTER, U+FFFD).


[1] Inside a PowerShell session you only see the effects of these CRCRLF sequences, not the sequences themselves, due to PowerShell invariably splitting an external program's stdout output into lines, and in this case the first CR in the CRCRLF sequence becomes an empty line of its own - for more information, see this answer (the latter relates to sfc.exe rather than wmic, but the problem is the same).

[2] If you use >> to append to an existing file, the BOM is not written, but note that UTF16-LE encoding is blindly applied, irrespective of the encoding the existing content has.

Upvotes: 3

Bacon Bits
Bacon Bits

Reputation: 32155

As others have mentioned in the comments, WMIC is pretty poorly behaved for the pipeline because it outputs inconsistent line endings (CR+CR+LF). The Powershell behavior you're seeing is consistent with the input it's getting, it's just that WMIC is badly behaved. IMO, you might as well just call it twice if you want to write to a file and to the screen. It's not likely to change and isn't a slow command:

wmic recoveros get autoreboot
wmic recoveros get autoreboot > out.txt

However, you should be able to do this to strip any blank or whitespace-only lines:

wmic recoveros get autoreboot | powershell.exe "$input | Where-Object { -not [String]::IsNullOrWhiteSpace($_) } | Tee-Object out.txt"

That works for me but it's also significantly slower because the system has to start Powershell to do it.

Otherwise, is there a reason not to use Powershell entirely for your script? If you're doing lots of similar operations it's probably worthwhile. You can more-or-less replace the wmic call with Get-CimInstance -ClassName Win32_OSRecoveryConfiguration. If you want to know the actual class names that wmic uses, you can call wmic alias list brief.

Upvotes: 2

Related Questions