15

Is there a standard way to make use of Invoke-WebRequest or Invoke-RestMethod in PowerShell to get information from a web page using a query string?

For example, I know that the following when used with a well formed JSON end point will work:

$Parameters = @{
  Name = 'John'
  Children = 'Abe','Karen','Jo'
}
$Result = Invoke-WebRequest -Uri 'http://.....whatever' -Body ( $Parameters | ConvertTo-Json)  -ContentType application/json -Method Get

along with the equivalent Invoke-WebMethod. An important aspect of this is the content type and ConvertTo-JSON which manages the transformation of the parameters specified in the -Body part in to a standard form, including the array aspect of the "Children" field.

What is an equivalent way to do this with a website which uses, say, a comma delimited convention for managing array arguments in the URL or an approach such as "Children[]=Abe&Children[]=Karen&Children=Jo"?

Is there a content type that I'm missing and is there an equivalent ConvertTo-?? that I can use? My guess is that someone has had to do this before.

For context this is an often used way of encoding an array parameter in the URL and is commonly seen in PHP web sites.

passing arrays as url parameter

Edit Removed references to PHP except for specific context and adjusted the title to refer to a query string. The problem is about encoding a query string not PHP per se.

Community
  • 1
  • 1
user1383092
  • 507
  • 1
  • 7
  • 17
  • According to the HTTP/1.1 specification: GET request = no server-side body-parsing. Combining a GET request with a request body makes no sense. – Mathias R. Jessen Sep 07 '15 at 16:49
  • I am not sure what Invoke-WebRequest does behind the scenes and you are doubtless right regarding the nomenclature but in the above use case it will encode the URL using the standard GET query e.g. http://...../?Name=John in the above example - though the way it builds an array parameter is not clear. The array building is the crux of my question. – user1383092 Sep 07 '15 at 16:57
  • I see. I think the easiest way to find out is to break out NetMon or WireShark and see what is sent over the wire – Mathias R. Jessen Sep 07 '15 at 17:20

2 Answers2

19

It seems that the server running PHP is irrelevant here. I think you're asking how to send key/value pairs as query string parameters.

Simple Case

If that's the case, you're in luck. Both Invoke-RestMethod and Invoke-WebRequest will take a [hashtable] in the body and construct your query string for you:

$Parameters = @{
    Name = 'John'
    Children = 'Abe','Karen','Jo'
}
Invoke-WebRequest -Uri 'http://www.example.com/somepage.php' -Body $Parameters -Method Get # <-- optional, Get is the default

Passing Complex Objects

Now seeing that the issue is that you want a query string parameter to have multiple values, essentially an array, this rules out the data types you can pass to the body parameter.

So instead, let's build the URI piece by piece first by starting with a [UriBuilder] object and adding on a query string built using an [HttpValueCollection] object (which allows duplicate keys).

$Parameters = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
$Parameters['Name'] = 'John'
foreach($Child in @('Abe','Karen','Joe')) {
    $Parameters.Add('Children', $Child)
}

$Request = [System.UriBuilder]'http://www.example.com/somepage.php'

$Request.Query = $Parameters.ToString()

Invoke-WebRequest -Uri $Request.Uri -Method Get # <-- optional, Get is the default
briantist
  • 45,546
  • 6
  • 82
  • 127
  • This will result in a uri-query looking like `"?Name=John&Children=System.Object[]"` :\ – Mathias R. Jessen Sep 07 '15 at 17:29
  • Thanks, I'll investigate this. I have edited my question to make it clearer that I am concerned with building a query string. – user1383092 Sep 08 '15 at 07:23
  • @briantist Thanks, that works. I've sprung off that to post an answer close to what I'm looking for. Ideally I'd like to work with PS hash tables and convert those on the fly to an HTTP query string. Your pointers show how to do that. – user1383092 Sep 08 '15 at 08:42
  • 1
    Unfortunately, both `Invoke-RestMethod` and `Invoke-WebRequest` don't URL-encode the values in the given hashtable, which makes this feature effectively completely useless. The approach with the `[HttpValueCollection]` is much better. – Tomalak Nov 26 '18 at 14:09
  • I use post man to pass a get with a body and it works. When I try to pass it using a query string, it fails. can you tell me what I am doing wrong – Golden Lion Apr 19 '23 at 15:06
