4

It seems that Invoke-WebRequest cannot serialize arrays as POST form data:

PS> $uri = "http://httpbin.org/post"

PS> Invoke-WebRequest $uri -Body @{Stuff=[string[]]@("abc", "def")} -Method Post

StatusCode        : 200
StatusDescription : OK
Content           : {
                      "args": {},
                      "data": "",
                      "files": {},
                      "form": {
                        "Stuff": "System.String[]"
                      },
                      "headers": {
                        "Cache-Control": "max-age=259200",
                        "Connection": "close",
                        "Content-Length"...
RawContent        : HTTP/1.0 200 OK
                    Access-Control-Allow-Origin: *
                    Connection: keep-alive
                    Content-Length: 589
                    Content-Type: application/json
                    Date: Fri, 11 Jul 2014 20:40:42 GMT
                    Server...
Forms             : {}
Headers           : {[Access-Control-Allow-Origin, *], [Connection, keep-alive],
                    [Content-Length, 589]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 589

Since .NET does not accept duplicate key names in dictionaries, I can't do something like that:

PS> Invoke-WebRequest $uri -Body @{Stuff="abc"; Stuff="def"} -Method Post
At line:1 char:45
+ Invoke-WebRequest $uri -Body @{Stuff="abc"; Stuff="def"} -Method Post
+                                             ~~~~~
Duplicate keys 'Stuff' are not allowed in hash literals.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : DuplicateKeyInHashLiteral

I double-checked that the error does not come from httpbin by sending a raw HTTP request with content Stuff[]=abc&Stuff[]=def, and I get this response instead:

{
    "args": {},
    "data": "",
    "files": {},
    "form": {
        "Stuff[]": [
            "abc",
            "def"
        ]
    },
    "headers": {
        "Cache-Control": "max-age=259200",
        "Connection": "close",
        "Content-Length"...

1 Answer 1

3

You are correct. The Body param for Invoke-WebRequest is an Object, but for form key-value pairs it expects an IDictionary and does not handle arrays.

A possible work-around is build the request manually using System.Net.HttpWebRequest and send the form key-value pairs in a System.Collections.Specialized.NameValueCollection.

function Invoke-WebRequestExample
{
  [CmdletBinding()]
  Param
  (
    [Parameter(Mandatory=$true)][System.Uri] $Uri,
    [Parameter(Mandatory=$false)][System.Object] $Body,
    [Parameter(Mandatory=$false)][Microsoft.PowerShell.Commands.WebRequestMethod] $Method
    # Extend as necessary to match the signature of Invoke-WebRequest to fit your needs.
  )
  Process
  {
    # If not posting a NameValueCollection, just call the native Invoke-WebRequest.
    if ($Body -eq $null -or $body.GetType() -ne [System.Collections.Specialized.NameValueCollection]) {
      Invoke-WebRequest @PsBoundParameters
      return;
    }

    $params = "";    
    $i = 0;
    $j = $body.Count;
    $first = $true;
    while ($i -lt $j) {       
      $key = $body.GetKey($i);
      $body.GetValues($i) | %{
        $val = $_;
        if (!$first) {
          $params += "&";
        } else {
          $first = $false;
        }
        $params += [System.Web.HttpUtility]::UrlEncode($key) + "=" + [System.Web.HttpUtility]::UrlEncode($val);
      };
      $i++;
    }
    $b = [System.Text.Encoding]::UTF8.GetBytes($params);

    # Use HttpWebRequest instead of Invoke-WebRequest, because the latter doesn't support arrays in POST params.
    $req = [System.Net.HttpWebRequest]::Create($Uri);
    $req.Method = "POST";
    $req.ContentLength = $params.Length;
    $req.ContentType = "application/x-www-form-urlencoded";

    $str = $req.GetRequestStream();
    $str.Write($b, 0, $b.Length);
    $str.Close();
    $str.Dispose();

    [HttpWebResponse] $res = $req.GetResponse();
    $str = $res.GetResponseStream();
    $rdr = New-Object -TypeName "System.IO.StreamReader" -ArgumentList ($str);
    $content = $rdr.ReadToEnd();
    $str.Close();
    $str.Dispose();
    $rdr.Dispose();

    # Build a return object that's similar to a Microsoft.PowerShell.Commands.HtmlWebResponseObject
    $ret = New-Object -TypeName "System.Object";
    $ret | Add-Member -Type NoteProperty -Name "BaseResponse" -Value $res;
    $ret | Add-Member -Type NoteProperty -Name "Content" -Value $content;
    $ret | Add-Member -Type NoteProperty -Name "StatusCode" -Value ([int] $res.StatusCode);
    $ret | Add-Member -Type NoteProperty -Name "StatusDescription" -Value $res.StatusDescription;
    return $ret;
  }
}

$uri = "http://www.someurl.com/";
$b1 = @{myval = "foo"};
Invoke-WebRequestExample -Uri $uri -Body $b1 -Method Post;
$b2 = New-Object -TypeName "System.Collections.Specialized.NameValueCollection";
$b2.Add("myval[]", "foo");
$b2.Add("myval[]", "bar");
Invoke-WebRequestExample -Uri $uri -Body $b2 -Method Post;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.