Windows powershell русский язык в консоли

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

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

В процессе разработки очень часто возникает необходимость запустить из 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, как кодировка настроенная по умолчанию для консоли.

В данной статье приведу несколько практических примеров по изменению кодировки в 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)

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

Можно заметить, что используя утилиты CMD в консоли ISE кириллица выводится кракозябрами, а в Powershell.exe — такая проблема не наблюдается.
Давайте выясним что является этому причиной

Например при попытке получить результат команды

& "ping" ya.ru

мы увидим такую картину

Ping_ISE

Ping из ISE кракозябрами

Посмотрим кодировку в обоих консолях следующей командой

[Console]::outputEncoding
ISE

outputEncoding_ISE

Кодировка по умолчанию в ISE
Powershell

outputEncoding_PoSh

Кодировка по умолчанию в Powershell

Теперь посмотрим текущую страницу кодировки (CP) в CMD

chcp

CMD_chcp

Кодировка CMD по умолчанию
Вывод

Как мы убедились, кодировки СMD и Powershell.exe совпадают, а ISE в свою очередь использует Windows-1251

Соответственно ISE ожидает что на вход ему и будут попадать символы в кодировке 1251, а по факту CMD пытается передать их в кодировке DOS — cp866

Решение

Решение достаточно простое — поменять используемую кодировку консоли ISE на cp866 командой:

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

После этого повторно выполняем нужную нам команду и получаем приемлемый результат

Ping_ISE_fixed

Корректная кодировка в ISE

Вы можете получить ошибку следующего вида

Исключение при задании "outputEncoding" : "Неверный дескриптор.

Что бы смена кодировки применилась успешно, нужно предварительно выполнить любую CMD команду, которая вернет вам текстовый вывод кракозябрами.

Опять же подойдет Ping, так как при вызове Ping Powershell сразу обращается к Legacy утилите из System32, а не какому то своему встроенному механизму.

После этих действий команда на смену кодировки успешно отработает

Записки администратора

PowerShell не отображает русский язык, если вы поставили Windows на другом языке (проблема может появится с: арабским, китайским, японским языками)

PowerShell не отображает русский язык (Windows 10)

Статья на других языках:
?? – PowerShell does not display Russian (Windows 10)
?? – PowerShell no muestra ruso (Windows 10)

PowerShell — расширяемое средство автоматизации от Microsoft с открытым исходным кодом, состоящее из оболочки с интерфейсом командной строки и сопутствующего языка сценариев.

Википедия

Причина проблемы в использовании не TrueType шрифтов для консоли (при установке Windows на языке отличным от русского).

Решение проблемы:

  1. Щелкните правой клавишей мыши по заголовку окна PowerShell;
  2. Выберите Свойства;
  3. Переключитесь на вкладку Шрифты;
  4. Выберите шрифт Consolas.

PowerShell изменение шрифтов в консоли для поддержки русского языка

Здесь же, можно настроить удобный размер шрифта.

Русский язык должен корректно отображаться в консоли PowerShell.

Для корректного отображения арабского, китайского или японского языков, выбирайте TrueType шрифт с поддержкой символов этого языка.


Проблема PowerShell не отображает русский язык (Windows 10), обсуждалось в этой статье. Я надеюсь, что теперь вы сможете настроить корректное отображение русского, арабского, китайского или другого языка. Однако, если вы столкнетесь с каким-то проблемами установки языка в PowerShell, не стесняйтесь написать в комментариях. Я постараюсь помочь.

  • Remove From My Forums
  • Question

  • Всем доброго времени суток.

    При попытке отправить сообщение из PS вместо кирилического теста приходит ??????

    Как можно это побороть? Если не помогает[System.Console]::OutputEncoding
    = [System.Text.Encoding]::UTF8
    ?

    Сам скрипт:

    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)
            }
        }

    [System.Text.Encoding]::Default.Codepage

    После конвертации в utf-8, часть символов начинает читатся. Но часть остается вопросами.

    [Console]::outputEncoding

    IsSingleByte      : True
    BodyName          : IBM437
    EncodingName      : OEM United States
    HeaderName        : IBM437
    WebName           : IBM437
    WindowsCodePage   : 1252
    IsBrowserDisplay  : False
    IsBrowserSave     : False
    IsMailNewsDisplay : False
    IsMailNewsSave    : False
    EncoderFallback   : System.Text.InternalEncoderBestFitFallback
    DecoderFallback   : System.Text.InternalDecoderBestFitFallback
    IsReadOnly        : False
    CodePage          : 437

    • Edited by

      Sunday, January 17, 2021 6:07 PM

Answers

  • Ваша функция «перекодирования» не может правильно работать. Ведь если конкретного символа нет в какой то кодировке, то он будет заменен на ?. А кодировка 1252 вообще не содержит кириллицы. Вот и получайте кучу знаков вопросов.

    Увы, но в РФ почему то очень мало кто знает как работать с кодировками вообще и с русскими буквами в частности, все норовят делать «перекодировщики» даже не понимая что они не могут работать. :(

    Чтоб все работало правильно вам надо сделать две вещи:

    1. Сохранять файл скрипта в кодировке где есть кириллица, например UTF-8 или UTF-16. При сохранении файла в notepad следует выбрать одну из этих опций. 

    2. Использовать для вывода на консоль кодировку где есть кириллица. Опять же, лучше всего использовать UTF-8/UTF-16, но можно и 1251, например. Перекодирование в последнем случае будет выполнено автоматически, никаких нерабочих
    суррогатов вроде «перекодирования» не потребуется.

    Сменить кодировку консоли можно так: 

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


    This posting is provided «AS IS» with no warranties, and confers no rights.

    • Marked as answer by
      Иван ПродановMicrosoft contingent staff, Moderator
      Thursday, January 14, 2021 5:44 PM

  • Windows powershell подключение к удаленному рабочему столу
  • Windows playstation 3 controller driver for windows
  • Windows powershell не запускается от имени администратора
  • Windows powershell admin что это
  • Windows player windows 7 скачать torrent