Я пытаюсь использовать Process.Start с перенаправленным вводом-выводом для вызова PowerShell.exe со строкой, а для возврата обратно - все в UTF-8. Но я, похоже, не могу выполнить эту работу.
Что я пробовал:
-  Передача команды для запуска через параметр -Command
- Запись PowerShell script в виде файла на диск с кодировкой UTF-8
- Запись PowerShell script в виде файла на диск с UTF-8 с спецификацией
- Запись PowerShell script в виде файла на диск с UTF-16
-  Настройка Console.OutputEncodingкак в моем консольном приложении, так и в PowerShell script
-  Настройка $OutputEncodingв PowerShell
-  Настройка Process.StartInfo.StandardOutputEncoding
-  Выполнение всего с Encoding.UnicodeвместоEncoding.UTF8
В каждом случае, когда я проверяю байты, которые я даю, я получаю разные значения в исходной строке. Мне очень понравилось бы объяснение, почему это не работает.
Вот мой код:
static void Main(string[] args)
{
    DumpBytes("Héllo");
    ExecuteCommand("PowerShell.exe", "-Command \"$OutputEncoding = [System.Text.Encoding]::UTF8 ; Write-Output 'Héllo';\"",
        Environment.CurrentDirectory, DumpBytes, DumpBytes);
    Console.ReadLine();
}
static void DumpBytes(string text)
{
    Console.Write(text + " " + string.Join(",", Encoding.UTF8.GetBytes(text).Select(b => b.ToString("X"))));
    Console.WriteLine();
}
static int ExecuteCommand(string executable, string arguments, string workingDirectory, Action<string> output, Action<string> error)
{
    try
    {
        using (var process = new Process())
        {
            process.StartInfo.FileName = executable;
            process.StartInfo.Arguments = arguments;
            process.StartInfo.WorkingDirectory = workingDirectory;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
            process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
            using (var outputWaitHandle = new AutoResetEvent(false))
            using (var errorWaitHandle = new AutoResetEvent(false))
            {
                process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        output(e.Data);
                    }
                };
                process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        error(e.Data);
                    }
                };
                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
                outputWaitHandle.WaitOne();
                errorWaitHandle.WaitOne();
                return process.ExitCode;
            }
        }
    }
    catch (Exception ex)
    {
        throw new Exception(string.Format("Error when attempting to execute {0}: {1}", executable, ex.Message),
            ex);
    }
}
Update
Я обнаружил, что если я сделаю это script:
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Write-Host "Héllo!"
[Console]::WriteLine("Héllo")
Затем вызовите его через:
ExecuteCommand("PowerShell.exe", "-File C:\\Users\\Paul\\Desktop\\Foo.ps1",
  Environment.CurrentDirectory, DumpBytes, DumpBytes);
Первая строка повреждена, но вторая не соответствует:
H?llo! 48,EF,BF,BD,6C,6C,6F,21
Héllo 48,C3,A9,6C,6C,6F
Это говорит о том, что мой код перенаправления работает нормально; когда я использую Console.WriteLine в PowerShell, я получаю UTF-8, как я ожидаю.
Это означает, что команды PowerShell Write-Output и Write-Host должны делать что-то другое с выходом, а не просто вызывать Console.WriteLine.
Обновление 2
Я даже попробовал следующее, чтобы заставить кодовую страницу консоли PowerShell использовать UTF-8, но Write-Host и Write-Output продолжают создавать разорванные результаты, когда работает [Console]::WriteLine.
$sig = @'
[DllImport("kernel32.dll")]
public static extern bool SetConsoleCP(uint wCodePageID);
[DllImport("kernel32.dll")]
public static extern bool SetConsoleOutputCP(uint wCodePageID);
'@
$type = Add-Type -MemberDefinition $sig -Name Win32Utils -Namespace Foo -PassThru
$type::SetConsoleCP(65001)
$type::SetConsoleOutputCP(65001)
Write-Host "Héllo!"
& chcp    # Tells us 65001 (UTF-8) is being used
Решение
Ответ Ли был правильным. Как говорит Ли, я пытался все заставить PowerShell производить UTF-8, но это кажется невозможным. Вместо этого нам просто нужно прочитать поток, используя ту же кодировку PowerShell (стандартная OEM-кодировка). Нет необходимости сообщать Process.StartInfo для чтения с другой кодировкой, так как он уже читает по умолчанию.
Обновить, снова
Собственно, это не так. Я думаю, что Process.Start использует всю текущую кодировку; когда я запускал его под консольным приложением, он использовал OEM-кодировку и мог читать результат. Но при работе под Windows Service это не так. Поэтому я должен был принудительно его явно.
Вы можете получить кодовую страницу, используемую консолью, по ссылке @andyb, размещенной:
Мне нужно было использовать подписи здесь: http://www.pinvoke.net/default.aspx/kernel32.getcpinfoex
Затем назначьте его:
CPINFOEX info;
if (GetCPInfoEx(CP_OEMCP, 0, out info))
{
    var oemEncoding = Encoding.GetEncoding(info.CodePage);
    process.StartInfo.StandardOutputEncoding = oemEncoding;
}