0

The following appears to work reasonably well as a "first cut". Thanks to @briantist for the key point which was to use the .NET HttpValueCollection. It seems we have to "roll our own" way of building the query string.

The below code snip shows a simple way to transform the hash table containing parameters and values in to a well formed query string by simply traversing the hash table. One limitation is that nesting is not permitted (a parameter value cannot be a complex type such as a hash table).

# Setup, parameters is now a PowerShell hash table
# we convert that on the fly to an appropriate URL
$Parameters = @{
    Name = 'John'
    Children = 'Abe','Karen','Jo'
}
$Uri = 'http://example.com/somepage.php'
$AddSquareBracketsToArrayParameters = $true



$HttpValueCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
foreach ($Item in $Parameters.GetEnumerator()) {
    if ($Item.Value.Count -gt 1) {
        # It is an array, so treat that as a special case.
        foreach ($Value in $Item.Value) {
            # Add each item in the array, optionally mark the name of the parameter
            # to indicate it is an array parameter.
            $ParameterName = $Item.Key
            if ($AddSquareBracketsToArrayParameters) { $ParameterName += '[]' }                 
            $HttpValueCollection.Add($ParameterName, $Value)
        }
    } else {
        # Add the scalar value.
        $HttpValueCollection.Add($Item.Key,$Item.Value)
    }
}
# Build the request and load it with the query string.
$Request  = [System.UriBuilder]($Uri)
$Request.Query = $HttpValueCollection.ToString()

# Now fire off the request.
Invoke-WebRequest -Uri $Request.Uri
user1383092
  • 507
  • 1
  • 7
  • 17
  • Cool, just wanted to point that another way you can check for a value being an array is to do something like this: `if ($Item.Value -is [Array]) { }` – briantist Sep 08 '15 at 16:22
  • @briantist thanks that looks cleaner. Would it also give the right behaviour for an array of a single element? – user1383092 Sep 08 '15 at 16:31
  • Depends on what you mean by the right behavior. It will return `true` if it is truly an array with one element. If not, it will return false. `@('a') -is [Array]` should return `true`. `'a' -is [Array]`: `false`. – briantist Sep 08 '15 at 16:34
  • I should have been more explicit. The behaviour I'm referring to is on the server side. If the server expects a single field (array of unit length) to be called "FieldName" but it ends up being called "FieldName[]" because it is viewed as an array (albeit of unit length) that may cause trouble. I'm not sure of this as it may be dependent on the server side script. – user1383092 Sep 08 '15 at 16:42
  • To me the `[]` syntax you're using for arrays in the query string looks very odd. I've never seen such a thing. Typically what I've seen is that the keys just appear multiple times in the query string with different values (the answer I posted will produce a query string that way) as in `?Key1=Something&Key2=ThingA&Key2=ThingB&Key2=ThingC`. – briantist Sep 08 '15 at 16:55
  • This is outside my area of knowledge, for sure, but my understanding is that it can be language specific. For example, see the following regarding PHP [getting-array-param-out-of-query-string-with-php](http://stackoverflow.com/questions/6243702/getting-array-param-out-of-query-string-with-php) and [parse-query-string-into-an-array](http://stackoverflow.com/questions/5397726/parse-query-string-into-an-array). This kind of implementation detail can be very frustrating. – user1383092 Sep 08 '15 at 17:07
  • One example of an API requiring this is [PagerDuty REST API v2](https://v2.developer.pagerduty.com/docs/migrating-to-api-v2#filtering) : _'Previously, filters may have been specified as a comma-separated list, e.g.:'_ `/api/v1/incidents?service=PAGRDTY,PGRDUTY`. _'In API v2, the parameter will instead look like:'_ `/incidents?service_ids[]=PAGRDTY&service_ids[]=PGRDUTY`. – Petru Zaharia Oct 17 '17 at 21:26
  • This code didn't produce a query string like `children[]=Abe&children[]=Karen&children[]=Jo` for me, it produced `children=Abe%2cKaren%2cJo`. – felixfbecker Sep 10 '18 at 19:40