Part 4 – Coding the PowerShell Module

8 Aug

This is part four of a multi-part post. Here are links to all the posts in this series.

Developing an Azure Service Bus PowerShell Module – Part 1/

Part 2 – Developing the First Test Using Pester

Part 3 – More Tests, this time with Pester’s Mock

Part 4 – Coding the PowerShell Module

Introduction

In my last post, I developed the Pester unit tests for the New-QueueMessage and Read-QueueMessage cmdlets I am developing for an Azure Service Bus PowerShell module. In this post, I will illustrate developing each of the functions.

New-SasToken

Figuring out how to generate the SAS token took some digging. Fortunately, there are code samples on MSDN (linked below) that illustrate how to correctly compute the SHA hash required for the token. The token is made of four parts:

  • the name of the Azure Service Bus namespace
  • the SHA 256 hash,
  • the expiration time (which is the number of seconds since the beginning of the epoch starting at midnight January 1, 1970 UTC)
  • the SAS Policy name

The SHA 256 hash is the hash of another string, which includes:

  • the name of the Azure Service Bus namespace
  • a newline character
  • the expiration time

First, we need to compute the token expiration time. The code for that looks like this.

$origin = [DateTime]"1/1/1970 00:00"
$diff = New-TimeSpan -Start $origin -End $Expiry
$tokenExpirationTime = [Convert]::ToInt32($diff.TotalSeconds)

Then I create the string that will be hashed.

$stringToSign = [Web.HttpUtility]::UrlEncode($Namespace) + "`n" + $tokenExpirationTime

The next step is to new up an instance of an HMACSHA256 class, which will do the work of computing the hash. The Key property of the HMACSHA256 is set to a byte array that contains the SAS Policy key from the Azure portal.

Here’s the code to new-up the HMACSHA256 class. Again, note that you don’t set the key property to the key from the Azure portal, it’s set to a byte array created from the key.

$hmacsha = New-Object -TypeName System.Security.Cryptography.HMACSHA256 
$hmacsha.Key = [Text.Encoding]::UTF8.GetBytes($Key)

Next the hash is computed with the HMACSHA256 class instance. The hash is converted to a base 64 string. That is what is used in the token.

$hash = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
$signature = [Convert]::ToBase64String($hash)

The last step is to create the token. The token is a formatted string that looks like this.

Here’s the code to create the token. Note the grave character at the end of each line.

$token = [string]::Format([Globalization.CultureInfo]::InvariantCulture, `
	"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", `
	[Web.HttpUtility]::UrlEncode($Namespace), `
	[Web.HttpUtility]::UrlEncode($signature), `
	$tokenExpirationTime, `
	$PolicyName)

Now, when I run the Pester unit test, I get the following.

Note that the last test no longer throws the NotImplementedException. Now that test passes. This is a good time to commit my changes to source control.

New-QueueMessage

One of the parameters of New-QueueMessage is a PSCustomObject that should contain a property named Body. Any other properties on that object will be assigned to the BrokerProperties header parameter of the web request. The first thing the function needs to do is break that object apart.

$body = $Message.Body
$Message.psobject.properties.Remove("Body")

Next is to set up the parameters for the Invoke-WebRequest cmdlet.

$uri = "https://$Namespace.servicebus.windows.net/$QueueName/messages"
$token = New-SasToken -Namespace $Namespace -Policy $PolicyName -Key $Key 
$headers = @{ "Authorization"="$token"; "Content-Type"="application/atom+xml;type=entry;charset=utf-8" }
$headers.Add("BrokerProperties", $(ConvertTo-Json -InputObject $Message -Compress))

Finally, the Invoke-WebRequest call. The normal output of the command is redirected to the $null automatic variable. If an error occurs, Invoke-WebRequest will output the error to the error stream stderr.

Invoke-WebRequest -Uri $uri -Headers $headers -Method Post -Body $body > $null

When I run my Pester tests, I get the following.

Two of my unit tests are passing now. This is a good time to commit my changes to source control.

Read-QueueMessage

Read-QueueMessage is a lot like New-QueueMessage. Make the call to the Service Bus REST API endpoint and use the results to construct a PSCustomObject that contains the BrokerProperties and the body of the response. I’ll start by constructing the parameters required for the Invoke-WebRequest cmdlet.

$uri = "https://$Namespace.servicebus.windows.net/$QueueName/messages/head"
$token = New-SasToken -Namespace $Namespace -Policy $PolicyName -Key $Key 
$headers = @{ "Authorization"="$token" }

Next make the call to the Service Bus REST API.

$response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Delete

Finally, construct the PSCustomObject that contains the brokered message properties and the body of the message.

$brokeredMessage = ConvertFrom-Json -InputObject $response.Headers.BrokerProperties 
Add-Member -InputObject $brokeredMessage `
	-MemberType NoteProperty `
	-Name "Body" `
	-Value $response.Content

When I run my Pester tests, I get the following output.

All tests are passing. This is a good time to commit my changes.

In this post, I walked through the process of developing the three functions that make up my module. The module isn’t done yet, it doesn’t support all the envisioned scenarios yet. For example, I want to support the Service Bus Peek operation and retrieving messages from the dead letter queue in the Read-MessageQueue cmdlet. Comment based help is also needed so users can use the Get-Help cmdlet to see information about the cmdlets in the module. However, describing that process would be redundant and this series of posts has become long enough.

These posts have described using Visual Studio 2017 and the PowerShell Tools for Visual Studio 2017 plug-in to develop a simple but useful PowerShell module. The module can send and receive messages to Azure Service Bus. It has also described using Git for source control and the Pester unit test framework to support the development best-practice Test-Driven Development. My goal has been to show these technologies working together to create something useful.

Resources:

Service Bus authentication with Shared Access Signatures

Service Bus HTTP Client

Shared Access Signature authentication with Service Bus

Receive and Delete Message (Destructive Read)

Send Message (to an Azure Service Bus Queue)

Mike Williams

Software developer working with public cloud and hybrid cloud technologies since 2014 and System Center products since 2009 with heavy emphasis on system integration and enterprise cloud solutions.