This 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.
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.
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.
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.
Go ahead and register the new endpoint.
Running our Get-FileApi function now is resulting in this output:
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:
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:
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:
Your root folder should just have the folders and a single file FilesApi.psm1 (we created that in the beginning):
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:
Lets take it for a spin, first we need to create an authorization variable:
After getting an authorization object, we go ahead and run Get-FileApi with the parameter:
Well, I’ll be darned, it works. Who would have thought.
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.
The module
We will build a module called FilesAPI. The module folder will look like this: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
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:
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:
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:
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:
Final module work
If you have followed along until now, your functions directory should look like this:Your root folder should just have the folders and a single file FilesApi.psm1 (we created that in the beginning):
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:
Lets take it for a spin, first we need to create an authorization variable:
After getting an authorization object, we go ahead and run Get-FileApi with the parameter:
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
You can find part 1 of this tutorial here.
Cheers
Tore
REST APIs are the way of the future! Great seeing this all done in PowerShell! Thanks!
ReplyDeleteThanks, always nice to hear :-). Take care!
DeleteThis comment has been removed by a blog administrator.
ReplyDelete