Powershell windows 1251 to utf 8

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

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

  • Remove From My Forums
  • Вопрос

  • Получаю по API JSON результат. Но русские буквы отображаются в виде знаков вопроса. Есть идеи, товарищи? Спасибо заранее

    Что запускаю:

    $url2 = "http://********/rooms?limit=3&access_token=******"
    $rres = Invoke-WebRequest -Method GET -Uri $url2 -ContentType "application/json;charset=utf-8" | ConvertFrom-Json
    $Lmres = $rres.chunk
    Write-Host $Lmres[0].Content

    Получаю:

    @{body=???? ??????) ; msgtype=m.text}
    @{body=??????????????; msgtype=m.text}
    @{body=??????????; msgtype=m.text}

    Кодировку запроса к json менял на win-1251, ничего не изменилось.

Ответы

  • С проблемными сайтами отдает ISO-8859-1(что является багом), функцию конвертирования, я привел выше.

    PS > $res = iwr http://php.su
    PS > $res.BaseResponse
    
    
    IsMutuallyAuthenticated : False
    Cookies                 : {}
    Headers                 : {Transfer-Encoding, Connection, Vary, Content-Type...}
    SupportsHeaders         : True
    ContentLength           : -1
    ContentEncoding         :
    ContentType             : text/html
    CharacterSet            : ISO-8859-1
    Server                  : nginx
    LastModified            : 2/28/2018 1:12:32 PM
    StatusCode              : OK
    StatusDescription       : OK
    ProtocolVersion         : 1.1
    ResponseUri             : http://www.php.su/
    Method                  : GET
    IsFromCache             : False
    
    
    
    PS > $res.BaseResponse.CharacterSet
    ISO-8859-1

    PS. Можно скачать портативную версию PowerShell Core и написать скрипт в ней — https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-x64.zip

    • Помечено в качестве ответа

      1 марта 2018 г. 6:36

  • Данного поля в заголовке может и не быть. С WebClient нормально отображается кодировка?

    $wb = New-object System.Net.WebClient -Property @{Encoding = [System.Text.Encoding]::UTF8}
    $wb.Headers.Add("Content-Type","application/json;charset=utf-8")
    $wb.DownloadString($url2)

    • Помечено в качестве ответа
      [technoir]
      1 марта 2018 г. 6:36

I need to convert a text file to UTF-8 format via Windows command prompt. This needs to be done on another machine and I do not have rights to install software on that machine. I need something like:

c:\notepad   source-file target-file --encoding option

Is there a Windows command prompt utility which can do it?

Kamil Maciorowski's user avatar

asked Jan 5, 2017 at 13:58

user1107888's user avatar

I need to convert a text file to utf-8 format via windows command prompt

You can easily do this with PowerShell:

Get-Content .\test.txt | Set-Content -Encoding utf8 test-utf8.txt

Further Reading

  • Convert from most encodings to utf8 with powershell

answered Jan 5, 2017 at 14:38

DavidPostill's user avatar

DavidPostillDavidPostill

154k77 gold badges354 silver badges395 bronze badges

13

Use iconv from GNUWin32 pack. It is much faster, especially if your files are about or more than 1 Gb.

"C:\Program Files (x86)\GnuWin32\bin\iconv.exe" -f cp1251 -t utf-8 source.txt > result.txt

Kamil Maciorowski's user avatar

answered Feb 21, 2018 at 15:09

Raul N-k's user avatar

Raul N-kRaul N-k

711 silver badge1 bronze badge

3

Here is for each convert *.text file to *.sql file:

foreach ($file in get-ChildItem *.txt) {
    Echo $file.name
    Get-Content $file | Set-Content -Encoding utf8 ("$file.name" +".sql")
 }

answered May 20, 2019 at 10:20

nobjta_9x_tq's user avatar

1

You can do this from the command prompt as follows:

powershell -command "Get-Content .\test.txt" > test-utf8.txt

It turns out that piping the output to a file from the command prompt saves as utf-8.

answered Sep 30, 2020 at 20:49

Gord Hooker's user avatar

1

POWERSHELL: # Assumes Windows PowerShell, use -Encoding utf8BOM with PowerShell Core. For multiple files:

FIRST SOLUTION:

$files = Get-ChildItem c:\Folder1\ -Filter *.txt 

foreach ($file in $files) {

    Get-Content $file.FullName | Set-Content "E:\Temp\Destination\$($file.Name)" -Encoding utf8BOM

}

OR, SECOND SOLUTION (for multiple files):

get-item C:\Folder1*.* | foreach-object {get-content -Encoding utf8BOM $_ | out-file ("C:\Folder1" + $_.Name) -encoding default}

OR, THE THIRD SOLUTION: (only for 2 files)

$a = "C:/Folder1/TEST_ro.txt"
 $b = "C:/Folder1/TEST_ro-2.txt"
 (Get-Content -path $a) | Set-Content -Encoding UTF8BOM -Path $b

