使用 C/C++ 通过管道到/从 Powershell 设置 UTF-8 输入并获取 UTF-8 输出

Posted

技术标签:

【中文标题】使用 C/C++ 通过管道到/从 Powershell 设置 UTF-8 输入并获取 UTF-8 输出【英文标题】:Set UTF-8 Input and Get UTF-8 Output through pipe to/from Powershell with C/C++ 【发布时间】:2021-08-30 18:22:44 【问题描述】:

我无法将正确的 utf-8 字符串写入 powershell 子进程。 ASCII 字符有效,但 utf-8 字符,例如'ü',会有不同的解释。从同一个 powershell 子进程读取时出现同样的问题。

总结:我想通过我的程序使用 utf-8 编码的 powershell。

更新: 使用AllocConsole(); 分配控制台,然后调用SetConsoleCP(CP_UTF8);SetConsoleOutputCP(CP_UTF8);,正如@mklement 在他的回答中提到的那样,如果您有一个没有任何控制台的GUI 应用程序,它对我有用。如果您有控制台应用程序,则不必手动分配控制台。

更新 2: 如果你有一个 GUI 并调用了AllocConsole(),你可以在之后调用ShowWindow(GetConsoleWindow(), SW_HIDE); 来隐藏控制台,如here 所述。

到目前为止我所尝试的:

将输入和输出编码设置为 utf-8 $OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8进程内 对 UTF-16 执行相同操作以防出现错误,例如...ext.Encoding]::Unicode 对 ISO-Latin 1 (cp1252) 执行相同操作 使用 wchar_t 作为所有测试编码的缓冲区和输入 测试给定字符串的字节顺序 测试 Unicode(每个字符 4 个字节,而不是 2 个) 自己一点一点的构建字符串 将编译器标志设置为 \D UNICODE

编写代码示例:

std::string test("ls ä\n");
DWORD ret = WriteFile(std_in_write, test.c_str(), test.size(), &number_of_bytes_written, nullptr);
if (ret == 0) 
    throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_WRITE_TO_FILE, GetLastError());

输出:ls ├ñ

示例代码:

HANDLE std_in_read = nullptr;
HANDLE std_in_write = nullptr;
HANDLE std_out_read = nullptr;
HANDLE std_out_write = nullptr;
SECURITY_ATTRIBUTES security_attr;
STARTUPINFO startup_info;
PROCESS_INFORMATION process_information;
DWORD buffer_size = 1000000;

security_attr = sizeof(SECURITY_ATTRIBUTES), nullptr, true;

if (!CreatePipe(&std_in_read, &std_in_write, &security_attr, buffer_size)) 
    throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_IN_PIPE, GetLastError());


if (!CreatePipe(&std_out_read, &std_out_write, &security_attr, buffer_size)) 
    throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_OUT_PIPE, GetLastError());


GetStartupInfo(&startup_info);
startup_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup_info.wShowWindow = SW_HIDE;
startup_info.hStdOutput = std_out_write;
startup_info.hStdError = std_out_write;
startup_info.hStdInput = std_in_read;

if (!CreateProcess(TEXT(default_powershell_path), nullptr, nullptr, nullptr, TRUE, 0, nullptr, TEXT(default_windows_path), &startup_info, &process_information)) 
    throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_PROCESS, GetLastError());


std::string test("ls ä\n");
DWORD ret = WriteFile(std_in_write, test.c_str(), test.size(), &number_of_bytes_written, nullptr);
if (ret == 0) 
    throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_WRITE_TO_FILE, GetLastError());


DWORD dword_read;
while (true) 
    DWORD total_bytes_available;
    if (PeekNamedPipe(std_out_read, nullptr, 0, nullptr, &total_bytes_available, nullptr) == 0) 
        throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_COPY_FROM_PIPE, GetLastError());
    

    if (total_bytes_available != 0) 
        DWORD minimum = min(buffer_size, total_bytes_available);
        char buf[buffer_size];
        if (ReadFile(std_out_read, buf, minimum, &dword_read, nullptr) == 0) 
            throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_READ_FILE, GetLastError());
        

        std::string tmp(buf);
        std::cout << tmp << std::endl;
    

    if (total_bytes_available == 0) 
        break;
    

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));

注意:redirect-input-and-output-of-powershell-exe-to-pipes-in-c 不能重复,因为该代码仅适用于 ASCII 字符,根本无法处理 utf-8 字符。

也没有 c-getting-utf-8-output-from-createprocess 的重复项,因为建议的解决方案不会像上面提到的那样起作用,我想输入 utf-8 以及读取 utf-8。

【问题讨论】:

【参考方案1】:

您需要通过SetConsoleCPSetConsoleOutputCP WinAPI 函数,因为 PowerShell CLI 使用它们来解码其 stdin 输入并对其 stdout 输出进行编码。

