Декодирование Base64 в PowerShell: полное руководство
Где-то в строке лога, в конфиге или в сообщении об ошибке вы на него наталкиваетесь: длинный ряд букв и цифр, изредка с плюсом или слэшем, и один-два знака равенства, подозрительно припаркованных в самом конце. Выглядит как шум. Шумом он не является. Это Base64, и вы уже знаете, чего хотите: то, что он прячет.
Base64 - это перевод, а не сжатие и не замок. Он переписывает любую последовательность байтов в печатаемый текст, используя алфавит из 64 символов плюс знак равенства в качестве завершающего заполнения: четыре символа на каждые три входных байта (поэтому закодированные данные примерно на 33% крупнее исходных). Главная страница этого сайта подробно разбирает алфавит, побитовую арифметику и варианты формата, так что в этой статье мы займёмся тем, где именно PowerShell делает разницу: единственным методом .NET, который вы будете вызывать, правилами, которые он налагает, и десятком уголков реальной работы, где декодирование в PowerShell становится интересным.
Метод и его контракт
PowerShell не имеет собственного cmdlet для Base64. Работу выполняет метод класса .NET, который входит в платформу ещё с .NET Framework 1.1, выпущенного в 2003 году, за три года до выхода самого PowerShell:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
Вот и весь API: одна строка на входе, один байтовый массив на выходе. Он работает в любом PowerShell на любой операционной системе, в Windows PowerShell 5.1 и в PowerShell 7 на Windows, Linux и macOS, потому что это просто .NET. Контракт короткий и легко запоминается, поэтому вот он в виде таблицы:
| Вход | Что вы получите |
|---|---|
$null |
Пустой массив, без ошибки. PowerShell молча превращает $null в пустую строку перед вызовом |
| Пустая строка | Пустой массив, без ошибки |
| Корректные данные | byte[], никогда не строка, даже если данные - это текст |
| Некорректные данные | FormatException, обёрнутый для вас в MethodInvocationException |
Одно предупреждение перед тем, как писать обработку ошибок: у этого FormatException всего одно сообщение, и оно покрывает три разных греха. Символ вне алфавита, больше двух символов заполнения или непробельный символ, спрятанный среди заполнения, - всё это порождает ровно одно и то же предложение. Когда вы его увидите, сообщение не скажет, какой именно грех вы совершили, так что придётся вернуться и перечитать свой ввод:
try {
[System.Convert]::FromBase64String("SGV!G8s=")
}
catch {
$real = $_.Exception.InnerException
$real.GetType().Name
# FormatException
$real.Message
}
И у правила «длина кратно четырём» есть один край, который удивляет людей при первой же встрече. Четыре символа без заполнения - это вполне корректно: просто лишние биты последнего символа отбрасываются. Три символа - не кратное четырём число, и такой ввод отклоняется:
[System.Convert]::FromBase64String("SGVs").Count
# 3: четыре символа без заполнения - это нормально
[System.Convert]::FromBase64String("SGV")
# FormatException: три символа - не кратное четырём число
Что декодер принимает, а что нет
Декодер строг в отношении алфавита и снисходительный ровно в одном конкретном вопросе. Допустимые символы - это 64 цифры Base64 (от A до Z, от a до z, от 0 до 9, плюс и слэш) и знак равенства в качестве завершающего заполнения. Игнорируются ровно четыре пробельных символа - где бы и как часто они ни появлялись: табуляция, перевод строки, возврат каретки и пробел. Официальная документация .NET перечисляет их по их Unicode-названиям, а это значит, что перед вами задокументированная гарантия, а не случайное везение.
На практике это суперсила. MIME, почковое кодирование, сделавшее Base64 знаменитым, переносит закодированные строки на границе 76 символов, поэтому данные, прошедшие через письмо, тикет или лог-файл, обычно приходят, разбитыми на множество строк. Декодеру всё равно. Вставляйте как есть:
$wrapped = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gQmFzZTY0IHRleHQg`r`n" +
"YXJyaXZlcyB3cmFwcGVkIGF0IHNldmVudHktc2l4IGNvbHVtbnMgaW4gbWFpbCwgc28gdGhlIGRl`r`n" +
"Y29kZXIgbXVzdCBub3QgY2FyZS4="
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($wrapped))
# The quick brown fox jumps over the lazy dog. Base64-текст приходит
# переломанным при 76 символах в письме, так что декодеру всё равно.
Всё остальное, что не символ алфавита, - жёсткий стоп. Самые частые нарушители в дикой природе - неразрывный пробел (любимец текста, вставленного с веб-страницы) и метка порядка байтов (невидимая метка, которая цепляется к вам, когда файл был прочитан с неправильной кодировкой). Ни то, ни другое не считается пробельным символом с точки зрения этого метода, поэтому оба варианта приводят к исключению:
try {
[System.Convert]::FromBase64String("SGVs`u{00A0}G8=")
}
catch {
$_.Exception.InnerException.GetType().Name
# FormatException
}
Такая строгость - осознанный выбор, а не упрямство. RFC 4648, стандарт, закрепивший Base64 в 2006 году, предписывает реализациям отклонять символы вне алфавита, если протокол явно не разрешает снисходительность, потому что декодер, молча глотающий чужие символы, можно превратить в скрытый канал для контрабанды данных мимо всего, что проверяет только алфавит. Декодер .NET следует строгому правилу, и вам обычно этого и нужно.
Байтовый массив - это не строка
Метод намеренно останавливается на байтовом массиве. Что эти байты означают - это второе решение, которое можете принять только вы, и ошибиться в нём - значит совершить самый известный промах в работе с Base64 в PowerShell. Допустимое по умолчанию допущение, UTF-8, подходит почти для всего, что есть в интернете, и весь цикл занимает два вызова:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
Кодировки, к которым вы будете обращаться на самом деле, и что делает каждая из них, когда вы ошиблись:
| Кодировка | Когда использовать | Если вы ошиблись |
|---|---|---|
UTF8 |
Веб-API, JSON, JWT, всё современное. Безопасный вариант по умолчанию | Текст в Latin-1 или UTF-16 вернётся кракозябрами |
Unicode (UTF-16LE) |
Данные пришли из Windows-инструментов, из значения реестра или из строки .NET, закодированной до отправки | У каждого символа появляется невидимый зазор, потому что вы прочитали один байт там, где задумано два |
ASCII |
Классические учётные данные HTTP Basic и другие протоколы, гарантированно 7-битные | Всё, что выше значения 127, превращается в вопросительный знак |
Latin1 |
Легаси-европейский текст, старше UTF-8 | Многобайтовые последовательности UTF-8 рассыпаются на несколько неверных букв |
Default |
Почти никогда. Это системная кодировка машины | Ваш скрипт будет по-разному работать при каждом региональном параметре Windows |
Классический провал - текст UTF-8, декодированный как UTF-16. Байты настоящие, метод доволен, а результат всё равно мусор:
# "SGk=" - это байты "Hi" в UTF-8
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SGk="))
# Один нечитаемый символ: 2 байта UTF-8 прочитаны как одна 2-байтовая единица UTF-16
Практическое правило: если декодированный текст выглядит так, будто у каждого символа невидимый зазор вокруг, или будто это совсем другой алфавит, - вы ошиблись ровно на одну кодировку. Спросите, где данные были созданы, и при сомнениях доверяйте UTF-8, но глазами проверьте первые несколько символов. И решайте кодировку до того, как декодировать, а не после того, как кракозябры появятся в вашем логе.
base64url: алфавит, который хорошо себя ведёт в URL
Родственник Base64 встретится вам в каждом API-токене, JWT и идентификаторе, вшитом в URL, с которым вам доведётся иметь дело. Плюс и слэш стандартного Base64 законны в URL только после процентного кодирования, а заполнение из знаков равенства выглядит как разделитель полей. Поэтому RFC 4648 определил алфавит, безопасный для URL и имён файлов: те же 64 символа, только плюс становится дефисом, а слэш - подчёркиванием. Заполнение обычно отбрасывают совсем, потому что длина данных делает его ненужным. В RFC внимательно оговорено, что этот вариант следует называть base64url, а не просто «base64», и дальше в этом разделе мы следуем именно этому.
У .NET есть выделенный класс для этого варианта, System.Buffers.Text.Base64Url, добавленный в .NET 9 вместе с быстрыми методами кодирования и декодирования, построенными целиком на параметрах ReadOnlySpan<T>. Актуальный PowerShell (7.4 и новее, когда он работает на версии .NET, где этот класс уже есть) сегодня действительно может вызывать эти перегрузки со span-параметрами напрямую: связыватель методов теперь выполняет неявное преобразование массив/строка в span, так что [System.Buffers.Text.Base64Url]::DecodeFromChars("--__AQI") работает без лишних церемоний. Всегда так не было: Windows PowerShell 5.1 и старые выпуски PowerShell 7.x вообще не умели привязываться к span-параметрам, а до .NET 9 класса попросту не существовало, поэтому любому скрипту, который должен работать на 5.1, на старом 7.x или на хосте с версией .NET до девятой, по-прежнему нужна переносимая версия: поменяйте два символа местами и восстановите заполнение перед тем, как передать текст стандартному декодеру. Заполнение, которое нужно добавить, - это ровно то, что делает длину кратной четырём:
$token = "--__AQI" # base64url, без заполнения
$standard = $token.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
$bytes = [System.Convert]::FromBase64String($standard)
$bytes -join ","
# 251,239,255,1,2
В этом небольшом блоке живут две ловушки. Первая - арифметика заполнения: данные, чья длина и так кратна четырём, заполнения не требуют, и именно страж -eq 4 держит выражение честным. Вторая - направление: когда вы только декодируете, вы добавляете заполнение и меняете символы; вы никогда не убираете заполнение со входных данных стандартного Base64, потому что стандартные декодеры ожидают, что оно на месте. Если источник - JWT или API-токен, то перед вами base64url без заполнения, и рецепт выше - ровно та форма, которая вам нужна.
Открываем JWT без ключей
JSON Web Token - это три сегмента base64url, склеенные точками: заголовок, данные и подпись. Первые два - обычный JSON, и поскольку Base64 не является шифрованием, прочитать их может любой, у кого есть токен. Это особенность, а не дефект: токен рассчитан на то, чтобы его осматривали, и именно подпись делает его неподдельным. PowerShell превращает этот взгляд в трёхстрочник:
$jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
$parts = $jwt.Split(".")
function Decode-UrlSegment([string]$segment) {
$standard = $segment.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($standard))
}
Decode-UrlSegment $parts[0] | ConvertFrom-Json | ConvertTo-Json -Compress
Decode-UrlSegment $parts[1] | ConvertFrom-Json
# свойство name:
(Decode-UrlSegment $parts[1] | ConvertFrom-Json).name
# John Doe
Три вещи, которые стоит держать в уме. Третий сегмент, подпись, тоже записан в base64url, но декодируется в байты бинарной подписи, а не в текст, так что не ждите там красивого JSON. Заголовок обычно просто сообщает, каким алгоритмом подписан токен (HS256, RS256, ...), а заголовок, в котором написано none, - это красный флаг, а не удобство. И чтение данных - не значит доверять им: base64 позволяет вам увидеть заявления, а делает их подлинными только подпись. Если ваша работа - принимать токены, проверяйте подпись ключом издателя; если ваша работа - отлаживать один из них, кода выше достаточно.
Файлы, PEM и долгий путь к байтам
Самая распространённая форма - текстовый файл, в котором лежит Base64 чего-то побольше: резервной копии, скачанного бинарного файла, сериализованного объекта. Весь путь занимает четыре строки, и современный способ получить вывод - настоящий байтовый массив, а не текстовые догадки:
$encoded = Get-Content -Path ./payload.b64 -Raw
$encoded = $encoded.Trim()
$bytes = [System.Convert]::FromBase64String($encoded)
[System.IO.File]::WriteAllBytes("./payload.bin", $bytes)
$bytes.Length
# сколько байтов содержал этот текст
Обратное чтение исходного бинарного файла - то место, где PowerShell 6 и новее оправдывают своё существование. Параметр -AsByteStream читает сырые байты, а в сочетании с -Raw отдаёт вам настоящий byte[] одним движением:
$bytes = Get-Content -Path ./photo.png -AsByteStream -Raw
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path ./photo.b64 -Value $encoded -NoNewline
$bytes.Length
# исходный размер, до 33-процентного текстового налога
Уберите -Raw - и получите поток отдельных объектов байтов (Object[] при присвоении), что подходит для осмотра, но неправильно для передачи в методы .NET, ожидающие массив. А в Windows PowerShell 5.1 параметра -AsByteStream нет вообще, поэтому на 5.1 надёжное чтение - это [System.IO.File]::ReadAllBytes(), который существует везде.
PEM - это бронированный родственник, которого вы знаете из каждого сертификата и закрытого ключа: тело стандартного Base64, обычно сложенное по 64 символа, между строками -----BEGIN ... и -----END .... Броня - это текст; тело - это данные. Снимите броню, склейте строки, декодируйте:
$pem = Get-Content -Path ./certificate.pem -Raw
$body = ($pem -split "`n") | Where-Object { $_ -notmatch "^-----" } | ForEach-Object { $_.Trim() }
$der = [System.Convert]::FromBase64String(($body -join ""))
$der.Length
# бинарный размер сертификата в DER
Поскольку стандартный декодер в любом случае игнорирует пробельные символы, -join "" - это двойная страховка, а не требование, но явное указание в скрипте того, что именно вы отбрасываете, делает его поведение одинаковым на любой машине и при любой конвенции окончаний строк. В противоположном направлении, при свёртке байтов DER в PEM, нужен всего лишь Base64-кодер плюс две строки текста, и статья про кодирование на сестринском сайте показывает 64-колоночный перенос целиком.
Сертификаты и коробка инструментов Windows
Сертификаты - самые тяжёлые Base64-граждане в повседневной работе, и PowerShell способен взять в руки всю эту семью. Файл PFX - это бинарный пакет из сертификата и закрытого ключа, и именно этот формат вы чаще всего находите, лежащим Base64-текстом в конфиг-файлах и скриптах развёртывания. Обратное декодирование в живой сертификат - это однострочник с типом .NET, и он работает кроссплатформенно в PowerShell 7:
$bytes = [System.Convert]::FromBase64String($pfxText)
$password = ConvertTo-SecureString "secret" -AsPlainText -Force
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes, $password)
$cert.Subject
# CN=example.org
$cert.NotAfter
# когда он перестаёт быть действующим
PowerShell 7 также поставляется с Get-PfxCertificate, который читает файл PFX прямо с диска с параметром -Password, так что для файлов на диске можно пропустить ручное декодирование вовсе. Голый сертификат (без ключа) ещё проще: байты DER идут прямо в тот же тип X509Certificate2, без какого-либо пароля.
Помимо языка, стоит знать два нативных инструмента. На Windows certutil -decode infile.b64 outfile декодирует Base64-файл с семантикой файл-на-вход, файл-на-выход (добавьте -f, чтобы перезаписать), что делает его выбором для быстрых решений в обычном командном окне. У его брата certutil -encode есть флаг, который стоит запомнить: -unicodetext преобразует входной текст в UTF-16 до того, как закодировать его Base64, пряча целое решение о кодировке внутри одного переключателя. На Linux и macOS классическая утилита - base64 -d, которая декодирует файл или стандартный ввод, по умолчанию пропуская переводы строк; в GNU coreutils добавьте -i, если данные ещё содержат пробелы, табуляции или CRLF из Windows-почты.
Команды в Base64-конверте
У PowerShell есть встроенная причина говорить на Base64 с версии 1.0: параметр -EncodedCommand самого хоста. Вы передаёте pwsh Base64-строку, он декодирует байты как UTF-16LE, и результат выполняется как команда. Официальное назначение, прямо из документации, - отправлять команды, которым нужны сложные кавычки или фигурные скобки, не воевая с правилами кавычек внешней оболочки:
$command = "Write-Host encoded-hello"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgAGUAbgBjAG8AZABlAGQALQBoAGUAbABsAG8A
pwsh -NoProfile -EncodedCommand $encoded
# encoded-hello
Прочтите вторую строку этого примера внимательно, потому что на неё спотыкаются все: данные должны быть в UTF-16LE, то есть [System.Text.Encoding]::Unicode. Если вместо этого закодировать команду в UTF-8, PowerShell с удовольствием декодирует её как UTF-16LE и выполнит команду, состоящую из кракозябр, а сообщение об ошибке, которое он выдаст, - точный портрет этой ошибки:
$wrong = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($command))
pwsh -NoProfile -EncodedCommand $wrong
# Ошибка: стена из нечитаемых символов, «The term ... is not recognized...»
Именно из-за этого же механизма команды безопасности интересуются Base64 в PowerShell. Длинный непроницаемый токен, переданный в -EncodedCommand, - распространённая форма для автоматизированных инструментов, и именно поэтому продукты защиты конечных точек декодируют такие данные перед выполнением: в Base64 нет ничего, что скрывало бы команду от декодера, он скрывает её только от человека, читающего список процессов. Если вы генерируете закодированные команды для собственной автоматизации, храните исходную команду рядом с токеном, потому что сам токен не объяснится в три часа ночи.
Декодирование, когда ввод огромный
Для повседневных размеров подход с единственным методом и есть быстрый. Бинарный файл в пять мегабайт становится строкой примерно из 6,9 миллиона символов, и декодирование этой строки занимает однозначные миллисекунды на современной машине. В самой документации .NET сказано, что FromBase64String предназначен для обработки единой строки, содержащей все данные; это правда, и это работает хорошо вплоть до очень больших пределов, потому что метод оперирует строкой на месте, без сколько-нибудь значимых лишних копий.
Когда данные больше, чем вы готовы удерживать в одной строке, или приходят потоком (загрузка, сокет, огромный лог), задокументированный инструмент - это System.Security.Cryptography.FromBase64Transform в обёртке CryptoStream: вы подаёте ему Base64-текст и читаете из него декодированные байты, а живым в любой момент остаётся только небольшой буфер. Обратите внимание: TransformStream, C#-помощник для этой задачи, - это расширение метода, а PowerShell расширения методов не видит, поэтому вы создаёте CryptoStream напрямую:
$inputStream = [System.IO.File]::OpenRead("./payload.b64")
$transform = [System.Security.Cryptography.FromBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new(
$inputStream, $transform, [System.Security.Cryptography.CryptoStreamMode]::Read)
$destination = [System.IO.File]::Create("./payload.bin")
$buffer = New-Object byte[] 65536
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$destination.Write($buffer, 0, $read)
}
$destination.Dispose()
$stream.Dispose()
$inputStream.Dispose()
Для девяноста процентов задач простой путь остаётся правильным: прочитайте весь текстовый файл с помощью Get-Content -Raw, обрежьте пробельные символы, декодируйте, запишите байты. Обращайтесь к потоковой версии, когда файл слишком велик, чтобы его удобно держать в памяти, или когда данные прибывают по кусочкам. И не пытайтесь циклом ходить по строкам и декодировать каждую строку по отдельности: группы из четырёх символов Base64 не уважают ваши переводы строк, поэтому строка, разрезающая группу посередине, не декодируется сама по себе. Прочитайте весь текст, а потом декодируйте один раз.
Ловушки, которые съедают целые полдни
- Угадывание кодировки. UTF-8, прочитанный как UTF-16, или Latin-1, прочитанный как UTF-8, производит уверенные кракозябры. Решайте кодировку по источнику данных, ставьте по умолчанию UTF-8 и посматривайте на первые несколько декодированных символов, прежде чем доверять остальному.
- Невидимые символы с веба. Неразрывный пробел или метка порядка байтов, вставленные со страницы или из письма с форматированием, - это чужой символ для декодера, и он бросает банальное
FormatException. Прогоните ввод через.Trim()и проверку на непечатаемые символы перед декодированием. - Замешательство вокруг заполнения. Стандартный Base64 приходит с
=или==в конце; base64url из токенов приходит без него. Подсовывание одного рецепту, сделанному для другого, - самый частый тихий сбой в API-работе, и проверка длины из раздела про base64url - тот самый страж. - Одно сообщение, три преступления. Поскольку сообщение
FormatExceptionпокрывает и плохие символы, и лишнее заполнение, и загрязнённое заполнение всё разом, блоки catch, которые только записывают сообщение в лог, водят вас по кругу. Логируйте ещё и длину ввода, и первый проблемный участок. - Ожидание строки в ответ. Результат всегда - байтовый массив. В момент, когда вы начинаете напрямую форматировать его как строку, вы получаете список чисел, а не текст. Конвертируйте явной кодировкой, один раз, в самом конце.
- Файловое значение по умолчанию в 5.1. Windows PowerShell 5.1 читает файлы без BOM кодировкой ANSI системы, тогда как PowerShell 7 предполагает UTF-8. Если ваш скрипт читает текстовый файл Base64 на 5.1, а файл в UTF-8 и вокруг данных есть не-ASCII, порча происходит до того, как декодер вообще что-то увидит.
- Отношение к Base64 как к замку. Это перевод. Пароль, токен или секрет в Base64 - это простой текст в костюме, и любой декодер на планете, включая этот, открывает его одной строкой.
Привычки, которые держат скрипты честными
- Обрезайте внешний ввод перед декодированием. Один
.Trim()убирает больше производственных инцидентов, чем любой обработчик ошибок. - Валидируйте перед декодированием, когда источник недоверен: после удаления четырёх допустимых пробельных символов строка должна содержать только символы алфавита и не более двух знаков равенства в конце. Быстрая проверка регулярным выражением превращает таинственное исключение в чёткое сообщение об отклонённом вводе.
- Держите байты байтами вплоть до самого последнего шага. Декодируйте один раз, передайте
byte[]файловому API или кодировщику, которому оно нужно, и только тогда конвертируйте в текст осознанно выбранной кодировкой. - Логируйте длины, а не данные. Размер ввода и размер декодированного вывода рассказывают почти всё об ошибке декодирования, без вставки потенциально чувствительных данных в лог.
- Для всего, что пересекает провод, записывайте, в каком алфавите оно - стандартном или base64url, - и какое правило заполнения, той же строкой кода, которая его декодирует. Ваш будущий я - тот, кто прочитает эту заметку.
Как PowerShell унаследовал свой декодер
Кратчайшая истинная история Base64 в PowerShell состоит в том, что PowerShell не писал его никогда. Метод, которым вы пользуетесь, Convert.FromBase64String, вышел вместе с .NET Framework 1.1 в 2003 году, и каждый PowerShell начиная с версии 1.0 в ноябре 2006 года просто открывал наружу .NET, на котором он работает. Пока проект строился, его звали Monad, впервые он был показан публично на Professional Developers Conference в октябре 2003 года, а к моменту выхода пара кодировщик-декодер .NET, которую он оборачивает, уже три года жила повседневной жизнью.
Сам формат был стандартизирован в тот же год, что и запуск оболочки. RFC 4648, опубликованный в октябре 2006 года, - тот самый документ, который зафиксировал алфавит, правила заполнения, ожидание строгого декодирования и вариант base64url, и по сей день он описывает ровно то поведение, которое реализует FromBase64String. Когда в августе 2016 года PowerShell стал открытым и кроссплатформенным как PowerShell Core, декодер приехал вместе с ним на Linux и macOS без единого изменения, потому что менять было нечего.
Единственное подлинное дополнение - поддерживаемый сообществом модуль Microsoft.PowerShell.TextUtility из PowerShell Gallery, чей cmdlet ConvertFrom-Base64 оборачивает тот же метод .NET и добавляет переключатель -AsByteArray плюс текстовый режим по умолчанию, который декодирует как UTF-8. Установите его командой Install-Module -Name Microsoft.PowerShell.TextUtility, если вам нравится форма cmdlet, но одно предупреждение: модуль сейчас в архиве и больше не поддерживается активно, и это ещё одна причина, по которой встроенный метод остаётся рекомендацией для новых скриптов.
Факты, которые стоит запомнить
- Декодер игнорирует табуляции, переводы строк, возвраты каретки и пробелы в любом месте ввода. Сто сложенных строк декодируются ровно так же, как одна длинная строка.
$nullи пустая строка оба декодируются в пустой массив без единого стона, что делаетFromBase64Stringнеобычно снисходительным на краях.- Единственное сообщение
FormatExceptionпокрывает три разных режима отказа. Когда оно срабатывает, ответ лежит во вводе, а не в сообщении. "SABpAA=="- это строкаHiв собственном внутреннем кодировании PowerShell, UTF-16LE. Она вдвое длиннее UTF-8-кодирования тех же двух букв, и именно это соотношение - отпечаток Windows-нативного текста в любом Base64, который вам доведётся прочитать.-EncodedCommandсуществует с первого же релиза PowerShell, и его данные предписано записывать в UTF-16LE, а не UTF-8. Закодируйте неверной кодировкой - и оболочка с удовольствием выполнит ваши кракозябры.- Новые span-основанные Base64-помощники .NET, включая класс
Base64Url, были недоступны из старых выпусков PowerShell, потому что span - это byref-подобные типы, к которым связыватель методов не умел привязываться. Это изменилось: актуальный PowerShell (7.4+, на версии .NET, достаточно новой, чтобы поставлять этот класс) привязывает аргумент-массив или строку к параметруReadOnlySpan<T>без единой жалобы, так что прямой вызов сегодня работает. Обмен двух символов оправдывает себя как версия, которая ещё и работает на Windows PowerShell 5.1 и старых хостах, а не как последняя оставшаяся дорога. Get-Content -AsByteStreamбез-Rawотдаёт вам поток объектов байтов, а не байтовый массив. Добавьте-Raw- и тип будет ровно тем, что ожидают методы .NET.
Долгий путь вокруг
Всё в этой статье - о том, как взять Base64-строку и вернуть свои данные. Зеркальная операция, превращение данных в Base64, выглядит как однострочник, пока вы не столкнётесь с тем, что строки PowerShell - не байты, что UTF-16 удваивает ваш размер, что у переноса строк есть две штатные ширины, а у вывода base64url - собственная двухсимвольная хирургия. Тому направлению посвящён отдельный полный разбор, со своими ловушками и своей историей, в связанной статье на сестринском сайте, «Кодирование Base64 в PowerShell», на которую эта страница ссылается ниже.
Последнее обновление: 2026-09-07
Связанная статья: Кодирование Base64 в PowerShell: полное руководство