“ SFC”输出重定向格式设置问题-Powershell / Batch

时间:2019-09-01 22:06:54

标签: powershell batch-file encoding formatting io-redirection

我正在研究一个Powershell脚本,其中几个命令输出显示在窗口中,并附加到文件或变量中。在我使用sfc命令之前,它一直可以正常工作。通过管道传输或重定向时,输出“断开”:

> sfc /?
Vérificateur de ressources Microsoft (R) Windows (R) version 6.0[...]

> sfc /? | Tee-Object -Variable content
 V Ú r i f i c a t e u r   d e   r e s s o u r c e s   M i c r o s o f t   ( R )   W i n d o w s   ( R )   v e r s i o  á 6 . 0[...]

是否还有其他命令,例如sfc的格式相同,或者如果重定向将导致输出中断?


编辑

Powershell示例代码,使用接受的答案中的代码:

# Run a command
function RunCommand([ScriptBlock] $command) {

    # Run the command and write the output to the window and to a variable ("SFC" formatting)
    $stringcommand = $command.ToString()
    if (
        $stringcommand -match "^SFC$" -or
        $stringcommand -match "^SFC.exe$" -or
        $stringcommand -match "^SFC .*$" -or
        $stringcommand -match "^SFC.exe .*$"
    ) {
        $oldEncoding = [console]::OutputEncoding
        [console]::OutputEncoding = [Text.Encoding]::Unicode
        $command = [ScriptBlock]::Create("(" + $stringcommand + ")" + " -join ""`r`n"" -replace ""`r`n`r`n"", ""`r`n""")
        & ($command) 2>&1 | Tee-Object -Variable out_content
        [console]::OutputEncoding = $oldEncoding

    # Run the command and write the output to the window and to a variable (normal formatting)
    } else {
        & ($command) 2>&1 | Tee-Object -Variable out_content
    }

    # Manipulate output variable, write it to a file...
    # ...
    return
}

# Run commands
RunCommand {ping 127.0.0.1}
RunCommand {sfc /?}
[void][System.Console]::ReadKey($true)
exit

CMD示例代码,使用more格式化sfc输出:

@echo off
setlocal enabledelayedexpansion
set "tmpfile=%TEMP%\temp.txt"
set "outputfile=%TEMP%\output.txt"

REM; Run commands
call :RunCommand "ping 127.0.0.1"
call :RunCommand "sfc"
pause
exit /b

REM; Run a command
:RunCommand

    REM; Run the command and write the output to the window and to the temp file
    set "command=%~1"
    (!command! 2>&1) >!tmpfile!

    REM; Write the output to the window and to the output file ("SFC" formatting)
    set "isSFC=0"
    (echo !command!|findstr /I /R /C:"^SFC$" > NUL) && (set "isSFC=1")
    (echo !command!|findstr /I /R /C:"^SFC.exe$" > NUL) && (set "isSFC=1")
    (echo !command!|findstr /I /R /C:"^SFC .*$" > NUL) && (set "isSFC=1")
    (echo !command!|findstr /I /R /C:"^SFC.exe .*$" > NUL) && (set "isSFC=1")
    (if !isSFC! equ 1 (
        (set \n=^
%=newline=%
)
        set "content="
        (for /f "usebackq tokens=* delims=" %%a in (`more /p ^<"!tmpfile!"`) do (
            set "line=%%a"
            set "content=!content!!line!!\n!"
        ))
        echo.!content!
        (echo.!content!) >>!outputfile!

    REM; Write the output to the window and to the locked output file (normal formatting)
    ) else (
        type "!tmpfile!"
        (type "!tmpfile!") >>!outputfile!
    ))
goto :EOF

2 个答案:

答案 0 :(得分:3)

js2010's answer中提到,sfc.exe实用程序-令人惊讶-输出经过UTF-16 LE(“ Unicode”)编码的文本

由于PowerShell不期望这样做,因此会误解sfc的输出。 [1]

解决方案是(暂时)将[console]::OutputEncoding更改为UTF-16LE ,这告诉PowerShell / .NET期望外部程序使用什么字符编码,即如何解码外部程序。程序输出到 .NET字符串(它们以UTF-16代码单元存储在内存中)。

