1

I am running into a bit of trouble. I am trying to do a POST-request with PowerShell. The problem is that the request-body uses the same key (you can upload multiple images), multiple times, so I can't build a hashtable to send the request. So the requestbody looks like this:

name               value

image              1.jpg
image              2.jpg
subject            this is the subject
message            this is a message

A user with a similar problem (but not the same context) asked this before, and got as a response to use a List with KeyValuePair class. See https://stackoverflow.com/a/5308691/4225082

I cannot seem to create this. I found this https://bensonxion.wordpress.com/2012/04/27/using-key-value-pairs-in-powershell-2/ They use $testDictionary=New-Object “System.Collections.Generic.Dictionary[[System.String],[System.String]]” to make the dictionary, but this doesn't translate to a list.

I managed to create (what I think is needed) by using $r = New-Object "System.Collections.Generic.List[System.Collections.Generic.KeyvaluePair[string,string]]" and created a key by using $s = New-Object “System.Collections.Generic.KeyvaluePair[string,string]", but I can't set the values of that key.

I also tried creating a FormObject, but you also can't use the same key multiple times.

What is the best and/or easiest way to do this?

Community
  • 1
  • 1
  • Thank you for that, I can populate my list now! It doesn't seem to work as expected though. Everything I enter in that list appears as a long, single string in the request, not as name-value pairs :( – Terror Factor Dec 02 '15 at 22:00

1 Answers1

1

I am going to answer my own question. Because of the research, I managed to use better search terms, and found someone with exactly the same problem: Does Invoke-WebRequest support arrays as POST form parameters?

I got rid of a bug (?) by changing [HttpWebResponse] to [System.Net.HttpWebResponse] and added the -WebSession parameter. I only needed it for the cookie, so I implemented that and didn't bother about the other stuff, it might need some tweaking for someone else!

This seemed to work at first glance, BUT for elements with the same key, it created an array, which messed up the order of the requestbody. Without the right order, the website won't accept it.

I messed around a bit more, and now I edited it to make use of multidimensional arrays. So I ended up with this (all credits to the original writer!):

function Invoke-WebRequestEdit
{
    [CmdletBinding()]
    Param
    (
    [Parameter(Mandatory=$true)][System.Uri] $Uri,
    [Parameter(Mandatory=$false)][System.Object] $Body,
    [Parameter(Mandatory=$false)][Microsoft.PowerShell.Commands.WebRequestMethod] $Method,
    [Parameter(Mandatory=$false)][Microsoft.PowerShell.Commands.WebRequestSession] $WebSession
    # 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().BaseType -ne [Array]) {
            Invoke-WebRequest @PsBoundParameters
            return;
        }

        $params = "";    
        $i = 0;
        $j = $body.Count;
        $first = $true;
        foreach ($array in $body){
            if (!$first) {
                $params += "&";
            } else {
                $first = $false;
            }
            $params += [System.Web.HttpUtility]::UrlEncode($array[0]) + "=" + [System.Web.HttpUtility]::UrlEncode($array[1]);
        }
        $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";
    $req.CookieContainer = $WebSession.Cookies

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

    [System.Net.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;
    }
}

The $body parameter is made like this:

$form=@()
$form+= ,@("value1",'somevalue')
$form+=,@("value2", 'somevalue')
$form+=,@("value2",'somevalue')
$form+=,@("value3",'somevalue')

Everything looks good now. It still doesn't work, but my original version with unique keys also doesn't work anymore, so there's probably something else going wrong.

Community
  • 1
  • 1
  • It works now, I forgot to set my WebSession variable as a global var. It worked yesterday, because I tried everything in the console, before creating the function. It was still active afterwards, but after a reboot it wasn't of course. #amateur – Terror Factor Dec 03 '15 at 03:58
  • It seems that the original solution with the NameValueCollection did work, and the order of the headers wasn't that important after all. I do prefer my array approach though :) – Terror Factor Dec 04 '15 at 02:56