(相比之下,$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8 仅在 PowerShell 进行外部程序调用时适用于 PowerShell 会话内。)

注意:如果调用进程本身不是控制台应用程序,您可能必须在调用SetConsoleCPSetConsoleOutputCP之前分配一个控制台,使用AllocConsole WinAPI 函数,但坦率地说,我不清楚 (a) 这是否会使这个控制台立即可见(这可能是不希望的)以及 (b) CreateProcess 调用是否会自动使用这个控制台。

如果不行,你可以先通过cmd.exe调用chcp再调用powershell.exe,按照cmd /c "chcp 65001 &gt;NUL &amp; powershell -c ..."的思路; chcp 65001 将控制台代码页设置为 65001,即 UTF-8。

(这会带来额外的开销,但与powershell.exe 进程相比,cmd.exe 进程相对轻量级,chcp.com 也是如此)。

这是一个示例命令,您可以从 PowerShell 运行以进行演示:

& 

  # Save the current code pages.
  $prevInCp, $prevOutCp = [Console]::InputEncoding, [Console]::OutputEncoding

  # Write the UTF-8 encoded form of string 'kö' to a temp. file.
  # Note: In PowerShell (Core) 7+, use -AsByteStream instead of -Encoding Byte
  Set-Content temp1.txt -Encoding Byte ([Text.UTF8Encoding]::new().GetBytes('kö'))

  # Switch to UTF-8, pipe the UTF-8 file's content to PowerShell's stdin,
  # verify that it was decoded correctly, and output it, again encoded as UTF-8.
  cmd /c 'chcp 65001 >NUL & type temp1.txt | powershell -nop -c "$stdinLine = @($input)[0]; $stdinLine -eq ''kö''; Write-Output $stdinLine" > temp2.txt'

  # Read the temporary file as UTF-8 and echo its content.
  Get-Content -Encoding Utf8 temp2.txt

  # Clean up.
  Remove-Item temp[12].txt
  # Restore the original code pages.
  [Console]::InputEncoding = $prevInCp; [Console]::OutputEncoding = $prevOutCp


这会输出以下内容,表明powershell 调用既正确读取了 UTF-8 编码的输入,也将其输出为 UTF-8:

True
ö

注意:

您可以绕过字符编码问题,方法是使用进程内PowerShell SDK 作为创建powershell.exe 子进程的替代方法,但我不知道这对 C++ 来说是多么痛苦。有关 C# 示例,请参阅this answer。

【讨论】:

我测试了它(将SetConsoleOutputCP(CP_UTF8) 放在主要的第一行),但仍然显示了错误的字符。有没有我必须把这条线放在一个特定的地方? 我尝试了AllocConsole() 的方法,但这并没有改变任何东西。在后台显示控制台不是我想要的,但总比没有好。我不确定你通过cmd调用什么?我的 GUI(使用 GTK+ [gtkmm] 制作)可能会调用多个 powershell 子进程。所以,我不能只调用一个 powershell 会话。有没有已知的方法可以通过管道将 utf-8 发送到 powershell? @SimonPio。您可以通过使用进程内 PowerShell SDK 绕过字符编码问题,但我不知道 C++ 有多么痛苦 - 有关 C# 示例,请参阅this answer。至于AllocConsole() - 您可能需要在随后的CreateProcess 调用中调整启动信息属性,但我只是猜测。至于cmd.exe:我的意思是每次需要调用powershell.exe,不要直接调用,通过cmd.exe调用,可以让你先执行chcp65001,设置控制台代码页(s) 到 UTF-8。 我尝试了仅使用控制台的应用程序和SetConsoleOutputCP(CP_UTF8)。这也不起作用。我还尝试在此控制台应用程序中使用CreateProcess(...) 创建一个cmd 进程,并通过WriteFile(...) 传递一个utf-8 字符串std::string test("chcp 65001 &amp; powershell -c mkdir C:\..\ägiüdjöfj\n"。输出又错了。也许我还必须解决将 utf-8 传递给 cmd 的问题?将是一个编码-ception... @SimonPio.,如果您的 PowerShell CLI 调用调用不符合规则的外部程序,输出编码也可能会关闭。我无法从您的代码中看出您正在尝试执行什么 PowerShell 命令。

以上是关于使用 C/C++ 通过管道到/从 Powershell 设置 UTF-8 输入并获取 UTF-8 输出的主要内容,如果未能解决你的问题,请参考以下文章

PowerShe 使用证书签名 ll脚本

PowerShe 命令行调试指引(转)

IPC$ 命名管道

有没有办法通过数据管道以预定义的顺序将文件从 S3 复制到红移

通过 ssh 堡垒主机使用 kubectl 从 bitbucket 管道进行部署

通过管道从 C++ 到 Python 的数据传输 (pywin32)