Creating a Powershell module as an API wrapper

We all love today’s modern web with lots of API’s available, both for retrieving information from various sources, gaining additional insights and for transform and enrich your data. Most API’s today are RESTFUL, meaning that they should follow the REST principles. REST is not a standard, it’s more a guideline for how to design your API.

With the REST guidelines in place many API’s share the same or similar structure and with that it gets easier to work with API’s as you can make use of the same techniques. If you’re familiar with Windows Powershell this is one of the easiest ways of exploring an API.

This was also the reason why my good colleague Martin Ehrnst and I decided to do a talk on using Powershell and API’s on the Nordic Infrastructure Conference (NIC) in Oslo this year. The slides and demos from that session, Invoke-{your}RestMethod will available here shortly.

One of the demos shows how to use Powershell modules as a wrapper for an API. While the built-in Powershell cmdlet Invoke-RestMethod is a great way to explore an API it expects you to know how to deal with HEADERS, Body payload, Authentication etc.

For many users this could be stopping them from using the API. To do some of the “dirty work” for your users (and for your self) the mentioned demo show an example of how to mask this in a Powershell module.

I won’t get into all details about Powershell modules or REST API’s, but I will show you how you could combine these to make it easier for you and your users when consuming an API.

For more details about writing a Powershell module, check out this guide from Microsoft. To dive in to the details about REST, check out the creator Roy Fielding’s publication

In this post we will continue with the two API’s in the NIC session, namely the Star Wars API (SWAPI) and the SupportBee API. These API’s does also use some HATEOAS (Hypermedia as the Engine of Application State) techniques where they have hyperlinks in the response.

Powershell module

While this API might not be the best one for showing the benefits of building a module considering it doesn’t do any Authentication, requires no headers etc. it’s easy to work with and many people knows about the Star Wars universe making it easier to think of ways to be creative with the different resources.

So, how do we build a module around a Rest API?

First of it’s important that you get to know the API and its structure as we did in the examples above.
Then I’d recommend you try to create a function for getting the objects for one of the resources, both all of those objects, but also by searching/specifying. This function will probably include the required techniques for working with the different components of the API, building a header, authentication, query parameters etc. It would need input parameters for the terms or Id’s to search for.

In the SWAPI example it would look something like the following:

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

functionGet-SWAPIObjects{

param(

$Id,

$Query

)

$baseUrl="https://swapi.co/api/"

$Resource="people"

$Uri=$baseUrl+$Resource

if($Id){

$Uri+="/$Id"

}

elseif($Query){

$Uri+="?search=$Query"

}

$response=Invoke-RestMethod-MethodGET-Uri$Uri

$response

}

If you’re happy with the output (concentrate first on getting the correct results and not how the actual results are outputted at this point), try to use a different resource and see if you get the expected output with the same function.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

functionGet-SWAPIObjects{

param(

$Id,

$Query

)

$baseUrl="https://swapi.co/api/"

$Resource="films"

$Uri=$baseUrl+$Resource

if($Id){

$Uri+="/$Id"

}

elseif($Query){

$Uri+="?search=$Query"

}

$response=Invoke-RestMethod-MethodGET-Uri$Uri

$response

}

Notice that the only thing changed in the function is the $Resource variable

So, we can see that we easily can reuse the same code and structure for multiple resources in this API. Now let’s take a look at the actual output.

We saw that if the call was made with specifying the ID of the object we got that object directly. In the other calls we got a different output which had the actual objects inside the “results” property.

Let’s try to change our function slightly so that we can output the objects directly each time

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

functionGet-SWAPIObjects{

param(

$Id,

$Query

)

$baseUrl="https://swapi.co/api/"

$Resource="people"

$Uri=$baseUrl+$Resource

if($Id){

$Uri+="/$Id"

}

elseif($Query){

$Uri+="?search=$Query"

}

$response=Invoke-RestMethod-MethodGET-Uri$Uri

if($response.results){

$response.results

}

else{

$response

}

}

Now we get the actual objects outputted in both scenarios.

Note that this might differ from other APIs which is why you need to get to know the API by exploring it when you build your module.

After confirming that the same function works for our other resources it’s time to finish our SWAPI module.

The special Invoke function

The key to this module will be the Invoke-SWAPIRequest function which is actually the function we’ve seen already. We’ll rename the Get-SWAPIObjects to Invoke-SWAPIRequest. Then we’ll create one Get-SWAPIResource function for each of the available resource endpoints in the API.

We will also do a couple of other changes to accomodate a few other things.

The objects will have some properties containing a full URI to a different object. For this we will add an URI parameter to the Invoke function so that we can call that directly. I’ve also added this to it’s own Parameterset while Resource and Query is in a different one so that the enduser only should be able to use either URI or Resource/query.
Furthermore the API returns paged results. While you might want this, I will page over all results to be able to output all results for a resource if no query or ID parameter is given.

The full Invoke-SWAPIRequest will also include a synopsis, but I’ve removed this for brevity.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

