Private/ConvertTo-MultipartFormData.ps1
|
function ConvertTo-MultipartFormData { <# .SYNOPSIS Builds a multipart/form-data request body, including file uploads. .DESCRIPTION Constructs a raw multipart/form-data byte payload and boundary marker, for use as the Body of an Invoke-IDRestMethod call together with an explicit -ContentType of the returned value. Implemented without Invoke-WebRequest's -Form parameter, which is PowerShell Core (6+) only, so this works on both Windows PowerShell 5.1 and PowerShell Core. .PARAMETER Field A hashtable of form field names to values. Values that are a FileSystemInfo object (e.g. from Get-Item) are sent as file parts, with a Content-Type derived from the file's extension (recognised image/CSV extensions only - anything else falls back to application/octet-stream); all other values are sent as plain text fields. .PARAMETER Boundary The multipart boundary string to use. A random one is generated if not supplied. .EXAMPLE $Form = ConvertTo-MultipartFormData -Field @{ Picture = (Get-Item .\photo.jpg) } Invoke-IDRestMethod -Uri $URI -Method POST -Body $Form.Body -ContentType $Form.ContentType .OUTPUTS PSCustomObject with 'Body' (byte[]) and 'ContentType' (string, including the boundary) properties. #> [CmdletBinding()] [OutputType('System.Management.Automation.PSObject')] param( [Parameter(Mandatory = $true)] [Hashtable]$Field, [Parameter(Mandatory = $false)] [String]$Boundary = [System.Guid]::NewGuid().ToString() ) Process { $LF = "`r`n" $Stream = [System.IO.MemoryStream]::new() foreach ($Key in $Field.Keys) { $Value = $Field[$Key] $Bytes = [System.Text.Encoding]::UTF8.GetBytes("--$Boundary$LF") $Stream.Write($Bytes, 0, $Bytes.Length) if ($Value -is [System.IO.FileSystemInfo]) { #A file part's Content-Type must reflect its actual content, not a generic #'application/octet-stream' - confirmed live via Set-IDApplicationIcon: the server #rejects an otherwise-valid image as "invalid format" when the part is labelled #application/octet-stream, even though the bytes/size/extension are all correct. $FileContentType = switch ($Value.Extension.ToLowerInvariant()) { '.png' { 'image/png' } '.jpg' { 'image/jpeg' } '.jpeg' { 'image/jpeg' } '.gif' { 'image/gif' } '.bmp' { 'image/bmp' } '.ico' { 'image/x-icon' } '.csv' { 'text/csv' } default { 'application/octet-stream' } } $Header = "Content-Disposition: form-data; name=`"$Key`"; filename=`"$($Value.Name)`"$LF" + "Content-Type: $FileContentType$LF$LF" $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Header) $Stream.Write($Bytes, 0, $Bytes.Length) $FileBytes = [System.IO.File]::ReadAllBytes($Value.FullName) $Stream.Write($FileBytes, 0, $FileBytes.Length) } else { $Header = "Content-Disposition: form-data; name=`"$Key`"$LF$LF" $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Header) $Stream.Write($Bytes, 0, $Bytes.Length) $Bytes = [System.Text.Encoding]::UTF8.GetBytes([String]$Value) $Stream.Write($Bytes, 0, $Bytes.Length) } $Bytes = [System.Text.Encoding]::UTF8.GetBytes($LF) $Stream.Write($Bytes, 0, $Bytes.Length) } $Bytes = [System.Text.Encoding]::UTF8.GetBytes("--$Boundary--$LF") $Stream.Write($Bytes, 0, $Bytes.Length) [PSCustomObject]@{ 'Body' = $Stream.ToArray() 'ContentType' = "multipart/form-data; boundary=$Boundary" } } } |