PowerShell Invoke-RestMethod

Image Description

Daily PowerShell #12

Scripting Daily PowerShell PowerShell Universal Web Cmdlets

October 29, 2021

Invoke-RestMethod is an alternative to Invoke-WebRequest that makes it easy to integrate PowerShell with HTTP REST API services. In this post, we’ll look at how to integrate with REST APIs with Invoke-RestMethod.

In this post, we’ll use PowerShell Universal to produce web APIs. You can learn how to create REST APIs with PowerShell Universal here. You’ll find examples throughout this post as well.

HTTP Methods

HTTP provides numerous verbs that define how an API is intended to react including GET, POST, PUT and DELETE. You can specify the method by using the -Method parameter of Invoke-RestMethod.

GET Method

GET is intended for retrieving data. GET methods should not change state in the REST API.

Let’s assume we have a basic API such as this.

New-PSUEndpoint -URL /user -Endpoint {
 @(
       [PSCustomObject]@{ UserName = "adam"; FirstName = "Adam"; LastName = "Driscoll" }
       [PSCustomObject]@{ UserName = "bruce"; FirstName = "Bruce"; LastName = "Willis" }
       [PSCustomObject]@{ UserName = "tom"; FirstName = "Tom"; LastName = "Hanks" }
   )
}

To invoke this API method, you can invoke the following cmdlet call. Invoke-RestMethod will inspect the Content-Type header and deserialize the JSON data into a PSCustomObject that you can use.

$Objs = Invoke-RestMethod http://localhost:5000/user
$Objs.UserName

POST Method

POST methods are intended for creating new entities with a REST API. POST methods typically accept a body and will return the result of the entity creation. You can POST data by passing objects to the -Body parameter of Invoke-RestMethod.

By default, the body will be an application/w-xxx-form-urlencoded content type.

In this example, we can create an API that accepts a couple of parameters. Each form field will be mapped to the parameter in the endpoint.

New-PSUEndpoint -URL /user -Method POST -Endpoint {
  param(
      [Parameter(Mandatory)]
      $UserName,
      [Parameter(Mandatory)]
      $FirstName,
      [Parameter(Mandatory)]
      $LastName
  )

  [PSCustomObject]@{
      UserName = $UserName 
      FirstName = $FirstName 
      LastName = $LastName
  }
}

To invoke this API, you can call Invoke-RestMethod with a -Body parameter that has a hashtable that includes values for each parameter.

Invoke-RestMethod http://localhost:5000/user -Method POST -Body @{
    UserName = "adamdriscoll"
    FirstName = "Adam"
    LastName = "Driscoll"
}

The Invoke-RestMethod call will return the object as a PSCustomObject.

PUT Method

The PUT method is typically used for updating entities in a REST API. Similar to the POST method, you will usually send a entity body of some kind.

In this example, we can create an API that accepts a couple of parameters. Each form field will be mapped to the parameter in the endpoint.

New-PSUEndpoint -URL /user -Method PUT -Endpoint {
  param(
      [Parameter(Mandatory)]
      $UserName,
      [Parameter(Mandatory)]
      $FirstName,
      [Parameter(Mandatory)]
      $LastName
  )

  [PSCustomObject]@{
      UserName = $UserName 
      FirstName = $FirstName 
      LastName = $LastName
  }
}

Unlike the POST method, with PUT, you will need to specify the content type.

Invoke-RestMethod http://localhost:5000/user -Method PUT -Body @{
    UserName = "adamdriscoll"
    FirstName = "Adam"
    LastName = "Driscoll"
} -ContentType 'application/x-www-form-urlencoded'

DELETE Method

The DELETE method is used for deleting entities within a REST API. This example does not use a body but rather a route parameter to filter out the item we are deleting.

 New-PSUEndpoint -URL /user/:id -Method DELETE -Endpoint {
  @(
        [PSCustomObject]@{ Id = 1; UserName = "adam"; FirstName = "Adam"; LastName = "Driscoll" }
        [PSCustomObject]@{ Id = 2; UserName = "bruce"; FirstName = "Bruce"; LastName = "Willis" }
        [PSCustomObject]@{ Id = 3; UserName = "tom"; FirstName = "Tom"; LastName = "Hanks" }
    ) | Where-Object Id -ne $Id
 }

