В данной статье приведу несколько практических примеров по изменению кодировки в 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?
asked Jan 5, 2017 at 13:58
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♦DavidPostill
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
answered Feb 21, 2018 at 15:09
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
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
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 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
You must log in to answer this question.
Not the answer you’re looking for? Browse other questions tagged
.
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
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 isWindows-1250
(both defined by the legacy system locale), which doesn’t match yourWindows-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.
- Update: There is a workaround: The
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
mklement0mklement0
387k65 gold badges613 silver badges787 bronze badges
6