但是,还有一个看起来像个错误的问题:非常麻烦,sfc.exe使用CRCRLF(`r`r`n)序列作为换行符,而不是Windows惯用的CRLF({{1} })换行符

PowerShell捕获外部程序的stdout输出时,将返回一个行数组而不是单个多行字符串,并且将以下换行样式可互换地处理 :CRLF(Windows风格),LF(Unix风格)和CR(过时的Mac风格-目前很少见)。
因此,它将CRCRLF视为两条换行符,它们同时反映在“ teed”和变量捕获中,然后包含多余的空行。
因此,解决方案是将数组元素与标准CRLF换行序列-`r`n连接在一起,然后将 2个连续 (sfc /?) -join "`r`n"替换为一个,以删除人为引入的换行符:`r`n

将它们放在一起:

-replace "`r`n`r`n", "`r`n"

请注意,# Save the current output encoding and switch to UTF-16LE $prev = [console]::OutputEncoding [console]::OutputEncoding = [Text.Encoding]::Unicode # Invoke sfc.exe, whose output is now correctly interpreted and # apply the CRCRLF workaround. # You can also send output to a file, but note that Windows PowerShell's # > redirection again uses UTF-16LE encoding. # Best to use ... | Set-Content/Add-Content -Encoding ... (sfc /?) -join "`r`n" -replace "`r`n`r`n", "`r`n" | Tee-Object -Variable content # Restore the previous output encoding, which is the system's # active OEM code page, which should work for other programs such # as ping.exe [console]::OutputEncoding = $prev 将包含一个单行多行字符串;使用$content来分割成几行。


至:

  

是否存在其他格式相同的命令,例如“ sfc”,如果重定向,会导致输出损坏?

我个人并不知道;与$content -split "`r`n"一样,无条件 UTF-16LE输出使我感到异常(其他程序可能会基于 opt-in 提供)。< / p>

具有仅Windows遗产的较旧的控制台程序使用(可能是固定的)OEM代码页,该页是8字节的单字节编码,是ASCII的超集。

现代的多平台控制台程序越来越多地使用UTF-8(例如Node.js CLI),它是可变宽度编码,能够编码与ASCII向后兼容的所有Unicode字符(即, 7位ASCII范围UTF-8将所有字符编码为单个ASCII兼容字节)。

如果您希望使PowerShell会话以及可能的所有控制台窗口完全受UTF-8识别,请参见this answer(但是,对于stil来说,需要{{1} })。


[1] 直接到控制台输出

sfc.exe的输出既未被PowerShell捕获也未通过cmdlet路由,例如sfc时,sfc直接将写到控制台,大概使用Unicode Windows API函数WriteConsole的版本,该版本需要UTF-16LE字符串。

以这种方式写入控制台允许打印所有Unicode字符,而不管当前处于活动状态的代码页(在Tee-Object / sfc中反映了什么)。 (虽然某些字体的 rendering 可能不够用,因为字体支持有限,并且缺少对BMP(基本多语言平面)之外的控制台(em> buffer)的支持(稀有)。 em>会正确保留所有字符,因此在此处复制和粘贴可能会在此处正确呈现-请参见this answer的底部。)

因此,直接输出到控制台的输出不受错误解释的影响,并且通常会按预期打印。

答案 1 :(得分:1)

像sfc一样输出unicode no bom。很棒。

cmd /c 'sfc > out'
get-content out -Encoding Unicode | where { $_ } # singlespace

输出:

Microsoft (R) Windows (R) Resource Checker Version 6.0
Copyright (C) Microsoft Corporation. All rights reserved.
Scans the integrity of all protected system files and replaces incorrect versions with
correct Microsoft versions.
SFC [/SCANNOW] [/VERIFYONLY] [/SCANFILE=<file>] [/VERIFYFILE=<file>]
    [/OFFWINDIR=<offline windows directory> /OFFBOOTDIR=<offline boot directory>]
/SCANNOW        Scans integrity of all protected system files and repairs files with
                problems when possible.
/VERIFYONLY     Scans integrity of all protected system files. No repair operation is
                performed.
/SCANFILE       Scans integrity of the referenced file, repairs file if problems are
                identified. Specify full path <file>
/VERIFYFILE     Verifies the integrity of the file with full path <file>.  No repair
                operation is performed.
/OFFBOOTDIR     For offline repair specify the location of the offline boot directory
/OFFWINDIR      For offline repair specify the location of the offline windows directory
e.g.
        sfc /SCANNOW
        sfc /VERIFYFILE=c:\windows\system32\kernel32.dll
        sfc /SCANFILE=d:\windows\system32\kernel32.dll /OFFBOOTDIR=d:\ /OFFWINDIR=d:\windows
        sfc /VERIFYONLY

或删除null和空白行(Windows将null打印为空格):

(sfc) -replace "`0" | where {$_}