Я пытаюсь использовать 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;
}