Public/base64.ps1
|
function ConvertTo-Base64 { <# .SYNOPSIS Converts a string or file to Base64 encoding. .DESCRIPTION Encodes a string or file content to Base64 format. Useful for encoding credentials, file contents, or other data that needs to be transmitted as text. .PARAMETER String String to encode to Base64. .PARAMETER Path Path to a file to encode to Base64. Relative paths are resolved against the current location. The whole file is encoded as one Base64 string, newlines included. .PARAMETER Encoding Text encoding to use (default: UTF8). Options: UTF8, ASCII, Unicode, UTF32. .EXAMPLE # Encode a simple string ConvertTo-Base64 -String "Hello World" .EXAMPLE # Encode credentials ConvertTo-Base64 -String "username:password" .EXAMPLE # Encode from pipeline "My secret text" | ConvertTo-Base64 .EXAMPLE # Encode a file, preserving newlines. Use this rather than piping # Get-Content, which splits on newlines and encodes each line separately. ConvertTo-Base64 -Path ./cloud-init.yaml .EXAMPLE # Encode a file ConvertTo-Base64 -Path "C:\config.txt" .EXAMPLE # Encode with different encoding ConvertTo-Base64 -String "Special chars: éàü" -Encoding Unicode #> [CmdletBinding(DefaultParameterSetName = 'String')] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'String')] [string]$String, [Parameter(Mandatory = $true, ParameterSetName = 'File')] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter()] [ValidateSet('UTF8', 'ASCII', 'Unicode', 'UTF32', 'UTF7')] [string]$Encoding = 'UTF8' ) process { try { if ($PSCmdlet.ParameterSetName -eq 'File') { # .NET resolves relative paths against the process working # directory, which Set-Location does not change, so hand it a # full filesystem path rather than whatever the caller typed. $resolvedPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) # Checked here rather than in a ValidateScript so the error can # name the path that was actually looked for. Explorer hides # known extensions, so a file that looks like 'data' is often # really 'data.txt'. if (-not [System.IO.File]::Exists($resolvedPath)) { if ([System.IO.Directory]::Exists($resolvedPath)) { throw "Path '$Path' is a directory, not a file. Resolved to: $resolvedPath" } $hint = '' $parent = [System.IO.Path]::GetDirectoryName($resolvedPath) $leaf = [System.IO.Path]::GetFileName($resolvedPath) if ($parent -and [System.IO.Directory]::Exists($parent)) { $nearby = @( [System.IO.Directory]::GetFiles($parent, "$leaf.*") | ForEach-Object { [System.IO.Path]::GetFileName($_) } ) if ($nearby.Count -gt 0) { $hint = " Did you mean: $($nearby -join ', ')?" } } throw "File not found: $resolvedPath (from -Path '$Path', relative to $($PWD.Path)).$hint" } # Read file as bytes $bytes = [System.IO.File]::ReadAllBytes($resolvedPath) } else { # Convert string to bytes using specified encoding $bytes = [System.Text.Encoding]::$Encoding.GetBytes($String) } # Convert to Base64 $base64 = [System.Convert]::ToBase64String($bytes) return $base64 } catch { # A bare 'throw' after Write-Error emits a second, confusing # ParentContainsErrorRecordException record. Raise one error. throw "Failed to convert to Base64: $($_.Exception.Message)" } } } Set-Alias -Name Convert-ToBase64 -Value ConvertTo-Base64 function ConvertFrom-Base64 { <# .SYNOPSIS Decodes Base64 back to text, raw bytes, or a file. .DESCRIPTION The inverse of ConvertTo-Base64. Decodes a Base64 string, or the Base64 content of a file, and returns it as text by default. Text is the default because that is the common case, but it is lossy for binary payloads: decoding an encoded image or archive into a string mangles it. Use -OutFile to write the bytes straight to disk, or -AsByteArray to get them back unmodified. Whitespace and line breaks in the input are ignored, so Base64 that has been wrapped across lines or read from a file decodes cleanly. .PARAMETER String The Base64 string to decode. Accepts pipeline input. .PARAMETER Path Path to a file containing Base64 text to decode. Relative paths are resolved against the current location. .PARAMETER Encoding Text encoding used to turn the decoded bytes back into a string (default: UTF8). Options: UTF8, ASCII, Unicode, UTF32. Ignored when -AsByteArray or -OutFile is used. .PARAMETER AsByteArray Return the decoded bytes instead of a string. Use this for binary data. Mutually exclusive with -OutFile. .PARAMETER OutFile Write the decoded bytes to this file instead of returning them. This is the lossless option for binary payloads. Mutually exclusive with -AsByteArray. .OUTPUTS System.String by default, System.Byte[] with -AsByteArray, nothing with -OutFile. .EXAMPLE # Decode a string ConvertFrom-Base64 -String "SGVsbG8gV29ybGQ=" .EXAMPLE # Round-trip through the pipeline "My secret text" | ConvertTo-Base64 | ConvertFrom-Base64 .EXAMPLE # Inspect the userdata attached to a VM (Get-CSVM -Name 'web-01').userdata | ConvertFrom-Base64 .EXAMPLE # Decode a file holding Base64 text ConvertFrom-Base64 -Path ./userdata.b64 .EXAMPLE # Restore a binary payload without corrupting it ConvertFrom-Base64 -Path ./image.b64 -OutFile ./image.png .EXAMPLE # Get the raw bytes back $bytes = ConvertFrom-Base64 -String $encoded -AsByteArray #> [CmdletBinding(DefaultParameterSetName = 'String')] [OutputType([string], [byte[]])] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'String')] [AllowEmptyString()] [Alias('Base64String')] [string]$String, [Parameter(Mandatory = $true, ParameterSetName = 'File')] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter()] [ValidateSet('UTF8', 'ASCII', 'Unicode', 'UTF32', 'UTF7')] [string]$Encoding = 'UTF8', [Parameter()] [switch]$AsByteArray, [Parameter()] [string]$OutFile ) begin { $writeToFile = $PSBoundParameters.ContainsKey('OutFile') if ($AsByteArray -and $writeToFile) { throw 'Specify either -AsByteArray or -OutFile, not both.' } } process { if ($PSCmdlet.ParameterSetName -eq 'File') { # .NET resolves relative paths against the process working # directory, which Set-Location does not change, so hand it a # full filesystem path rather than whatever the caller typed. $resolvedPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) if (-not [System.IO.File]::Exists($resolvedPath)) { if ([System.IO.Directory]::Exists($resolvedPath)) { throw "Path '$Path' is a directory, not a file. Resolved to: $resolvedPath" } $hint = '' $parent = [System.IO.Path]::GetDirectoryName($resolvedPath) $leaf = [System.IO.Path]::GetFileName($resolvedPath) if ($parent -and [System.IO.Directory]::Exists($parent)) { $nearby = @( [System.IO.Directory]::GetFiles($parent, "$leaf.*") | ForEach-Object { [System.IO.Path]::GetFileName($_) } ) if ($nearby.Count -gt 0) { $hint = " Did you mean: $($nearby -join ', ')?" } } throw "File not found: $resolvedPath (from -Path '$Path', relative to $($PWD.Path)).$hint" } $encoded = [System.IO.File]::ReadAllText($resolvedPath) } else { $encoded = $String } # Base64 read from a file, or wrapped at a column, carries line breaks $cleaned = [regex]::Replace($encoded, '\s', '') if ([string]::IsNullOrEmpty($cleaned)) { throw 'No Base64 content to decode: the input was empty or contained only whitespace.' } try { $bytes = [System.Convert]::FromBase64String($cleaned) } catch [System.FormatException] { $preview = if ($cleaned.Length -gt 40) { $cleaned.Substring(0, 40) + '...' } else { $cleaned } throw "Input is not valid Base64 ('$preview'): $($_.Exception.Message)" } if ($writeToFile) { $resolvedOut = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutFile) [System.IO.File]::WriteAllBytes($resolvedOut, $bytes) Write-Verbose "Wrote $($bytes.Length) byte(s) to $resolvedOut" return } if ($AsByteArray) { # Comma keeps the pipeline from unrolling the array into single bytes return , $bytes } return [System.Text.Encoding]::$Encoding.GetString($bytes) } } |