Skip to main content

Developing PowerShell modules for REST APIs – Part2

AzureFunctionThis is part 2 of the REST API blogpost. In part1 we successfully setup two REST API endpoints using the UniversalDashboard PowerShell module. In this part we are going to create a simple module that support some CRUD operation against our API. As we are trying to keep things as simple as possible, we will not use any fancy framework (like Plaster) to build our module. We are also going to skip a very important step you should familiarize yourself with, Pester tests. Lets get to it.


The module

We will build a module called FilesAPI. The module folder will look like this:

image

In the functions folder I have already added the 2 helper functions from part 1, Get-AuthorizationHeader and ConvertTo-Base64. The other folders are just placeholders for important stuff like classes, private functions that you do not want to make available for the module consumer and tests for Pester tests. For such a small module that we are going to create, one could argue that it is much easier to just add the functions directly in the module definition file (psm1), however I strongly urge you to create a folder structure for your modules. It is a huge benefit to have each function, test and class in a separate file. If you use the folder structure, Visual Studio shines compared to ISE as your module grows. It will also make the transition to module frameworks like Plaster much easier.

Since we are using a single file for each function, this is what we will put in out module file:

$ModuleScript variable contains the script that will load each function file, class and private function in our folder structure. As we add functions to the folders, it will only export the functions in the functions folder. It is a generic template that I started to use quite recently. The inspiration came from Kieran Jacobsen and his Plaster template. Go ahead and execute the script to create the FilesApi.psm1 module file.


First API function – Get-FileApi

We are going to start with the first simple function and we will name it Get-FileApi. As you might recall from the first part, the API needs an Authorization header for all calls to our API, so we need that as an parameter for the function. It might also be a good idea to add a parameter for Name so we can filter on specific files. Below is my function:

Basically we are just wrapping Invoke-RestMethod supplying the URI, method and the header containing the Authorization key/hashtable. We target the Authorization parameter as mandatory and leave the Name parameter optional. Save this function in the functions directory and name the file Get-FileApi.ps1.


Second API function – Set-FileApi

Secondly we are now going to create a function for the POST method of the API. The endpoint accepts 3 parameters:
  • Authorization
  • FileName
  • Content
Our function needs to implement these parameters. Since this is a function uses the verb Set, we should be nice to the consumers of our module and implement WhatIf, more commonly know as SupportsShouldProcess. I really hate it when a destructive command do not implement the WhatIf parameter, here is looking at you Azure for PowerShell. Again we are marking the Authorization parameter as Mandatory for our function:

Pretty cool, with a few extra lines of code, our functions supports the WhatIf parameter and we have created 2 functions that talk to an API. Copy the function and save it in the functions folder as Set-FileApi.ps1.


Turning it up a notch

Now at this point, we have two working function, however this is not really how APIs work. UniversalDashboards helps us quite a bit since the objects returned from Invoke-RestMethod is converted to real PowerShell objects. Invoke-RestMethod usually returns a HtmlWebResponseObject which contains two important properties. Content and StatusCode. To make it a little more interesting, we will try and get as close as possible to a real API. We are going to update our endpoints to return an object containing 2 properties; Content and StatusCode. Content will contain a JSON string of the object previously returned from the API. StatusCode is an HTTP code indicating the status of our request, which we will set to 200 if authorization is okay. We are also going to add a Name header for our Endpoint to simulate server side filtering. After the modifications, the Endpoint definition looks like this:

Go ahead and register the new endpoint.

Running our Get-FileApi function now is resulting in this output:

image

We can see the statuscode and the Content property containing a JSON string which is what we are after. So we have to check the StatusCode to verify that the request was OK (200) and then we have to convert the content string to an object using ConvertFrom-Json. After the modifications we have the following:


Running the Get-FileApi function gives us a nice object and filtering by Name works to, even with wildcards:

image

Applying the same logic to our CreateFileEndpoint, we update the definition to this:

After registering the new endpoint definition and running the unmodified Set-FileApi function, we get the new respons from our API:

image

Updating the Set-FileApi function to convert the JSON string to an object, it should look something like this:

Trying the Set-FileApi function after updating, we again get a nice object ouput:

image


Final module work

If you have followed along until now, your functions directory should look like this:

image

Your root folder should just have the folders and a single file FilesApi.psm1 (we created that in the beginning):

image

Now we are going to create the FilesApi.psd1 or what is know as the manifest. Here is the script I will be using:

A couple of notes here. If you plan to run this script several times, like if you add more functions, best practice is to “keep” the GUID for the module. If you run this script, the manifest will have a new GUID each time. Also if you are checking the manifest into source control, it is always nice that the manifest is encoded in UTF8. The New-ModuleManifest encodes it in ASCII format, which you probably do not want. In the last two lines I am converting the module to UTF8. After you have created the manifest, you may go ahead and start a new PowerShell session and import the module:

image

Lets take it for a spin, first we need to create an authorization variable:


image

After getting an authorization object, we go ahead and run Get-FileApi with the parameter:

image

Well, I’ll be darned, it works. Who would have thought.


What is missing?

A couple of things really. 

Help
As a best practice you should add help to your functions.

Tests
Test Driven Development, TDD for short, is something you might want to look into. In essence you write your unit tests before you create the function. As I have mentioned previously, you can use Pester for this. It it built-in and included with PowerShell as a module.

Authorization
Different APIs have a couple of ways of adding authentication to the requests. I have only shown one method. 

URL query parameters

I chose to not include it in this guide. If you come a cross such an API like I have, I strongly suggest you add an function that can build the url for you. If the interest it there, I can show the Join-Url function I wrote which will build an URL for you with childpaths and URL query parameters.

BaseURL problem for production/test
In our example, we have hard coded the URL in the functions. It is quite common to have a test/dev environment for the API and a production environment. This requires that your module needs to support 2 different base URL. You can solve this quite easily by adding a module-level variable in the psm1 file ([bool]$script:IsProduction = $false). Then you add 2 new functions to the module, Get-ApiBaseURL and Set-ApiEnvionment. Get-ApiBaseURL contains just a if-block and will return the correct URL for the environment you want to work with. The Set-ApiEnvironment will flip the module level boolean variable to match your environment. You might also want to add a Get-ApiEnvironment function that will return the value of the $Script:IsProduction variable. 

Key takeaway

The goal of creating this guide is twofold: Firstly I wanted you to become aware of the awesome module UniversalDashboard and how easy it is to create an REST API using only PowerShell. Secondly I wanted to show it is quite trivial to create a module that consumes an REST API. Hope you have enjoyed reading or doing the tutorial as much as I did.

You can find part 1 of this tutorial here.

Cheers

Tore

Comments

  1. REST APIs are the way of the future! Great seeing this all done in PowerShell! Thanks!

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete

Post a Comment

Popular posts from this blog

Serialize data with PowerShell

Currently I am working on a big new module. In this module, I need to persist data to disk and reprocess them at some point even if the module/PowerShell session was closed. I needed to serialize objects and save them to disk. It needed to be very efficient to be able to support a high volume of objects. Hence I decided to turn this serializer into a module called HashData. Other Serializing methods In PowerShell we have several possibilities to serialize objects. There are two cmdlets you can use which are built in: Export-CliXml ConvertTo-JSON Both are excellent options if you do not care about the size of the file. In my case I needed something lean and mean in terms of the size on disk for the serialized object. Lets do some tests to compare the different types: (Hashdata.Object.ps1) You might be curious why I do not use the Export-CliXML cmdlet and just use the [System.Management.Automation.PSSerializer]::Serialize static method. The static method will generate t

Toying with audio in powershell

Controlling mute/unmute and the volume on you computer with powershell. Add-Type -TypeDefinition @' using System.Runtime.InteropServices; [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioEndpointVolume { // f(), g(), ... are unused COM method slots. Define these if you care int f(); int g(); int h(); int i(); int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext); int j(); int GetMasterVolumeLevelScalar(out float pfLevel); int k(); int l(); int m(); int n(); int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext); int GetMute(out bool pbMute); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev); } [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), Inte

Creating Menus in Powershell

I have created another Powershell module. This time it is about Console Menus you can use to ease the usage for members of your oranization. It is available on GitHub and published to the PowershellGallery . It is called cliMenu. Puppies This is a Controller module. It uses Write-Host to create a Menu in the console. Some of you may recall that using Write-Host is bad practice. Controller scripts and modules are the exception to this rule. In addition with WMF5 Write-Host writes to the Information stream in Powershell, so it really does not matter anymore. Design goal I have seen to many crappy menus that is a mixture of controller script and business logic. It is in essence a wild west out there, hence my ultimate goal is to create something that makes it as easy as possible to create a menu and change the way it looks. Make it easy to build Menus and change them Make it as "declarative" as possible Menus The module supports multiple Men