Invoking this request will return all objects except the deleted one.

Invoke-RestMethod http://localhost:5000/user/2 -Method DELETE

Headers

Headers are included with HTTP requests to provide additional information about the request. This can include content type, length, cookies, user-agent strings and more. You can define additional headers by passing a hashtable to the -Header parameter of Invoke-RestMethod.

Assume you have an API that reads a specific header and returns a result based on that. In this example, if the Credential header contains the SuperAdmin string, then the user will have success, otherwise a 401 unauthorized will be returned.

New-PSUEndpoint -URL /secure-user -Method POST -Endpoint {
    if ($Headers['Credential'] -eq 'SuperAdmin')
    {
        "Success!"
        return 
    }

    New-PSUApiResponse -StatusCode 401
}

To invoke this API with Invoke-RestMethod, you can do the following. The first call will succeed while the second will fail.

Invoke-RestMethod http://localhost:5000/secure-user -Method POST -Headers @{
    Credential = "SuperAdmin"
}

Invoke-RestMethod http://localhost:5000/secure-user -Method POST

Query Strings

Query strings are included as part of the URL when making HTTP calls. They include additional information about the request and since they are part of the URL, can be stored and repeated. This can be a benefit rather than with headers which are not so easily included.

Assume we have an API that returns users based on the query string search string.

New-PSUEndpoint -URL /user -Endpoint {
@(
      [PSCustomObject]@{ UserName = "adam"; FirstName = "Adam"; LastName = "Driscoll" }
      [PSCustomObject]@{ UserName = "bruce"; FirstName = "Bruce"; LastName = "Willis" }
      [PSCustomObject]@{ UserName = "tom"; FirstName = "Tom"; LastName = "Hanks" }
  ) | Where-Object { $_.UserName -match $UserName } 
}

To invoke this API, we would modify the URL to include the query string with the value for username.

Invoke-RestMethod http://localhost:5000/user?username=ad

Content Type

The Content-Type header of an HTTP request helps the server determine what is contained within the body of the request. Some common content types include application/x-www-form-urlencoded, application/json and text/plain

Form Data

By default the Invoke-RestMethod content type will be set to application/x-www-form-urlencoded for POST methods. For all other methods, the content type is blank. This means when using the POST method for sending form data, you do not need to specify the content type.

You can specify a hashtable of values to the body of Invoke-RestMethod and it will automatically encode the data as an HTTP form post.

For example, here is a hashtable we could post with the cmdlet.

@{
    FirstName = "Adam"
    LastName = "Driscoll"
}

The above hashtable will be encoded like this.

FirstName=Adam&LastName=Driscoll

PowerShell Universal endpoints automatically read form keys and values into parameters so you could create an endpoint such as this.

 New-PSUEndpoint -URL /user -Method POST -Endpoint {
   param(
       [Parameter(Mandatory)]
       $FirstName,
       [Parameter(Mandatory)]
       $LastName
   )

   [PSCustomObject]@{
       FirstName = $FirstName 
       LastName = $LastName
   }
 }

Then, you can call Invoke-RestMethod to call that endpoint.

Invoke-RestMethod http://localhost:5000/user -Method POST -Body @{
    FirstName = "Adam"
    LastName = "Driscoll"
}

JSON Data

Many REST APIs accept and produce JSON data. JavaScript Object Notation (JSON) is frequently used by front-end web technologies. You can send and consume JSON easily will Invoke-RestMethod.

Let’s assume we have an endpoint that accepts and returns JSON like the one below. The endpoint will accept a JSON body, deserialize it, update a property and return the JSON back to the caller.

New-PSUEndpoint -Url /user -Method PUT -Endpoint {
    # Body of the HTTP Request
    $User = $Body | ConvertFrom-Json 

    $User.Updated = Get-Date
    $User | ConvertTo-Json
}

