Base64 Encode & Decode in PowerShell

PowerShell encodes Base64 via [Convert]::ToBase64String(), but has a critical trap: it defaults to UTF-16 (Unicode) byte encoding. This produces output that fails to decode on Linux, macOS, or any non-Windows system. Here is how to do it correctly.

Basic Base64 encode and decode

PowerShell does not ship with a dedicated base64 command like Linux does. Instead, you reach into the .NET System.Convert class, which both Windows PowerShell (5.1) and the cross-platform PowerShell 7 are built on. Base64 always operates on raw bytes, so the workflow is always two steps: turn your data into a byte[] array, then convert that array to or from a Base64 string.

The two methods you will use are [Convert]::ToBase64String() to encode bytes into a Base64 string, and [Convert]::FromBase64String() to decode a Base64 string back into bytes. Here is the simplest round trip:

$text    = "Hello, World!"
$bytes   = [System.Text.Encoding]::UTF8.GetBytes($text)
$encoded = [Convert]::ToBase64String($bytes)
$encoded
# SGVsbG8sIFdvcmxkIQ==

$decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($encoded))
$decoded
# Hello, World!

Notice that the encode step requires a text encoding (UTF8 here) to translate the string into bytes, and the decode step requires the same encoding to translate the bytes back into a string. Getting that encoding right is the single most important detail in PowerShell Base64 work, which is the subject of the next section.

The UTF-16 trap: why you must specify UTF-8

You will find a popular one-liner all over the internet that looks shorter and more convenient:

$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("Hello"))
$encoded
# SABlAGwAbABvAA==

That SABlAGwAbABvAA== is not what echo -n Hello | base64 produces on Linux (which is SGVsbG8=). The reason is that [System.Text.Encoding]::Unicode in .NET means UTF-16 little-endian, where every character occupies two bytes. The letter H becomes the byte pair 48 00, and those interleaved null bytes are what create the distinctive padding-heavy ...A...A...A pattern you see in the output.

UTF-16 Base64 will fail to decode correctly in virtually every other ecosystem. Python, JavaScript, the Linux base64 command, JWT libraries, and most APIs assume UTF-8 bytes. If you hand them a UTF-16 Base64 blob, they will either error out or return text peppered with null characters.

This trap is especially sneaky because powershell.exe -EncodedCommand genuinely does require UTF-16 Base64. So a lot of guidance written for that specific feature uses ::Unicode, and people copy it into contexts where UTF-8 is expected. Unless you are deliberately building an -EncodedCommand payload, always use [System.Text.Encoding]::UTF8.

A quick sanity check: a UTF-8 Base64 string of an ASCII message has no null-byte artifacts. If your output looks like SABlAGwAbABvAA== with a capital letter followed by lowercase letters and As in a repeating rhythm, you have accidentally used UTF-16.

Encode strings for cross-platform use (ASCII/UTF-8)

For anything that leaves your machine — API tokens, HTTP Basic Auth headers, data destined for a Linux server, JSON payloads — encode with UTF-8. UTF-8 is a strict superset of ASCII, so pure-ASCII text produces identical bytes either way, and any accented or non-Latin characters are encoded in the universally understood UTF-8 form.

function ConvertTo-Base64Utf8 {
    param([Parameter(Mandatory)][string]$Text)
    [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Text))
}

ConvertTo-Base64Utf8 "café"
# Y2Fmw6k=

A common real-world use is building an HTTP Basic Authentication header, where the format is the Base64 of username:password. The credentials must be UTF-8 per RFC 7617:

$user = "admin"
$pass = "s3cr3t"
$pair = "${user}:${pass}"
$token = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pair))

$headers = @{ Authorization = "Basic $token" }
Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers

If you want the convenience of ::ASCII, be aware it silently replaces any character above code point 127 with a ?. That is fine for guaranteed-ASCII data like a generated token, but dangerous for arbitrary user input. UTF-8 is the safer default in every case.

Decode Base64 back to a string

Decoding mirrors encoding: convert the Base64 string to bytes with [Convert]::FromBase64String(), then turn those bytes back into text with the matching encoding's GetString() method.

$encoded = "SGVsbG8sIFdvcmxkIQ=="
$bytes   = [Convert]::FromBase64String($encoded)
$text    = [System.Text.Encoding]::UTF8.GetString($bytes)
$text
# Hello, World!

The encoding you decode with must match the encoding the string was created with. If a Windows script produced UTF-16 Base64 and you decode it as UTF-8, you will see null characters wedged between every letter. In that case, decode with [System.Text.Encoding]::Unicode instead:

