Powershell encoding windows 1251

В данной статье приведу несколько практических примеров по изменению кодировки в PowerShell. Ранее я уже публиковал статью про смену кодировки, когда не отображались кириллические символы, сейчас рассмотрю тему более подробно.

Смена кодировки вывода в консоль

Сменить кодировку вывода в консоль можно одним из предложенных ниже способов:

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")

В данных примерах меняем ее на utf8. Это решает проблему с отображением кириллицы. Решение будет действовать только в текущем сеансе консоли.

Кракозябры в PowerShell ISE можно побороть вот так (сменив кодировку на cp866):

[Console]::outputEncoding = [System.Text.Encoding]::GetEncoding('cp866')

При сборке скрипта в exe файл через Win-PS2EXE тоже были проблемы с кодировкой при выводе кириллицы:

В Windows 10 помогло это:

[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("windows-1251")

В Win7 нужную кодировку не подобрал.

Смена кодировки вывода в файл Out-File

Вывод результата консольной утилиты, запущенной через PowerShell, в txt файл выдавал кракозябры. Помогло использование параметра -Encoding и выбор кодировки oem в конвейере в качестве параметра командлета Out-File (в примере zab_api.exe это консольная утилита, вывод которой нужно было писать в файл).

.\zab_api.exe | Out-File data.txt -Encoding oem

Глобальная смена кодировки на уровне системы

В этом решении будет рассказано как решить этот вопрос глобально.

Win + R -> Intl.cpl -> OK

На вкладке «Дополнительно»(«Administrative») Измените язык для программ, не поддерживающих Юникод — выберите Русский (Russian)

Перезагрузите систему

Время на прочтение6 мин

Количество просмотров150K

В процессе разработки очень часто возникает необходимость запустить из powershell скрипта консольное приложение. Что может быть проще?

#test.ps1
& $PSScriptRoot\ConsoleApp.exe

Изучим поведение консольных приложений при запуске их из командной строки, через PowerShell и через PowerShell ISE:

Результат выполнения

В PowerShell ISE возникла проблема с кодировкой, так как ISE ожидает вывод в кодировке 1251. Воспользуемся гуглом и найдем два решения проблемы: c использованием [Console]::OutputEncoding и через powershell pipeline. Воспользуемся первым решением:

test2.ps1

$ErrorActionPreference = "Stop"

function RunConsole($scriptBlock)
{
    $encoding = [Console]::OutputEncoding 
    [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
    try
    {
        &$scriptBlock
    }
    finally
    {
        [Console]::OutputEncoding = $encoding
    }
}

RunConsole {
    & $PSScriptRoot\ConsoleApp1.exe
}

Результат выполнения

В командной строке все хорошо, а вот в ISE ошибка. Exception setting «OutputEncoding»: «The handle is invalid.». Снова берем в руки гугл, и в первом же результате находим решение — надо запустить какое-нибудь консольное приложение для создания консоли. Ну что-же, попробуем.

test3.ps1

$ErrorActionPreference = "Stop"

function RunConsole($scriptBlock)
{
    # Популярное решение "устранения" ошибки: Exception setting "OutputEncoding": "The handle is invalid."
    & cmd /c ver | Out-Null

    $encoding = [Console]::OutputEncoding 
    [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
    try
    {
        &$scriptBlock
    }
    finally
    {
        [Console]::OutputEncoding = $encoding
    }
}

RunConsole {
    & $PSScriptRoot\ConsoleApp1.exe
}

Результат выполнения

Все красиво, все работает. Кто читал мою прошлую заметку, обратил внимание, что WinRM приносит нам много острых впечатлений. Попробуем запустить тест через WinRM. Для запуска воспользуемся вот таким скриптом:

remote1.ps1

param($script)

$ErrorActionPreference = "Stop"

$s = New-PSSession "."
try
{
    $path = "$PSScriptRoot\$script"
    Invoke-Command -Session $s -ScriptBlock { &$using:path }
}
finally
{
    Remove-PSSession -Session $s
}

Результат выполнения

Что-то пошло не так. Решение с созданием консоли не работает. Ранее мы находили два решения проблемы кодировки. Попробуем второй:

test4.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
    function ConvertTo-Encoding ([string]$From, [string]$To)
    {
        Begin
        {
            $encFrom = [System.Text.Encoding]::GetEncoding($from)
            $encTo = [System.Text.Encoding]::GetEncoding($to)
        }
        Process
        {
            $bytes = $encTo.GetBytes($_)
            $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
            $encTo.GetString($bytes)
        }
    }

    Write-Verbose "RunConsole: Pipline mode"
    &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 
}

RunConsole {
    & $PSScriptRoot\ConsoleApp1.exe
}

Результат выполнения

В ISE и через WinRM решение работает, а вот через командную строку и shell — нет.
Надо объединить эти два способа и проблема будет решена!

test5.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
    if([Environment]::UserInteractive)
    {
        # Популярное решение "устранения" ошибки: Exception setting "OutputEncoding": "The handle is invalid."
        & cmd /c ver | Out-Null

        $encoding = [Console]::OutputEncoding 
        [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")

        try
        {
            Write-Verbose "RunConsole: Console.OutputEncoding mode"
            &$scriptBlock
            return
        }
        finally
        {
            [Console]::OutputEncoding = $encoding
        }
    }

    function ConvertTo-Encoding ([string]$From, [string]$To)
    {
        Begin
        {
            $encFrom = [System.Text.Encoding]::GetEncoding($from)
            $encTo = [System.Text.Encoding]::GetEncoding($to)
        }
        Process
        {
            $bytes = $encTo.GetBytes($_)
            $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
            $encTo.GetString($bytes)
        }
    }

    Write-Verbose "RunConsole: Pipline mode"
    &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 
}

RunConsole {
    & $PSScriptRoot\ConsoleApp1.exe
}

Результат выполнения

Кажется, что проблема решена, но продолжим исследование и усложним наше консольное приложение, добавив в него вывод в stdError.

Результат выполнения

Становится все веселее :) В ISE исполнение скрипта прервалось на середине, а через WinRM мало того, что прервалось, так еще сообщение из stdErr прочитать невозможно. Первым шагом решим проблему с остановкой запускаемого из скрипта приложения, для этого перед запуском приложения изменим значение глобальной переменной $ErrorActionPreference.

test7.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
    if([Environment]::UserInteractive)
    {
        # Популярное решение "устранения" ошибки: Exception setting "OutputEncoding": "The handle is invalid."
        & cmd /c ver | Out-Null

        $encoding = [Console]::OutputEncoding 
        [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")

        try
        {
            Write-Verbose "RunConsole: Console.OutputEncoding mode"
            $prevErrAction = $ErrorActionPreference
            $ErrorActionPreference = "Continue"
            try
            {
                &$scriptBlock
                return
            }
            finally
            {
                $ErrorActionPreference = $prevErrAction
            }
        }
        finally
        {
            [Console]::OutputEncoding = $encoding
        }
    }

    function ConvertTo-Encoding ([string]$From, [string]$To)
    {
        Begin
        {
            $encFrom = [System.Text.Encoding]::GetEncoding($from)
            $encTo = [System.Text.Encoding]::GetEncoding($to)
        }
        Process
        {
            $bytes = $encTo.GetBytes($_)
            $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
            $encTo.GetString($bytes)
        }
    }

    Write-Verbose "RunConsole: Pipline mode"
    $prevErrAction = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    try
    {
        &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 
        return
    }
    finally
    {
        $ErrorActionPreference = $prevErrAction
    }
}

RunConsole {
    & $PSScriptRoot\ConsoleApp2.exe
}
Write-Host "ExitCode = $LASTEXITCODE"

Результат выполнения

Для тех что знает о существовании параметра -ErrorAction

error.cmd

echo error message 1>&2

errorActionTest.ps1

#error.cmd
#echo error message 1>&2

#errorActionTest.ps1
$ErrorActionPreference = "Stop"
Write-Host "before"
Invoke-Expression -ErrorAction SilentlyContinue -Command $PSScriptRoot\error.cmd
Write-Host "after"

Какой будет результат выполнения такого скрипта?

Вторым шагом доработаем скрипт удаленного запуска через WinRM, чтобы он не падал:

remote2.ps1

param($script)

$ErrorActionPreference = "Stop"

$s = New-PSSession "."
try
{
    $path = "$PSScriptRoot\$script"

    $err = @()
    $r = Invoke-Command -Session $s -ErrorAction Continue -ErrorVariable err -ScriptBlock `
    {
        $ErrorActionPreference = "Stop"
        & $using:path | Out-Host
        return $true
    } 

    if($r -ne $true)
    {
        Write-Error "The remote script was completed with an error"
    }

    if($err.length -ne 0)
    {
        Write-Warning "Error occurred on remote host"
    }
}
finally
{
    Remove-PSSession -Session $s
}

Результат выполнения

И осталось самое сложное — скорректировать сообщение формируемое через stdErr и при этом не изменить его положение в логе. В процессе решения этой задачи коллеги предложили самостоятельно создать консоль, воспользовавшись win api функцией AllocConsole.

test8.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "continue"

$consoleAllocated = [Environment]::UserInteractive
function AllocConsole()
{
    if($Global:consoleAllocated)
    {
        return
    }

    &cmd /c ver | Out-Null
    $a = @' 
[DllImport("kernel32", SetLastError = true)] 
public static extern bool AllocConsole(); 
'@

    $params = New-Object CodeDom.Compiler.CompilerParameters 
    $params.MainClass = "methods" 
    $params.GenerateInMemory = $true 
    $params.CompilerOptions = "/unsafe" 
 
    $r = Add-Type -MemberDefinition $a -Name methods -Namespace kernel32 -PassThru -CompilerParameters $params

    Write-Verbose "Allocating console"
    [kernel32.methods]::AllocConsole() | Out-Null
    Write-Verbose "Console allocated"
    $Global:consoleAllocated = $true
}

function RunConsole($scriptBlock)
{
    AllocConsole

    $encoding = [Console]::OutputEncoding 
    [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
    $prevErrAction = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    try
    {
        & $scriptBlock
    }
    finally
    {
        $ErrorActionPreference = $prevErrAction
        [Console]::OutputEncoding = $encoding
    }
}

RunConsole {
    & $PSScriptRoot\ConsoleApp2.exe
}
Write-Host "ExitCode = $LASTEXITCODE"

Избавится от информации, которую добавляет powershell к stdErr мне так и не удалось.

Надеюсь, что эта информация окажется полезной не только мне! :)

update 1
В некоторых сценариях использования создавалась дополнительная консоль, в которую выдавался результат выполнения скриптов. В скрипт test8.ps1 внесены исправления.

update 2
Так как у многих комментаторов статьи возникла путаница между понятиями набор символов (char set) и кодировка (encoding) хотел бы еще раз обратить внимание, что в статье решается проблема именно несоответствия кодировок консоли и вызываемого приложения.

Как можно увидеть из скрипта test8.ps1, кодировка указывается в статическом свойстве [Console]::OutputEncoding, и никто не мешает указать в нем одну из кодировок семейства unicode:

[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")

Но, для работы скрипта в стандартной консоли windows (aka cmd.exe) необходимо изменить шрифт консоли со стандартного «Rasters Fonts» на Consolas или «Lucida Console». Если бы данные скрипты мы использовали только на собственных рабочих станциях или серверах, то такое изменение было бы допустимо, но так как нам приходится распространять наши решения заказчикам, вмешиваться в системные настройки серверов мы не имеем права. Именно по этой причине в скриптах используется cp866, как кодировка настроенная по умолчанию для консоли.

Posted on 18/05/2016 by sie

Традиционные приложения (native application) запускаемые в консоли cmd.exe, как правило не являются unicode-приложениями. Иначе говоря, они ожидают ввод и выводят информацию в обычной кодировке, например, CP866 или CP1251. С другой стороны, Powershell является unicode-приложением. Это означает, что Powershell должен выполнять перекодировку в обе стороны при вызове native application. По умолчанию он это делает в большинстве случаев правильно. Но если на целевой системе вместо кодировки по умолчанию для non-unicode программ стоит не «Русский», что соответствует CP866, а «Английский» — CP437, при работе «русской» native application мы получим проблемы: программа не будет понимать ввод, а ее вывод будет абракадаброй.

Ситуацию можно исправить. В Powershell за перекодировку отвечают две переменные:

[Console]::OutputEncoding : отвечает за перекодировку <native application> -> <Powershell>

$OutputEncoding : отвечает за перекодировку <Powershell> -> <native application>

(Есть ещё [Console]::InputEncoding, которая отвечает за ввод с клавиатуры в консольное приложение)

Есть ещё случай, когда вывод native application происходит не в консоль, а перехватывается Powershell (например, при удаленном вызове через WS-Management). Тогда полученные строки придётся перекодировать «вручную». Пример такого скрипта https://xaegr.wordpress.com/2007/01/24/decoder/

Спасибо за наводку Александр AKA Kazun

Примеры:

[Console]::OutputEncoding = [Text.Encoding]::Unicode

[Console]::OutputEncoding = [Text.Encoding]::UTF8

$OutputEncoding = [Text.Encoding]::GetEncoding(1251)

[Text.Encoding]::Unicode

Filed under: Powershell, Windows |

Напомню, программа-оболочка «Windows PowerShell» версии 5.1 поставляется в составе операционной системы «Windows 10». Программу-оболочку «PowerShell» версии 7 для операционной системы «Windows 10» придется загрузить и установить отдельно, она не входит (пока) в состав операционной системы «Windows 10».

Сегодня обычно принято работать с текстовыми файлами в кодировке UTF-8 (обычно без метки BOM).

Нужно иметь в виду, что программа «Windows PowerShell» версии 5.1 обрабатывает запускаемые с ее помощью скрипты по старинке: по умолчанию считает их за файлы с однобайтовой кодировкой (Windows-1251, Windows-1252 и тому подобные). Чтобы она правильно интерпретировала скрипты в кодировке UTF-8, в начало файла следует добавлять метку BOM.

Программа «PowerShell» версии 7 может работать со скриптами как в кодировке UTF-8 с меткой BOM, так и в кодировке UTF-8 без метки BOM.

Особенно это важно, если в скрипте используется вывод в консоль символов, не входящих в таблицу ASCII. Например, вывод в консоль сообщений на русском языке.

Разбор проблемы на примере

Предположим, есть следующий простой скрипт в файле «script.ps1» (кодировка файла UTF-8 без метки BOM):

$file = $args[0]
if ($null -eq $file) {
    "Укажите в параметре путь к файлу. Скрипт прерывает работу."
    return
}
"$file"

Этот скрипт получает через первый параметр строку, которая должна содержать путь к файлу. Если параметр не указан, скрипт выводит сообщение об ошибке и прерывает работу. Иначе скрипт продолжает работу (в данном случае он просто выводит полученный им первый параметр в консоль).

Запускаем скрипт в программе «PowerShell» версии 7, параметр не указываем:

Всё в порядке.

Теперь запускаем этот же скрипт в программе «Windows PowerShell» версии 5.1, параметр не указываем:

И получили кучу сообщений об ошибках.

Реакция программы «Windows PowerShell» версии 5.1 на скрипт в кодировке UTF-8 без метки BOM, на самом деле, может сильно варьироваться в зависимости от символов, содержащихся в скрипте.

Например, сообщения об ошибках могут быть разные. В данном случае в итоге ошибка свелась к сообщению об «UnexpectedToken». Если определенным образом модифицировать фразу на русском языке, то можно получить сообщение про «TerminatorExpectedAtEndOfString» или еще какое-нибудь.

В принципе, из сообщения на вышеприведенной иллюстрации даже можно понять, где конкретно сломался синтаксический анализатор программы «Windows PowerShell» версии 5.1. В данном случае — это строчная русская буква «ф». В кодировке UTF-8 она представлена двумя байтами: D1 84. А программа «Windows PowerShell» версии 5.1 по умолчанию интерпретирует код скрипта побайтно. Второй байт буквы «ф» — 84 она читает в однобайтной кодировке Windows-1251 как символ (нижняя двойная открывающая кавычка). Таким образом, получается, что эта кавычка закрывает выводимую строку текста, а все последующие символы анализатор интерпретирует как другой элемент языка (токен), ну и для анализатора, понятно, этот элемент языка становится неожиданным (несколько команд в одной строке должны разделяться символом точки с запятой, а его тут нет).

Если в данном случае убрать из фразы на русском языке строчную букву «ф», то синтаксически скрипт будет интерпретирован программой «Windows PowerShell» версии 5.1 правильно, ошибки исчезнут. Вместо русских букв, правда, в консоль будут выведены кракозябры. Так что это никак не может являться решением проблемы:

Плохие решения

1) Можно преобразовать текст скрипта в кодировку Windows-1251. В программе «Windows PowerShell» версии 5.1 всё заработает, да. Но следует иметь в виду, что однобайтные кодировки — это прошлый век, сегодня следует использовать Юникод и юникодные кодировки, а конкретно UTF-8. Я даже не буду объяснять, почему это так. Кому интересно — рекомендую почитать о причинах возникновения Юникода. Тогда всё станет понятно.

2) Написать сообщение об ошибке на английском языке. Да, это сработает для любых символов, входящих в таблицу ASCII. Как известно, начало таблицы Юникода совпадает с таблицей ASCII. Кроме этого, в юникодной кодировке UTF-8 символы таблицы ASCII представляются одним байтом. Из-за этого символы таблицы ASCII (в том числе буквы латиницы) одинаково представляются в кодировке UTF-8 и в однобайтных кодировках типа Windows-1251, Windows-1252 и так далее. Однако, это не наш метод. Я хочу писать программы, которые будут говорить и на русском языке тоже.

Решение на будущее

Писать скрипты только для работы в программе «PowerShell» версии 7 и последующих, игнорируя программу «Windows PowerShell» версии 5.1. Пусть пользователи пользуются актуальными программами, а не разным старьем. Это решение выгодно для разработчика скриптов (не нужно писать две разных версии скрипта и тестировать их для двух разных программ-оболочек). Пользователю придется загрузить и установить «PowerShell» версии 7 (в общем, это не так уж и сложно).

Хорошее решение

Добавить в скрипт в кодировке UTF-8 метку BOM. Скрипт в такой кодировке будет правильно интерпретирован и программой «Windows PowerShell» версии 5.1 и программой «PowerShell» версии 7. (Правда, некоторые другие программы не любят метку BOM, но подавляющее большинство современных программ умеет с нею работать.)

In the development process is very often necessary to run a powershell script from a console application. What could be easier?

the

#test.ps1
& $PSScriptRoot\ConsoleApp.exe


Examine the behavior of console applications when running them from the command line, using PowerShell and using PowerShell ISE:

execution Result

In PowerShell ISE, there is a problem with encoding, as the ISE expects the output to the encoding 1251. Use Google and find two problem solving: c use [Console]::OutputEncoding and through the powershell pipeline. Let’s use the first solution:

test2.ps1

$ErrorActionPreference = "Stop"

function RunConsole($scriptBlock)
{
$encoding = [Console]::OutputEncoding 
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
try
{
&$scriptBlock
}
finally
{
[Console]::OutputEncoding = $encoding
}
}

RunConsole {
& $PSScriptRoot\ConsoleApp1.exe
}

execution Result

At the command prompt, all is well, but in the ISE error. Exception setting «OutputEncoding»: «The handle is invalid.».. Again, take the hands of Google and the first result, we find a solution, and you have to run the console application to create a console. Well, let’s try.

test3.ps1

$ErrorActionPreference = "Stop"

function RunConsole($scriptBlock)
{
# Popular solution to the "troubleshoot" error: Exception setting "OutputEncoding": "The handle is invalid.".
& cmd /c ver | Out-Null

$encoding = [Console]::OutputEncoding 
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")
try
{
&$scriptBlock
}
finally
{
[Console]::OutputEncoding = $encoding
}
}

RunConsole {
& $PSScriptRoot\ConsoleApp1.exe
}

execution Result

Everything is beautiful, everything works. Those who have read my last note, I noticed that WinRM brings us a lot of sharp impressions. Try to run the test via WinRM. To run use this script:

remote1.ps1

param($script)

$ErrorActionPreference = "Stop"

$s = New-PSSession "."
try
{
$path = "$PSScriptRoot\$script"
Invoke-Command -Session $s -ScriptBlock { &$using:path }
}
finally
{
Remove-PSSession -Session $s
}

execution Result

Something went wrong. The solution with the creation of the console is not working. We have previously found two solutions to the problem encoding. Let’s try the second:

test4.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
function ConvertTo-Encoding ([string]$From, [string]$To)
{
Begin
{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process
{
$bytes = $encTo.GetBytes($_)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}

Write-Verbose "RunConsole: Pipline mode"
&$scriptBlock | ConvertTo-Encoding cp866, windows-1251 
}

RunConsole {
& $PSScriptRoot\ConsoleApp1.exe
}

execution Result

In the ISE and using WinRM solution works, but via the command line and shell no.

It is necessary to combine these two methods and the problem will be solved!

test5.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
if([Environment]::UserInteractive)
{
# Popular solution to the "troubleshoot" error: Exception setting "OutputEncoding": "The handle is invalid.".
& cmd /c ver | Out-Null

$encoding = [Console]::OutputEncoding 
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")

try
{
Write-Verbose "RunConsole: Console.OutputEncoding mode"
&$scriptBlock
return
}
finally
{
[Console]::OutputEncoding = $encoding
}
}

function ConvertTo-Encoding ([string]$From, [string]$To)
{
Begin
{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)

Process
{
$bytes = $encTo.GetBytes($_)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}

Write-Verbose "RunConsole: Pipline mode"
&$scriptBlock | ConvertTo-Encoding cp866, windows-1251 
}

RunConsole {
& $PSScriptRoot\ConsoleApp1.exe
}

execution Result

It seems that the problem is solved, but will continue to study and complicate our console application and add the output to stdError.

execution Result

It gets better :) In the ISE, the script execution was interrupted in the middle, and using WinRM not only that, I interrupted, another message from stdErr can’t be read. The first step will solve the problem with a stop run from the script application to do this before you run the application change the value of the global $ErrorActionPreference variable.

test7.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "Continue"

function RunConsole($scriptBlock)
{
if([Environment]::UserInteractive)
{
# Popular solution to the "troubleshoot" error: Exception setting "OutputEncoding": "The handle is invalid.".
& cmd /c ver | Out-Null

$encoding = [Console]::OutputEncoding 
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866")

try
{
Write-Verbose "RunConsole: Console.OutputEncoding mode"
$prevErrAction = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try
{
&$scriptBlock
return
}
finally
{
$ErrorActionPreference = $prevErrAction
}
}
finally
{
[Console]::OutputEncoding = $encoding
}
}

function ConvertTo-Encoding ([string]$From, [string]$To)
{
Begin
{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process
{
$bytes = $encTo.GetBytes($_)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}

Write-Verbose "RunConsole: Pipline mode"
$prevErrAction = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try
{
&$scriptBlock | ConvertTo-Encoding cp866, windows-1251 
return
}
finally
{
$ErrorActionPreference = $prevErrAction
}
}

RunConsole {
& $PSScriptRoot\ConsoleApp2.exe
}
Write-Host "ExitCode = $LASTEXITCODE"

execution Result

For those that know about the-ErrorAction parameter

error.cmd

the

echo error message 1 > &2

errorActionTest.ps1

the

#error.cmd
#echo error message 1 > &2

#errorActionTest.ps1
$ErrorActionPreference = "Stop"
Write-Host "before"
Invoke-Expression -ErrorAction SilentlyContinue -Command $PSScriptRoot\error.cmd
Write-Host "after"

What will be the result of the execution of such a script?

The second step will modify the script remotely using WinRM, so he didn’t fall:

remote2.ps1

param($script)

$ErrorActionPreference = "Stop"

$s = New-PSSession "."
try
{
$path = "$PSScriptRoot\$script"

$err = @()
$r = Invoke-Command -Session $s-ErrorAction Continue-ErrorVariable err-ScriptBlock `
{
$ErrorActionPreference = "Stop"
& $using:path | Out-Host
return $true
} 

if($r-ne $true)
{
Write-Error "The remote script was completed with an error"
}

if($err.length-ne 0)
{
Write-Warning "Error occurred on remote host"
}
}
finally
{
Remove-PSSession -Session $s
}

execution Result

And it remains the most difficult — to adjust the generated message through stdErr and not to change its position in the log. In the process of solving this task, colleagues have offered to create their own console using the win api function AllocConsole.

test8.ps1

$ErrorActionPreference = "Stop"
#$VerbosePreference = "continue"

$consoleAllocated = [Environment]::UserInteractive
function AllocConsole()
{
if($Global:consoleAllocated)
{
return
}

&cmd /c ver | Out-Null
$a = @' 
[DllImport("kernel32", SetLastError = true)] 
public static extern bool AllocConsole(); 
'@

$params = New-Object CodeDom.Compiler.CompilerParameters 
$params.MainClass = "methods" 
$params.GenerateInMemory = $true 
$params.CompilerOptions = "/unsafe" 

$r = Add-Type -MemberDefinition $a -Name methods -Namespace kernel32 -PassThru -CompilerParameters $params

Write-Verbose "Allocating console"
[kernel32.methods]::AllocConsole() | Out-Null
Write-Verbose "Console allocated"
$Global:consoleAllocated = $true
}

function RunConsole($scriptBlock)
{
AllocConsole

$encoding = [Console]::OutputEncoding 

$prevErrAction = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try
{
&$scriptBlock
}
finally
{
$ErrorActionPreference = $prevErrAction
[Console]::OutputEncoding = $encoding
}
}

RunConsole {
& $PSScriptRoot\ConsoleApp2.exe
}
Write-Host "ExitCode = $LASTEXITCODE"

Get rid of information that adds powershell stdErr to me and failed.

I hope that this information will be useful not only to me! :)

update 1

In some usage scenarios, created additional console, which has been the result of the script execution. In the script test8.ps1 bug fixes.

update 2

As many commentators of the article there is confusion between the concepts character set (char set) and encoding (encoding) would like to once again note that the article solves the problem of mismatch encoding of the console and the calling application.

As you can see from the script test8.ps1, the encoding is specified in the static property [Console]::OutputEncoding, and no one bothers to specify in it one of the unicode encodings of the family:

the

[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")

But for the script to work in standard windows console (aka cmd.exe) you need to change the console font from standard «Rasters Fonts» to Consolas or «Lucida Console». If these scripts we used on their own workstations or servers, such a change would be acceptable, but since we have to distribute our solutions to customers, to intervene in the system settings of the servers we have no right. It is for this reason that the script uses cp866 as the charset which is configured by default to console.

Article based on information from habrahabr.ru

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Hp1010 драйвер windows 7 x64
  • Скрывается меню пуск windows 10
  • C windows system32 userinit exe c windows system32
  • Windows 10 flash buy
  • Как удалить точки восстановления на windows 10 кроме последней