functionInvoke-SWApiRequest{

[CmdletBinding()]

param(

[parameter(ParameterSetName="Res")]

$Resource,

[parameter(ParameterSetName="Res")]

$Query,

[parameter(ParameterSetName="Uri")]

$Uri,

$Method="GET"

)

$baseUrl="https://swapi.co/api/"

$output=@()

if($Uri){

Write-Verbose$uri

$output=Invoke-RestMethod-Method$Method-Uri$uri

}

else{

if($Query){

$query="?search="+$query

}

$uri=$baseUrl+$Resource+"/"+$Query

$response=Invoke-RestMethod-Method$Method-Uri$Uri

if($response.next){

while($response.next){

Write-Verbose$response.results.count

$output+=$response.results

$response=Invoke-RestMethod-Method$Method-Uri$response.next

if(!$response.next){

$output+=$response.results

}

}

}

else{

$output=$response

}

}

if($output.results-and$output-is[pscustomobject]){

$output.results

}

else{

$output

}

}

The resource functions will all look like this with the only difference in the $resource variable

All functions will be put in the same PSM1 file and to finish of the module you’ll create a module manifest (PSD1 file). This is the descriptor file for the module containing stuff like information about the Author, other required modules, license etc. You could create this from scratch, but there is a nice New-Modulemanifest cmdlet which can do it for you based on the input parameters you give it.

Both the PSM1 and PSD1 file should be put in the same folder and the folder name should match the name of the module. Then you can use the Import-Module cmdlet and point it to the path of the folder and you’ll have the cmdlets available.

Dealing with Authentication, Headers etc

As fun as it is playing around with the Star Wars API there isn’t much happening in terms of what you’ll need to do to get the API calls to work. That’s why we searched for an API that needed authentication and who also could be mapped to a common workflow, namely creating support tickets. We found the online ticketing system, SupportBee.

The SupportBee API has a similar structure as SWAPI. And after exploring it like we did with the SWAPI we’ll continue with our special support function and we’re naming it Invoke-SBAPIRequest.

We can see from the documentation that all API request needs to include a Header with the Content-Type and the Accept options set.
In addition all request besides the Create ticket request requires Authentication. This API uses Token Authentication and the token should be sent with the HTTP request in the auth_token parameter.

All of this goes in to the Invoke function so that we’ll only need to write that code once and all the other functions of the module will use that Invoke function to do the actual API calls.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

functionInvoke-SBApiRequest{

[cmdletbinding()]

param(

[parameter(Mandatory=$true)]

$SBCompany,

[parameter(Mandatory=$true)]

$Resource,

$Query,

$Content,

$Method="GET",

$AuthToken

)

$baseUrl="https://$SBCompany.supportbee.com/"

if(!$AuthToken-and$Resource-ne"tickets"-and$Method-ne"POST"){

Write-Error"Authentication token is mandatory for this request!"

}

$headers=@{

"Content-Type"='application/json';

"Accept"='application/json';

}

if($AuthToken){

if($query){

$Q="&"+$Query

}

$uri=$baseUrl+$Resource+"?auth_token=$AuthToken"+$Q

}

else{

$uri=$baseUrl+$Resource

}

if($Method-eq"POST"){

$body=$Content

}

Invoke-RestMethod-Method$Method-Uri$uri-Headers$headers-Body$body

}

In this Invoke function we’ll just return the result of the API call directly since the output object differs in what the “result” property will be named. Therefore we’ll specify the output further in each of the functions.

What we’ll see is that the company name and the auth token needs to be passed to the Invoke function every time. This is because we need to include the company name in the URI and because the token needs to be passed in the URI each time. This follows the REST principles as they state that a RESTFUL API should be stateless, at least from the server’s perspective.

You could use a couple of techniques to skip passing this every time. The company name could easily be hard coded if you only have one company to work with. The token is created by a specific user and could differ in your scenario. Sadly there is no API endpoint for retrieving the token, but you could store it as a global Powershell variable for the user, or you could add it as an environmental variable. For our purposes we’ll add it to each function call.

By using this function your users would be able to create tickets by using normal Powershell input parameters instead of struggle with creating a JSON payload and knowing what the syntax should be like, and which parameter names they have to use.

In this module we’ve chosen to mask the Invoke function as it doesn’t have much use for an end user

This could be done in the PSM1 file, but you could also do it in the definition file by specifying which functions to export

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.

FunctionsToExport=@(

'Get-SBTicket',

'New-SBTicket',

'Set-SBTicket',

'Get-SBComment',

'New-SBComment',

'Get-SBReply',

'New-SBReply',

'Get-SBLabel',

'Add-SBLabel'

)

As this module was created with the focus of demoing how and why you should create a module as an API wrapper it doesn’t do much of error handling so if you are considering using it for a production purpose be sure to add that to your code.

Summary

As you might know or have understood already, many Powershell modules are “just” wrappers for a web API.

Hopefully this post has shown how you can use Powershell modules as a wrapper for an API so that you could make it easier to consume it, both for yourself and also for your end users who might not know how to deal with Authentication, Headers and ensuring the correct Body syntax.