Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.2k views
in Technique[技术] by (71.8m points)

powershell - Does Invoke-WebRequest support arrays as POST form parameters?

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"...
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...