How to create a PowerShell Module to Interact with a REST API

Having knowledge with using REST APIs is becoming increasingly more important for us IT professionals when it comes to automating their tasks and infrastructure. Since many IT pros use PowerShell on a daily basis, it only makes sense to use the popular scripting language to work with REST APIs.

In this tutorial, you’ll learn how you can create a PowerShell module to interact with a REST API using the swagger petstore API as an example. You’ll also pick up some tips and tricks along the way to make the code work as efficiently as possible.

Requirements

This article will be a tutorial. If you intend to follow along, be sure you have Windows PowerShell v5.1 on hand. All example code was tested on 5.1 but the code may work on v6 or later on Mac or Linux also.

Creating the Module Folder and Files

Before we can get into the actual functional purpose of this module, you must create the module scaffolding itself.

There are a lot of ways to create a module with different structures. To make things easy, you’ll use the simplest type that consists of two files and a folder with the same name:

  • One PowerShell manifest file (.psd1) that contains information about our module.
  • One PowerShell module file (.psm1) file that contains all of our PowerShell cmdlets.
  • One folder containing the two files above.

Start by creating the folder that will contain the psm1 and psd1 files. In this example, create folder at *C:for the module using the command below.

 New-Item -Type Directory -Path C:\Source\PetStore\ 

Creating the PowerShell Module Manifest (PSD1)

Next up, create the PowerShell module manifest. A module manifest is a PowerShell data file (.psd1) that contains metadata about our module. You can use this file to specifiy all kinds of information and requirements for your module.

While you can create a manifest by hand, it’s a lot easier to use the built-in PowerShell cmdlet called New-ModuleManifest.

The code below will create a PowerShell module manifest with the New-ModuleManifest command. Feel free to edit the code to fit your specific application. This code will create the PowerShell module manifest Petstore.psd1 in the *C:folder.

 # Use splatting technique to make it more readable
 $ModuleManifestParams = @{
     Path            = "C:\Source\PetStore\PetStore.psd1" # Notice that the psd1 file has the same name as the folder it resides in
     Guid            = [GUID]::NewGuid().Guid # A unique GUID for the module for identification
     Author          = "Your Name Here" # Optional
     CompanyName     = "Company Name here" # Optional
     ModuleVersion   = "0.0.1" # Semantic versioning as recommended best practice for PowerShell modules
     Description     = "A PowerShell module to interact with the HttpBin API" # A short description of what the module does
 }
 
 # Run New-ModuleManifest with the splatted parameters
 New-ModuleManifest @ModuleManifestParams 

Creating the PowerShell Module (PSM1)

The .psm1 file is the file that will hold all module functions. Creating the PowerShell module file (.psm1) is just as simple as using PowerShell editor of our choice to create a new, blank text file at the C:.psm1 path.

Adding the Base URI the the Module

While not required or always possible, it’s a good practice to add the base URI for the API endpoints into the module. Setting this URL as a variable in the PSM1 file reduces the amount of code needed and so that we can quickly adapt between different versions.

 $script:PetStoreBaseUri = "https://petstore.swagger.io/v2"
 $script:PetstoreInvokeParams = @{
     ContentType = "application/json"
 } 

Once this variable has been set, you can now use it in any function when using Invoke-RestMethod as shown below.

PS51> Invoke-RestMethod -Uri "$script:PetStoreBaseUri/pet/<petid>" @script:PetstoreInvokeParams 

Writing the First Function

The first function that we will use is using the https://petstore.swagger.io/v2/pet/ endpoint to get data about our pets. This function will be called Get-PetstorePet as shown below.

 Function Get-PetstorePet {
     [cmdletbinding()]
     param(
         [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
         [int64]$PetID
     )
     Begin {
         ## Using the module-scoped API endpoint URI to create a function-specific URL to query
         $Uri = "$script:PetStoreBaseUri/pet/{petId}"
     }
     Process {
         # Create a temporary variable from the Uri variable contain int the PetId parameter
         $TempUri = $Uri -replace "\{petId\}", $PetId
             
         # Call the API endpoint using the $TempUri
         Invoke-RestMethod -Uri $TempUri -Method Get @script:PetStoreInvokeParams
     }
 } 

The parameter PetId is defined to match the specific REST API parameter for this endpoint. For a full list of parameters for the example endpoint, check them out here. By specifying ValueFromPipeline and ValueFromPipelineByPropertyName above, you will be able to supply parameters to this function from the pipeline like below.

 # Define pet id's to fetch
 $PetIds = @(100,101,102,103)
 $PetIds | Get-PetstorePet 

Importing the Module

Now that you’ve created the first function in the module, it’s time to start using it. Import the module using the Import-Module command as shown below.

PS51> Import-Module C:\Source\Petstore

Once imported, you can then run the function just created by running Get-PetstorePet.

PS51> Get-PetstorePet -PetId 123

Adding Pets

In the documentation of the API, you can see that to create a pet, you have to supply a body in JSON format. And since it will create a pet we will use the function name Add-PetstorePet.

But we don’t want to mess around with pure text since PowerShell is an object-oriented scripting language. What we’ll do instead is that we’ll create a hashtable like this that we’ll convert to JSON format:

$BodyJson = @{
     id        = $PetId
     category  = @{
         id   = $CategoryId
         name = $CategoryName
     }
     name      = $PetName
     photoUrls = $PhotoUrls
     tags      = $Tags
     status    = $Status
 } | ConvertTo-Json 

This is a much easier approach that trying to create the JSON directly and will save us lot’s of code and time.

We’ll use this as a base for the paramters that we need for our command:

param(
             # Id of the pet that you'll create
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
               [Alias("Id")] # This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module
         [int64]$PetId,
 
             # Category ID of the pet to create
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [int64]$CategoryId,
 
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string]$CategoryName,
 
             # Name of the pet
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string]$PetName,
 
             # Urls to photos of the pet
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string[]]$PhotoUrls,
 
             # Objects with tags containing Id and Name properties
         [Parameter(ValueFromPipelineByPropertyName)]
         [PSCustomObject[]]$Tags,
 
             # Status of the pet "Available","Taken" etc.
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string]$Status
 
     ) 