# String was encoded with ::Unicode (UTF-16)
$encoded = "SABlAGwAbABvAA=="
[System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($encoded))
# Hello

[Convert]::FromBase64String() is strict. It throws a FormatException if the input contains invalid characters, has incorrect padding, or includes whitespace such as line breaks. Strip newlines first with ($encoded -replace '\s', '') if you are reading Base64 from a wrapped file or pasted block.

If you are unsure which encoding produced a string, a reliable tactic is to decode the bytes and inspect them. Bytes that alternate between meaningful values and 0x00 are a strong signal of UTF-16; a continuous run of meaningful values is UTF-8 or ASCII.

Encode and decode files

For files, skip the text-encoding step entirely — you want the file's raw bytes, not an interpretation of them as text. In PowerShell 7, the cleanest way to read raw bytes is Get-Content -AsByteStream; in Windows PowerShell 5.1 the equivalent is Get-Content -Encoding Byte. Reaching directly into .NET with [System.IO.File] works on every version and is the most portable choice.

# Encode any file (image, PDF, binary) to a Base64 string
$bytes   = [System.IO.File]::ReadAllBytes("C:\path\to\logo.png")
$encoded = [Convert]::ToBase64String($bytes)
Set-Content -Path "logo.b64" -Value $encoded

To go the other way, read the Base64 text, convert it to bytes, and write those bytes back out as a binary file:

# Decode a Base64 file back to its original binary form
$encoded = Get-Content "logo.b64" -Raw
$bytes   = [Convert]::FromBase64String($encoded.Trim())
[System.IO.File]::WriteAllBytes("C:\path\to\logo-restored.png", $bytes)

Use Get-Content -Raw when reading the encoded file so the entire content arrives as a single string. Without -Raw, Get-Content returns an array of lines, and you would need to join them before decoding.

One useful variant is producing a data: URI for embedding an image directly in HTML or CSS. Just prepend the MIME type and the ;base64, marker:

$bytes = [System.IO.File]::ReadAllBytes("icon.png")
$uri   = "data:image/png;base64," + [Convert]::ToBase64String($bytes)
$uri | Set-Content "icon-datauri.txt"

PowerShell 7 on Linux and macOS

PowerShell 7 (the cross-platform, open-source edition built on .NET) runs natively on Linux and macOS, and all the [Convert] and [System.Text.Encoding] code above works identically there. The encoding rules become even more important in this context, because you are now likely sharing data with native Unix tools that universally expect UTF-8.

One genuine difference worth knowing: in PowerShell 7, the default file and pipeline encoding is UTF-8 (without a BOM), whereas Windows PowerShell 5.1 historically defaulted to UTF-16 for some cmdlets like Out-File and Set-Content. This makes PowerShell 7 more predictable, but you should still pass bytes explicitly through [System.Text.Encoding]::UTF8.GetBytes() rather than relying on cmdlet defaults when correctness matters.

# Identical script runs on Windows, Linux, and macOS under pwsh
$text    = "Crossplatform ✓"
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($text))
$encoded
# Q3Jvc3NwbGF0Zm9ybSDinJM=

# Verify against the native base64 tool on Linux/macOS:
#   echo -n "Crossplatform ✓" | base64
#   -> Q3Jvc3NwbGF0Zm9ybSDinJM=

Because PowerShell 7 sits on the same shell as bash and zsh on those systems, you can also just call the native base64 binary when you want a quick command-line conversion. But for scripts that must run on both Windows and Unix, the .NET API with explicit UTF-8 is the one approach that behaves consistently everywhere.


FAQ

How do I Base64 encode a string in PowerShell?

Use UTF-8 encoding for cross-platform compatibility: $bytes = [System.Text.Encoding]::UTF8.GetBytes("Hello"); $encoded = [Convert]::ToBase64String($bytes). Avoid using [System.Text.Encoding]::Unicode (UTF-16) unless you know the recipient also expects UTF-16.

Why does PowerShell Base64 not decode correctly on Linux?

PowerShell defaults to UTF-16LE (Unicode) byte encoding, which uses 2 bytes per character. The Base64 output looks different from what UTF-8-based tools produce. Specify UTF-8 explicitly: [System.Text.Encoding]::UTF8.GetBytes($string).

How do I decode a Base64 string in PowerShell?

Use [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($encoded)). If the string was encoded with UTF-16, use [System.Text.Encoding]::Unicode instead of UTF8.

Try Base64 encoding and decoding instantly

Paste any string or file — base64.dev auto-detects and converts it instantly.

Open base64.dev →