1

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>

  • Why?
  • Is there a way to correct without breaking the code?

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

Einstein1969
  • 317
  • 1
  • 13
  • wmic outputs is UTF8, whilst powershell typically uses ASCII encoding. try powershells native `get-wmiobject` as an alternative, or for powershell 3.0 + `get-ciminstance` – T3RR0R Apr 03 '21 at 11:06
  • 6
    I was fairly sure that WMIC output was UTF-16 LE @T3RR0R – Compo Apr 03 '21 at 11:11
  • 1
    wmic output on my machine seem UTF-16LE, using get-wmiobject as alternative is not a solution for me, since I have more and more dos code to execute and I don't wont recode in powershell. @T3RR0R – Einstein1969 Apr 03 '21 at 11:17
  • 1
    you are of course correct @compo – T3RR0R Apr 03 '21 at 11:27
  • 1
    It's WQL, and what your return is text. Posh returns the object. – Abraham Zinala Apr 03 '21 at 11:31
  • 1
    [`wmic` behaviour: each output line ends with `0x0D0D0A` (CR+CR+LF) instead of common `0x0D0A` (CR+LF).](https://stackoverflow.com/a/61899543/3439404), and `WMIC` output is `UTF-16 LE`. Check using `powershell -nopro "(cat out2.txt -encoding Byte) -join ' '"` – JosefZ Apr 03 '21 at 11:38
  • @JosefZ seem not. I edit the question. – Einstein1969 Apr 03 '21 at 12:38
  • 3
    OOPS! Regardless of WMIC output encoding, final file encoding comes from Tee-Object which uses Unicode encoding when it writes to files. (Beginning in PowerShell 6, `Tee-Object` uses BOM-less UTF-8 encoding when it writes to files.) – JosefZ Apr 03 '21 at 13:23
  • this is my : PSVersion 5.1.19041.610. @JosefZ. I want use default version in windows 10 installations (not too old). I think that version 5.x is my choice. – Einstein1969 Apr 03 '21 at 13:50
  • 2
    Yes, I see. About additional _Carriage Return_, check the following: `wmic RECOVEROS get autoreboot | powershell -nopro "$x=$input; $x.split( [System.Environment]::NewLine, 1) | foreach-object {$_.trim(), $($_.trim().Length) -join ','}"`. Output is `AutoReboot,10 ,0 FALSE,5 ,0 ,0 ,0` and every zero-length line represents that _additional_ ``, and PowerShell transforms it to regular _NewLine:. – JosefZ Apr 03 '21 at 13:58
  • 4
    Why use wmic at all? Powershell and its WMI/CIM adapter was literally written as a replacement for wmic (same author) – Mathias R. Jessen Apr 03 '21 at 14:02
  • @JosefZ. might be a workaround, but could it take away some intentional whitespace of the wmic command? Now I don't have a ready example with wmic, but this gives the idea: (echo foo & echo( & echo bar) | powershell -nopro "$x=$input; $x.split( [System.Environment]::NewLine, 1) | foreach-object {$_.trim(), $($_.trim().Length) -join ','}" – Einstein1969 Apr 03 '21 at 14:22
  • 1
    A workaround? `wmic RECOVEROS get autoreboot | powershell -nopro "$input | Where-object {$_.trim().Length -ne 0} | tee-object -a .\out2.txt"` – JosefZ Apr 03 '21 at 15:23
  • @JosefZ. Nope. The output is not the same. look for "wmic pagefile list /format:list". Maybe it leaves a lot of spaces, but if we remove them all the resulting file is hard to read because the commands are all attached. – Einstein1969 Apr 03 '21 at 16:08
  • 1
    @Einstein1969 [cmd is **not** DOS](https://superuser.com/a/1411173/241386). DOS can't run Windows commands and has different syntax from cmd – phuclv Apr 10 '21 at 16:21
  • 1
    @T3RR0R, from what I can tell, what encoding `wmic.exe` uses depends on whether its stdout is directly connected to a _file_ or not; in the latter case, it seems to use the console's code page, as expected; in the former case (direct output to a file) it inconsistently uses either UTF-16LE ("Unicode") for _queries_ (`get`), and, again, the console's code page for the status messages accompanying _updates_ (`set`). UTF-8 seems to be fundamentally unsupported. See the bottom section of my answer for details. – mklement0 Apr 10 '21 at 23:11
  • @phuclv yes i know, what do you advise me to do? – Einstein1969 Apr 11 '21 at 06:07

2 Answers2

3

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
2

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.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
  • wmic doesn't behave badly in my case it puts CR + LF where needed. The idea of executing the command twice could be a solution, but I have a doubt: there are commands that once executed change something and the second execution could be different? However, I add some information to the question that is important. The commands I have to execute are both fast and slow. The slow ones (that require "tee", but I don't probed) for the moment I have not taken them into consideration. I have to execute for example commands like chkdsk, dism, sfc, wmic, dos (internal, external), powershell. – Einstein1969 Apr 03 '21 at 13:33
  • 1
    Without seeing your code, it's impossible to answer your question on whether or not your calls will change something. That would be a separate question. If you don't want to call the same command twice, you could call the command and output to file and then output the file to the screen. – Bacon Bits Apr 03 '21 at 13:45
  • 1
    "wmic doesn't behave badly in my case it puts CR + LF where needed." No, it doesn't. That's why it's breaking. The fact that it also breaks CMD's `for` command -- which is what that link shows -- is proof that it's WMIC behaving badly. No command should output both CR and CRLF to stdout if that output is going to be parsed. – Bacon Bits Apr 03 '21 at 13:45
  • I did not think about it. The solution to run the command first on the file and then print it on the screen is acceptable. But only for the commands they execute quickly. For the slow ones I would like the progress printed. – Einstein1969 Apr 03 '21 at 13:56
  • sorry but from the wmic output that I put in the first edit I don't see the sequence CR CR LF . What are you referring to? @Bacon Bits – Einstein1969 Apr 03 '21 at 14:01
  • I have edit the question and insert the code tha I use for testing. But the the commands are of various types @Bacon Bits – Einstein1969 Apr 03 '21 at 14:13
  • "wmic recoveros get autoreboot | powershell.exe "$input | Where-Object { -not [String]::IsNullOrWhiteSpace($_) } | Tee-Object out.txt"" Isn't there a way to just remove the blank lines that are added and leave the blank ones that come from the command before the pipe? @Bacon Bits – Einstein1969 Apr 03 '21 at 16:12
  • I ran the most thorough test for the first proposed workaround, this is the code: ( %ComSpec% /c "echo(%CD%^>%%~C & %%~C & echo(" & %ComSpec% /c "echo(%CD%^>%%~C & %%~C & echo(" > out.txt & type out.txt & type out.txt >>test.txt ) The results are not satisfactory: Sometimes the screen loses a final line, not always. The resulting test.txt file loaded in notepad, some times, is with different accented characters and words with space between one character and the next, but not on all results. In addition, the results have a yes and a no line. – Einstein1969 Apr 04 '21 at 18:36
  • the second workaround has the same screen and file content but also removes intentionally white lines. As for the option to use powershell for wmi at the moment it is not a solution for me – Einstein1969 Apr 04 '21 at 18:55