And since the Begin block is the same as in the previous command we’ll just reuse it with a slightly different URL since the PetId parameter is no longer needed:

 Begin {
     $Uri = "$script:PetStoreBaseUri/pet"
 } 

Then define the Process block that will create the JSON and POST it to the REST endpoint using Invoke-RestMethod.

Process {
     # Create the JSON that we'll post to the endpoint
     $BodyObject = @{
         id        = $PetId
         category  = $Category
         name      = $PetName
         photoUrls = $PhotoUrls
         tags      = $Tags
         status    = $Status
     }
                 
     # This will remove properties with a null value since it may mess around with some API's
     $BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach {
         $BodyObject.psobject.properties.Remove($_.Name)
     }
 
     $BodyJson = $BodyObject | ConvertTo-Json
 
     # Call the API endpoint using the $TempUri
     Invoke-RestMethod -Uri $Script:Uri -Method POST -Body $BodyJson @PetStoreInvokeParams
 } 

Notice how the null valued properties were removed from $BodyObject. This was done because some REST APIs don’t like being feed null values.

Once the function has been built, it should like like below.

Function Add-PetstorePet {
     [cmdletbinding()]
     param(
             # Id of the pet that you'll create
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [Alias("Id")] # This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module
         [int64]$PetId,
 
             # hash containing Id and name of category
         [Parameter(ValueFromPipelineByPropertyName)]
         [hashtable]$Category,
 
             # Name of the pet
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string]$PetName,
 
             # Urls to photos of the pet
         [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
         [string[]]$PhotoUrls,
 
             # Objects with tags containing Id and Name properties
         [Parameter(ValueFromPipelineByPropertyName)]
         [PSCustomObject[]]$Tags,
 
             # Status of the pet "Available","Taken" etc.
         [Parameter(ValueFromPipelineByPropertyName)]
         [string]$Status
 
     )
     Begin {
         $Uri = "$script:PetStoreBaseUri/pet"
     }
     Process {
         # Create the JSON that we'll post to the endpoint
         $BodyObject = @{
             id        = $PetId
             category  = $Category
             name      = $PetName
             photoUrls = $PhotoUrls
             tags      = $Tags
             status    = $Status
         }
 
         $BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach {
             $BodyObject.psobject.properties.Remove($_.Name)
         }
 
         $BodyJson = $BodyObject | ConvertTo-Json
 
         # Call the API endpoint using the $TempUri
         Invoke-RestMethod -Uri $Uri -Method POST -Body $BodyJson @script:PetStoreInvokeParams
 
     }
 } 

You can now try to run the function by re-importing the module and running the function as shown below.

 PS51> Import-Module C:\Source\Petstore -Force
 PS51> Add-PetstorePet -PetName Fido -PetId 1 -PhotoUrls "http://somesite.com/picture.jpg"
 PS51> Get-PetstorePet -PetId 1 

Adding More Functions

Now that you have a template created for functions, you can now use this rough template for other methods associated with the REST API.

For example, if you copy the Add-PetstorePet function created earlier, you can now easily create a Remove-PetstorePet function by changing the name of the function and the method it runs Invoke-RestMethod with.

Function Remove-PetstorePet {
     [cmdletbinding()]
     param(
         [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
         [Alias("Id")]
         [int64]$PetId
     )
     Begin {
         $Uri = "$script:PetStoreBaseUri/pet/{petId}"
     }
     Process {
         # Create a temporary variable from the Uri variable containing the PetId paramter
         $TempUri = $Uri -replace "\{petId\}", $PetId
         
         # Call the API endpoint using the $TempUri
         Invoke-RestMethod -Uri $TempUri -Method DELETE @script:PetStoreInvokeParams # NOTICE how we replaced GET with DELETE
 
     }
 } 

Since parameters were set up to accept pipeline input, you can also pipe “pets” directly to the Remove-PetstorePet function too as shown below.

PS51> Get-PetstorePet -PetId 1 | Remove-PetstorePet

Summary

Creating a PowerShell module for REST APIs is all about making it reusable. Since many REST APIs uses swagger styled API’s it’s easy to reuse the same module for multiple APIs. Adding pipeline support also makes executing the functions much more intuitive.

Write for Veeam

If you’d also like to share your expertise, knowledge and bring something new to the table on Veeam blog (and earn money by doing that), then learn more details and drop us a line at the program page. Let’s get techy!

Similar Blog Posts
Technical | October 10, 2024
Technical | August 9, 2024
Business | July 5, 2024
Stay up to date on the latest tips and news
By subscribing, you are agreeing to have your personal information managed in accordance with the terms of Veeam’s Privacy Policy
You're all set!
Watch your inbox for our weekly blog updates.
OK