首页 > 解决方案 > “SFC”输出重定向格式问题 - Powershell / Batch

问题描述

我正在研究一个 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

标签: powershellbatch-fileencodingformattingio-redirection

解决方案


js2010 的回答中所述,该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( `r`n) 换行符

PowerShell,当它从外部程序捕获标准输出输出时,返回一个行数组而不是单个多行字符串,并且它可以互换地处理以下换行样式:CRLF(Windows 样式)、LF(Unix 样式)和 CR (过时的 Mac 风格 - 现在非常罕见)。
因此,它将 CRCRLF 视为两个换行符,它们反映在“teed”和变量中捕获的输出中,然后包含额外的空行。
因此,解决方案是将数组元素与标准 CRLF 换行符序列连接起来 -(sfc /?) -join "`r`n"然后将2 个连续 `r`n替换为1,以删除人为引入的换行符:-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它将包含单个多行字符串;用于$content -split "`r`n"拆分为行数组。


至于:

是否有其他命令(例如“sfc”)以相同的方式格式化,或者如果重定向会导致输出损坏?

不是我个人知道的;无条件的 UTF-16LE 输出,如sfc.exe's 的情况,让我觉得不寻常(其他程序可能会在选择加入的基础上提供)。

具有仅限 Windows 传统的旧控制台程序使用(可能已修复)OEM 代码页,这是一种单字节 8 位编码,是 ASCII 的超集。

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

如果您想让您的 PowerShell 会话和可能的所有控制台窗口完全支持 UTF-8,请参阅此答案(但是,这样做仍然需要上述解决方法sfc)。


[1] 直接到控制台输出

sfc输出既不被 PowerShell 捕获也不通过 cmdlet 路由时Tee-Object,直接sfc写入控制台,大概使用 Unicode 版本的WriteConsoleWindows API 函数,它需要 UTF-16LE 字符串。

以这种方式写入控制台允许打印所有 Unicode 字符,而与当前处于活动状态的代码页(反映在chcp/中)无关。[console]::OutputEncoding(虽然某些字符的渲染可能会不足,但由于有限的字体支持和缺乏对 BMP(基本多语言平面)之外的(稀有)字符的支持,控制台缓冲区正确地保留了所有字符,因此在别处复制和粘贴可能会渲染正确存在-请参阅此答案的底部。)

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


推荐阅读