answered Aug 1, 2022 at 14:19

Just Me's user avatar

Just MeJust Me

8161 gold badge16 silver badges40 bronze badges

For those who want to batch convert several files (e.g.: all *.txt files in folder and sub-folders):

dir *.txt -Recurse | foreach {
  # May remove the line below if you are confident
  Copy-Item $_ $_.bkp
  
  # Note that since we are reading and saving to the same file,
  # we need to enclose the command in parenthesis so it fully executes 
  # (reading all content and closing the file) before proceeding
  (Get-Content $_) | Set-Content -Encoding utf8 $_
}

answered Apr 12 at 13:46

J.Hudler's user avatar

You must log in to answer this question.

Not the answer you’re looking for? Browse other questions tagged

.

I have some text files with different encodings. Some of them are UTF-8 and some others are windows-1251 encoded. I tried to execute following recursive script to encode it all to UTF-8.

Get-ChildItem *.nfo -Recurse | ForEach-Object {
$content = $_ | Get-Content

Set-Content -PassThru $_.Fullname $content -Encoding UTF8 -Force}  

After that I am unable to use files in my Java program, because UTF-8 encoded has also wrong encoding, I couldn’t get back original text. In case of windows-1251 encoded files I get empty output as in case of original files. So it makes corrupt already UTF-8 encoded files.

I found another solution, iconv, but as I see it needs current encoding as parameter.

$ iconv options -f from-encoding -t to-encoding inputfile(s) -o outputfile 

Differently encoded files are mixed in a folder structure, so files should stay on same path.

System uses Code page 852.
Existing UTF-8 files are without BOM.

asked Nov 13, 2018 at 13:32

plaidshirt's user avatar

plaidshirtplaidshirt

5,20919 gold badges92 silver badges181 bronze badges

0

In Windows PowerShell you won’t be able to use the built-in cmdlets for two reasons:

  • From your OEM code page being 852 I infer that your «ANSI» code page is Windows-1250 (both defined by the legacy system locale), which doesn’t match your Windows-1251-encoded input files.

  • Using Set-Content (and similar) with -Encoding UTF8 invariably creates files with a BOM (byte-order mark), which Java and, more generally, Unix-heritage utilities don’t understand.

    • Update: There is a workaround: The New-Item cmdlet, when combined with the -Value parameter, (surprisingly) does create BOM-less UTF-8 files — see this answer.

Note: PowerShell (Core) 7+ now defaults to BOM-less UTF8 and also allows you to pass any available [System.Text.Encoding] instance to the -Encoding parameter, so you could solve your problem with the built-in cmdlets there.

You must therefore use the .NET framework directly:

Get-ChildItem *.nfo -Recurse | ForEach-Object {

  $file = $_.FullName

  $mustReWrite = $false
  # Try to read as UTF-8 first and throw an exception if 
  # invalid-as-UTF-8 bytes are encountered.
  try {
    [IO.File]::ReadAllText($file, [Text.Utf8Encoding]::new($false, $true))
  } catch [System.Text.DecoderFallbackException] {
    # Fall back to Windows-1251
    $content = [IO.File]::ReadAllText($file, [Text.Encoding]::GetEncoding(1251))
    $mustReWrite = $true
  } 

  # Rewrite as UTF-8 without BOM (the .NET frameworks' default)
  if ($mustReWrite) {
    Write-Verbose "Converting from 1251 to UTF-8: $file"
    [IO.File]::WriteAllText($file, $content)
  } else {
    Write-Verbose "Already UTF-8-encoded: $file"
  }

}

Note: As in your own attempt, the above solution reads each file into memory as a whole, but that could be changed.

Note:

  • If an input file comprises only bytes with ASCII-range characters (7-bit), it is by definition also UTF-8-encoded, because UTF-8 is a superset of ASCII encoding.

  • It is highly unlikely with real-world input, but purely technically a Windows-1251-encoded file could be a valid UTF-8 file as well, if the bit patterns and byte sequences happen to be valid UTF-8 (which has strict rules around what bit patterns are allowed where).
    Such a file would not contain meaningful Windows-1251 content, however.

  • There is no reason to implement a fallback strategy for decoding with Windows-1251, because there is no technical restrictions on what bit patterns can occur where.
    Generally, in the absence of external information (or a BOM), there’s no simple and no robust way to infer a file’s encoding just from its content (though heuristics can be employed).

answered Nov 13, 2018 at 14:52

mklement0's user avatar

mklement0mklement0

387k65 gold badges613 silver badges787 bronze badges

6

  • Powervr sgx545 драйвер windows 10
  • Powershell core 7 для windows 7
  • Powershell windows 10 проверка системы
  • Powertoys для windows 10 что это
  • Pre sp2 для windows 7