powershellmultipartform-datadotnet-httpclient

Sending 'multipart/form-data' from PowerShell using [System.Net.Http.HttpClient]


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'.


Solution


  • 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: