I am working on creating a custom function (Invoke-WebExRequest
) for interacting with WebEx API. I have it mostly working, but am unable to get it to work with 'multipart/form-data' data.
I am building my body manually (plain text). I am able to get it to work using 'Invoke-WebRequest
' using the '-ContentType' switch, specifying the type and boundary.
This code block works for posting my multipart/form-data using Invoke-WebRequest
:
$GUID = New-Guid
$Body = @"
------------$($GUID)------------
Content-Disposition: form-data; name="roomId"
$($RoomId)
------------$($GUID)------------
Content-Disposition: form-data; name="html"
<h3>Testing...</h3>
------------$($GUID)------------
"@
$ContentType = "multipart/form-data; boundary=----------$($GUID)------------"
$Result = Invoke-WebRequest -Headers $Headers `
-Uri $URI `
-Method 'Post' `
-Body $Body `
-ContentType $ContentType$GUID = New-Guid
If I just want to post a text message using JSON, I am able to get it to work with [System.Net.Http.HttpClient]
:
$Body = @{
"roomId" = $RoomId
"text" = 'This is just a test'
}
$Client = [System.Net.Http.HttpClient]::new()
$Content = [System.Net.Http.HttpRequestMessage]::new()
$Content.Headers.Add('Authorization',"Bearer $($Token)")
$Content.RequestUri = $URI
$Content.Method = 'Post'
$UTF8 = [System.Text.Encoding]::UTF8
$Body = $($Body | ConvertTo-Json)
$Body = [System.Net.Http.StringContent]::new($Body,$UTF8,'application/json')
$Content.Content = $Body
$Client.SendAsync($Content).GetAwaiter().GetResult()
However, my attempts at posting multipart/form-data using [System.Net.Http.HttpClient]
have so far been unsuccessful.
I have tried the following:
$Body = @"
------------$($GUID)------------
Content-Disposition: form-data; name="roomId"
$($RoomId)
------------$($GUID)------------
Content-Disposition: form-data; name="html"
<h3>Testing with [System.Net.Http.HttpClient]</h3>
------------$($GUID)------------
"@
$Client = [System.Net.Http.HttpClient]::new()
$Content = [System.Net.Http.HttpRequestMessage]::new()
$Content.Headers.Add('Authorization',"Bearer $($Token)")
$Content.RequestUri = $URI
$Content.Method = 'Post'
$UTF8 = [System.Text.Encoding]::UTF8
$Body = [System.Net.Http.StringContent]::new($Body,$UTF8,'multipart/form-data')
$Content.Content = $Body
$Client.SendAsync($Content).GetAwaiter().GetResult()
I do not receive an errors from the code, but the response I receive back is '400 - BadRequest'.
At least one problem with your approach of manually constructing the body is that you forgot to provide a boundary
attribute as part of the Content-Type
header that specifies the GUID that is used to separate the parts of the submission.
Insert the following statement after $Body = [System.Net.Http.StringContent]::new($Body,$UTF8,'multipart/form-data')
(note that you cannot directly include this attribute in the constructor argument passed to [System.Net.Http.StringContent]::new(...)
):
$Body.Headers.ContentType = "multipart/form-data; boundary=`"$guid`""
When using the dedicated .NET APIs (see next section), part-specific Content-Type
headers get added too; to specify them manually inside your $Body
string, add the following after each ------------$($GUID)------------
line (adjust the media type as needed, e.g. text/html
):
Content-Type: text/plain; charset=utf-8
It is oversights such as these that make it preferable to use dedicated APIs, which implicitly ensure proper construction of the request body - see next section.
Use .NET's dedicated class for media type multipart/form-data
, [System.Net.Http.MultipartFormDataContent]
:
Here is self-contained sample code that builds on yours; it works in both PowerShell editions:
# Load the relevant assembly.
# Note: Not strictly necessary in PowerShell (Core) 7: it is loaded by default.
Add-Type -Assembly System.Net.Http
# Sample input data
$roomId = 123
$htmlText = '<h3>Testing...</h3>'
$token = 'a1234c567' # authorization token
$url = 'https://00c78e52-4e81-4dcd-bbc4-0545b8f52ea8.requestcatcher.com'
# Construct the multi-part form data.
$multiPartFormData = [System.Net.Http.MultipartFormDataContent]::new()
# Add the 1st part...
$partContent = [System.Net.Http.StringContent] "$roomId"
# Note:
# * $partContent.Headers.ContentDisposition is automatically filled in as:
# form-data; name=utf-8
# * $partContent.Headers.ContentType is automatically filled in as:
# Content-Type: text/plain; charset=utf-8
# Both these header fields can be overridden manually.
$multiPartFormData.Add($partContent, 'roomId')
# ... and another.
$partContent = [System.Net.Http.StringContent] $htmlText
$multiPartFormData.Add($partContent, 'html')
# Create an HTTP client.
$client = [System.Net.Http.HttpClient]::new()
# Create a POST message with the target URL.
$message = [System.Net.Http.HttpRequestMessage]::new('POST', $url)
# Add an authorization header.
$message.Headers.Add('Authorization', "Bearer $token")
# Assign the multi-part form data as the content (body).
$message.Content = $multiPartFormData
# Finally, send the message, synchronously (the equivalent of an
# Invoke-WebRequest call).
# Note:
# * In PowerShell 7, you could more simply use:
# $client.Send($message)
$client.SendAsync($message).GetAwaiter().GetResult()
# Clean up.
($multiPartFormData, $message, $client).Dispose()
The request will look something like this:
POST / HTTP/1.1
Host: 00c78e52-4e81-4dcd-bbc4-0545b8f52ea8.requestcatcher.com
Authorization: Bearer a1234c567
Content-Length: 322
Content-Type: multipart/form-data; boundary="22379103-e526-4115-8109-150d767bce0d"
--22379103-e526-4115-8109-150d767bce0d
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=roomId
123
--22379103-e526-4115-8109-150d767bce0d
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=html
<h3>Testing...</h3>
--22379103-e526-4115-8109-150d767bce0d--
Note:
The above uses a random requestcatcher.com URL as the target URL.
If you open said URL (stored in the $url
variable) in your browser prior to running the code, you can inspect each resulting request that is later submitted to that URL - that is how the request text posted above was obtained.