How PowerShell Does Base64 (Under the Hood)
PowerShell has no built-in Base64 cmdlet. Instead, it exposes .NET's System.Convert.ToBase64String() via the [Convert]type accelerator. The flow is always: text → byte array → Base64 stringand the reverse: Base64 string → byte array → text.
The byte-array step is where everyone trips. You must pick a text encoding — UTF8, ASCII, Unicode (which means UTF-16LE in .NET, not what you might expect), or UTF32. Different encodings produce different byte sequences for the same text, so the resulting Base64 strings differ too. The industry-wide default for Base64 interop is UTF-8.
Encoding a String — The Standard Pattern
# PowerShell 5.1 and 7+ — identical syntax
$text = "Hello 世界 👋"
# Step 1: text → UTF-8 bytes
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
# Step 2: bytes → Base64 string
$encoded = [Convert]::ToBase64String($bytes)
Write-Output $encoded
# SGVsbG8g5LiW55WMIPCfkYs=
# One-liner
$result = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($text))
# Note: [System.Text.Encoding]::UTF8 and [Text.Encoding]::UTF8 are the same
# thing — the System namespace is implicit. Use the shorter form.Decoding Back to a String
$encoded = "SGVsbG8g5LiW55WMIPCfkYs="
# Base64 string → bytes
$bytes = [Convert]::FromBase64String($encoded)
# Bytes → UTF-8 text (must match the encoding used at encode time)
$text = [Text.Encoding]::UTF8.GetString($bytes)
Write-Output $text
# Hello 世界 👋
# Guard against invalid input
try {
$bytes = [Convert]::FromBase64String($userInput)
$text = [Text.Encoding]::UTF8.GetString($bytes)
}
catch [System.FormatException] {
Write-Error "Invalid Base64 input"
}Encoding a File
# Fastest: direct .NET file read (works in PS 5.1 and 7+)
$path = "C:\Users\you\Pictures\logo.png"
$bytes = [System.IO.File]::ReadAllBytes($path)
$encoded = [Convert]::ToBase64String($bytes)
# Save to a file (no trailing newline)
Set-Content -Path "logo.png.b64" -Value $encoded -NoNewline
# PowerShell 7+ cmdlet alternative (slower but idiomatic)
$bytes = Get-Content -Path $path -AsByteStream -Raw
$encoded = [Convert]::ToBase64String($bytes)
# PowerShell 5.1 only (deprecated in 7+)
$bytes = Get-Content -Path $path -Encoding Byte -Raw
$encoded = [Convert]::ToBase64String($bytes)
# Data URL for embedding in HTML
$mime = "image/png"
$dataUrl = "data:$mime;base64,$encoded"
Write-Output $dataUrlDecoding a Base64-Encoded File
# Read the Base64 text, convert back to bytes, write to file
$encoded = Get-Content -Path "logo.png.b64" -Raw
$bytes = [Convert]::FromBase64String($encoded)
[System.IO.File]::WriteAllBytes("logo_restored.png", $bytes)
# Round-trip verification
$orig = Get-FileHash "logo.png" -Algorithm SHA256
$copy = Get-FileHash "logo_restored.png" -Algorithm SHA256
if ($orig.Hash -eq $copy.Hash) {
Write-Output "Round-trip OK"
} else {
Write-Error "Round-trip FAILED — check encoding"
}The -EncodedCommand Parameter (UTF-16LE Only)
# powershell.exe -EncodedCommand requires UTF-16LE bytes.
# This is a PowerShell-specific quirk, NOT a Base64 standard.
# Any other Base64 use (web APIs, JWT, Basic Auth) must use UTF-8.
$script = @"
Get-Process | Where-Object { $_.CPU -gt 10 } | Select-Object Name, CPU
"@
# CRITICAL: must use ::Unicode (which is UTF-16LE in .NET)
$bytes = [Text.Encoding]::Unicode.GetBytes($script)
$encoded = [Convert]::ToBase64String($bytes)
# Run via powershell.exe
powershell.exe -NoProfile -EncodedCommand $encoded
# Or save and launch from cmd.exe / Task Scheduler
Write-Output "powershell.exe -EncodedCommand $encoded"
# Common mistake: using UTF8 for -EncodedCommand produces garbled output
# or "Cannot process the command because of a missing parameter" errors.URL-Safe Base64 for JWTs and URL Parameters
# PowerShell has no built-in URL-safe Base64 — do the character swap yourself
function ConvertTo-Base64Url {
param([Parameter(Mandatory)][string]$Text)
$bytes = [Text.Encoding]::UTF8.GetBytes($Text)
$b64 = [Convert]::ToBase64String($bytes)
# Replace + with -, / with _, strip = padding (JWT style)
return $b64.Replace('+', '-').Replace('/', '_').TrimEnd('=')
}
function ConvertFrom-Base64Url {
param([Parameter(Mandatory)][string]$Encoded)
# Reverse: _ to /, - to +, re-add padding
$b64 = $Encoded.Replace('-', '+').Replace('_', '/')
switch ($b64.Length % 4) {
2 { $b64 += '==' }
3 { $b64 += '=' }
}
$bytes = [Convert]::FromBase64String($b64)
return [Text.Encoding]::UTF8.GetString($bytes)
}
$jwt = ConvertTo-Base64Url -Text '{"user_id":42,"role":"admin"}'
Write-Output $jwt
# eyJ1c2VyX2lkIjo0Miwicm9sZSI6ImFkbWluIn0
$back = ConvertFrom-Base64Url -Encoded $jwt
Write-Output $back
# {"user_id":42,"role":"admin"}HTTP Basic Auth Header
$user = "admin"
$pass = "secret123"
$pair = "$($user):$($pass)"
$token = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($pair))
$headers = @{
"Authorization" = "Basic $token"
"Accept" = "application/json"
}
$response = Invoke-RestMethod `
-Uri "https://api.example.com/protected" `
-Headers $headers
Write-Output $response
# Avoid putting credentials in source. Use Get-Credential or an env var:
# $cred = Get-Credential
# $pair = "$($cred.UserName):$($cred.GetNetworkCredential().Password)"
# $token = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($pair))Streaming a Large File (No Full Load)
# For files over ~200 MB, stream through ToBase64Transform to avoid OOM
$input = [System.IO.File]::OpenRead("C:\big-file.bin")
$output = [System.IO.File]::Create("C:\big-file.b64")
$transform = [System.Security.Cryptography.ToBase64Transform]::new()
$writer = [System.Security.Cryptography.CryptoStream]::new(
$output,
$transform,
[System.Security.Cryptography.CryptoStreamMode]::Write
)
$buffer = [byte[]]::new(65536)
while (($read = $input.Read($buffer, 0, $buffer.Length)) -gt 0) {
$writer.Write($buffer, 0, $read)
}
$writer.FlushFinalBlock()
$writer.Dispose()
$output.Dispose()
$input.Dispose()
Write-Output "Streamed encode complete"PowerShell 5.1 vs 7+ — What Changed
- Get-Content byte read: PS 5.1 uses
-Encoding Byte -Raw; PS 7+ uses-AsByteStream -Raw(the old-Encoding Byteis deprecated). - Default output encoding: PS 5.1 cmdlets like
Out-Filedefault to UTF-16LE with BOM. PS 7+ defaults to UTF-8 without BOM. This matters if you pipe a Base64 string to a file and then read it back — mixed encodings can break round-trip. - Here-strings and apostrophes: Identical in both. The
[Convert]and[Text.Encoding]accelerators work the same across versions. - Error handling:
[Convert]::FromBase64StringthrowsSystem.FormatExceptionon invalid input — same type in both versions.
Common Pitfalls in PowerShell Base64 Code
- Using
[Text.Encoding]::Unicodeexpecting UTF-8 — In .NET,Encoding::Unicodeis UTF-16 Little Endian. It emits two bytes per ASCII character. The resulting Base64 is twice as long as it should be and no other language can decode it as text. Always use::UTF8for cross-platform interop. - Omitting
-NoNewlineon Set-Content —Set-Contentappends a trailing newline by default. If you write the Base64 to a file and read it back, the newline becomes part of the string and[Convert]::FromBase64Stringthrows FormatException on the extra character. Add-NoNewline. - Treating
Encoding::Defaultas portable — On Windows,::Defaultis often Windows-1252. On Linux (PS 7+), it's typically UTF-8. Code that uses::Defaultbreaks when moved between hosts. Pin to::UTF8. - Confusing -EncodedCommand with regular Base64 —
-EncodedCommanduses UTF-16LE. Every other Base64 use case (web APIs, JWT, Basic Auth, image embedding) uses UTF-8. Keep them separate. - Loading huge files with ReadAllBytes —
[File]::ReadAllByteson a 2 GB file allocates 2 GB plus the Base64 output (another ~2.7 GB). Use the streamingCryptoStreampattern above for anything over a few hundred MB.
One-Liner Cookbook
# Encode text
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("Hello"))
# Decode text
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("SGVsbG8="))
# Encode a file
[Convert]::ToBase64String([IO.File]::ReadAllBytes("file.bin"))
# Decode a file
[IO.File]::WriteAllBytes("out.bin", [Convert]::FromBase64String((Get-Content in.b64 -Raw)))
# Encode for -EncodedCommand (UTF-16LE)
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("Get-Date"))
# Basic Auth header
"Basic " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("user:pass"))Key Facts
- Core API:
- [Convert]::ToBase64String($bytes) and [Convert]::FromBase64String($str)
- Default encoding:
- [Text.Encoding]::UTF8 — use for all cross-platform work
- -EncodedCommand:
- Requires [Text.Encoding]::Unicode (UTF-16LE) — PowerShell-specific
- File read (PS 5.1):
- Get-Content -Encoding Byte -Raw OR [IO.File]::ReadAllBytes()
- File read (PS 7+):
- Get-Content -AsByteStream -Raw OR [IO.File]::ReadAllBytes()
- URL-safe:
- Manual character swap — PowerShell has no built-in URL-safe variant
- Streaming:
- CryptoStream + ToBase64Transform for >200 MB files
- Error type:
- System.FormatException on bad input
Related Base64 Tools
- Base64 Encode Online — general-purpose browser encoder
- Base64 Encode in Bash — the Linux/macOS CLI counterpart
- Base64 Encode in C# — same .NET APIs as PowerShell
- Base64 Encode File — language-agnostic file encoder
- URL-Safe Base64 — cross-language URL encoding
- Base64 Decode Online — browser decoder
- JWT Debugger — inspect JWT tokens