To invoke this endpoint with Invoke-RestMethod, you need to serialize a hashtable or PSCustomObject to JSON and specify the proper content type.

Invoke-RestMethod http://localhost:5000/user -Method PUT -Body (@{
    FirstName = "Adam"
    LastName = "Driscoll"
} | ConvertTo-Json) -ContentType 'application/json'

Authentication

The web cmdlets support numerous ways to authenticate.

Default Authentication

Default authentication is used for using your current user credentials and supplying them to systems that support Negotiate authentication. In the Windows world, this is use synonymous with Single-Sign On or Windows Authentication. It uses NTLM or Kerberos to provide a token that the server can use to verify the user’s identity.

With PowerShell Universal, you can enable Windows Authentication via IIS or through the Kestrel web server. Once enabled, you can enforce authentication by specifying the -Authentication parameter.

The below endpoint will return the current user name of the caller of the endpoint. Unauthenticated calls will return a 401 status code.

New-PSUEndpoint -Url /me -Authentication -Endpoint {
   $Identity
}

To call endpoints using Invoke-RestMethod with default authentication, you can use -DefaultAuthentication. You will need to include the -AllowUnencryptedAuthentication parameter if you have not enabled HTTPS.

 Invoke-RestMethod http://localhost:5000/me -UseDefaultCredentials -AllowUnencryptedAuthentication

Bearer Authentication

Bearer authentication takes advantages of tokens such as JSON Web Tokens. Tokens are granted to identities like users and applications to provide authentication and authorization information to web APIs. Tokens are useful because they do not include user credentials, can contain roles, expire after a certain amount of time and can be revoked by the server.

Bearer authentication is enabled by default in PowerShell Universal and you can generate app tokens by navigating to Security \ Tokens in the admin console. Using the same endpoint as with default authentication, we can enforce authentication by the server.

New-PSUEndpoint -Url /me -Authentication -Endpoint {
   $Identity
}

To call this endpoint with a bearer token, we can use the authorization header of Invoke-RestMethod. Place the app token directly in the authorization header value with after the Bearer string.

$AppToken = 'This Token Value' 
Invoke-RestMethod http://localhost:5000/me -Headers @{ Authorization = "Bearer $AppToken" }

Web Sessions

Many times HTTP services maintain a session between HTTP calls. This ensures that users do not need to reauthenticate with every call and session data can be retained to adjust how calls function. Create a web session can be accomplished with the -WebSession and -SessionVariable parameters of Invoke-RestMethod.

As an example, we’ll use the cookie sign in functionality of PowerShell Universal.

First, we need to establish a session using -SessionVariable. The following example calls PowerShell Universal’s login form.

Invoke-RestMethod http://localhost:5000/api/v1/signin -Body (@{
    username = "admin"
    password = "password"
} | ConvertTo-Json) -SessionVariable "PSUSession" -ContentType 'application/json' -Method POST

After running this command, you’ll establish a session that you can reuse for additional web requests. The $PSUSession variable will be defined that contains the session data.

Now, we can call our authenticated endpoint to return our identity.

Invoke-RestMethod http://localhost:5000/me -WebSession $PSUSession

Error Handling

You will frequently run into errors when dealing with REST APIs. This may come in the form of non-200 status codes such as 401 or 403 or server error codes like 500. By default, non-200 status codes will result in an error thrown by Invoke-RestMethod

For example, we can call a nonexistent API in PowerShell Universal. You’ll see an error returned by Invoke-RestMethod.

Invoke-RestMethod http://localhost:5000/notthere

Sometimes, servers will return additional information about what error has occurred.

For example, we may have an endpoint that throws an exception in PowerShell Universal.

New-PSUEndpoint -Url /throws -Endpoint {
  throw "Oh, no!"
} -ErrorAction Stop

When invoke this endpoint in PowerShell 7 or later, you will see the error message.

Invoke-RestMethod http://localhost:5000/throws

This is not the case for Windows PowerShell so you will need to retrieve the exception and get the error message from it.

try 
{ 
    Invoke-RestMethod http://localhost:5000/throws 
} 
catch 
{ 
    [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream()).ReadToEnd() 
}