The -Form option
With Power Shell 7.2+ there was a neat feature added for multipart/form data.
From MS Learn Example 6:
$Uri = 'https://api.contoso.com/v2/profile'
$Form = @{
firstName = 'John'
lastName = 'Doe'
email = 'john.doe@contoso.com'
avatar = Get-Item -Path 'c:\Pictures\jdoe.png'
birthday = '1980-10-15'
hobbies = 'Hiking','Fishing','Jogging'
}
$Result = Invoke-WebRequest -Uri $Uri -Method Post -Form $Form
But this feature comes with some downsides. For me the lack of an option to control the MIME-TYPE of the file made it useless. I have to send a PDF as application/pdf
, but PS will send it as octet-stream
.
flexible script using .NET
Inspired by MS Learn Example 5 and answers found in various questions, I crafted my own Power Shell Script using .NET classes. It results in a quite flexible and powerful script, I think:
#
# Assembles a POST WebRequest
#
Add-Type -AssemblyName System.Net.Http, System.Collections
$url = "http://localhost:14783"
$Uri = "$url/public/system_under_test.php"
$cookiedomain = "localhost"
# collect the streams here to dispose them at the end
$OpenStreams = [System.Collections.Generic.List[System.IO.FileStream]]::new()
# returns a .NET ByteArrayContent, useful for text, numbers and others
function Get-Text-Part {
param ( $FieldName, $FieldValue )
$Header = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new('form-data')
$Header.Name = $FieldName
$FieldValueAsBytes =[System.Text.Encoding]::UTF8.GetBytes($FieldValue)
$FieldContent = [System.Net.Http.ByteArrayContent]::new($FieldValueAsBytes)
$FieldContent.Headers.ContentDisposition = $Header
return $FieldContent
}
# returns a .NET StreamContent object , useful for arbitrary files
function Get-File-Part {
param ( $FieldName, $FilePath, $MimeType )
$Header = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new('form-data')
$Header.Name = $FieldName
$Header.FileName = Split-Path -leaf $FilePath
$FileStream = [System.IO.FileStream]::new($FilePath, [System.IO.FileMode]::Open)
$FileContent = [System.Net.Http.StreamContent]::new($FileStream)
$OpenStreams.Add($FileStream)
$FileContent.Headers.ContentDisposition = $Header
$FileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($MimeType)
return $FileContent
}
$MultipartContent = [System.Net.Http.MultipartFormDataContent]::new()
$MultipartContent.Add((Get-Text-Part -FieldName 'captcha' -FieldValue 'so what'))
$MultipartContent.Add((Get-Text-Part -FieldName 'company' -FieldValue 'ACME'))
$MultipartContent.Add((Get-Text-Part -FieldName 'name' -FieldValue 'Jon Doe'))
$MultipartContent.Add((Get-Text-Part -FieldName 'phone' -FieldValue '123'))
$FullFilePath = Join-Path -Path (Get-Location) -ChildPath 'HelloWorld.pdf'
$MultipartContent.Add((Get-File-Part -FieldName 'pdf1' -FilePath $FullFilePath -MimeType 'application/pdf'))
$FullFilePath = Join-Path -Path (Get-Location) -ChildPath 'myImage.jpg'
$MultipartContent.Add((Get-File-Part -FieldName 'image1' -FilePath $FullFilePath -MimeType 'image/jpeg'))
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
$session.Cookies.Add((New-Object System.Net.Cookie("session name", "m5k4ek685786586997097746av", "/", "$cookiedomain")))
# you probably do not need -WebSession and exhaustive -Headers
Invoke-WebRequest -Uri $Uri -Body $MultipartContent -Method 'POST' -WebSession $session -ContentType 'multipart/form-data;'-Headers @{
"Accept"="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
"Accept-Encoding"="gzip, deflate, br"
"Accept-Language"="de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
"Cache-Control"="max-age=0"
"Origin"="$url"
"Referer"="$url/public/system_under_test.php"
"Sec-Fetch-Dest"="document"
"Sec-Fetch-Mode"="navigate"
"Sec-Fetch-Site"="same-origin"
"Sec-Fetch-User"="?1"
"Upgrade-Insecure-Requests"="1"
"sec-ch-ua"="`"Chromium`";v=`"112`", `"Google Chrome`";v=`"112`", `"Not:A-Brand`";v=`"99`""
"sec-ch-ua-mobile"="?0"
"sec-ch-ua-platform"="`"Windows`""
}
# Dispose streams otherwise they could stay locked
foreach ( $stream in $OpenStreams){
$stream.Dispose()
}