Creating a generic PowerShell paging function
In my last post (creating a report of sent and received mails for Exchange Online mailboxes) I ran into a challenge with the way certain cmdlets handled paging of data. If you haven't read that I'd recommend familiarising yourself then popping back. To mitigate this I wrote a custom function to handle the paging of the "Get-MessageTrace" command. It worked effectively but if instead we have a different cmdlet the function can't handle it. This post addresses that point by modifying the function to be generic so that it can be applied to different cmdlets that support paging.
There are two examples cmdlets that come to mind however I am hoping there are more otherwise this won't be useful other than as an exercise. I'll be using these two example cmdlets for testing which are both from the Exchange Online PowerShell module:
As a quick re-cap, paging allows us to cater for large amounts of data without overwhelming the server. This is done by splitting results into separate pages with a maximum number of results returned on each page. Its a concept especially pertinent for SaaS services where an intensive query could affect other customers. In this case paging allows the service provider to set a limit of results and handle the queries in smaller chunks to manage the load. Let's dive in.
An explanation of the logic for the core paging function works can be found here in my original post.
Making the function generic
There is no standard when it comes to how a Cmdlet should implement paging therefore the function needs to handle different parameters. These can be split into two categories:
1) Items we need to know explicitly or the process will fail:
Name of the Cmdlet we are calling
Page size parameter name and value
Page number parameter name
While not all of the above arguments are mandatory, if they are used we need to be able to identify them so we can apply the correct operation, such as incrementing the page by one. Different cmdlets may use different terminology to define these variables. Taking page size as an example this could be "-pagesize 100" in one cmdlet but in another it could be "-maxresults 100" by requiring that these parameters are identified we can ensure they are appropriately handled.
2) Items that need to deal with ambiguity
Each cmdlet can have many other parameters a user may want to use that are not relating to paging. We don't want to constrain our function so must have a way to deal with any additional arguments. To cater for this we're going to "splat" the parameters into a hashtable which the function will accept and process. Splatting is an alternative solution to passing in individual parameters and works well if we have a lot of parameters/values which can make cmdlet calls long and unwieldly. As with any hashtable, we have a unique set of keys and a corresponding value for each. In this case our keys represent the cmdlet parameter names e.g. "SenderAddress" with the values (somewhat unsurprisingly) being the value e.g. "aRandomEmailAddress@domain.com".
It can be initialised with values:
Or we can initialise empty and add, change and remove values as we go:
When the splatted parameters are supplied to a cmdlet, PowerShell treats it as though we've written them out longhand (note that we must prefix our splatted variable with an "@").
Equally if we have multiple invocations to make, perhaps altering certain parameters in between, splatting saves us from continually repeating parameters and values in the code, instead we just update the key/value in the hashtable and pass it in again. It is worth noting that a downside of splatting is that your code can become less readable so bear that in mind and use when appropriate.
Using a variable as a cmdlet call
This falls into the "simple once you know it" camp but I think it's worth stating all the same...
The ampersand (&) is the "call" operator within PowerShell, this allows us to treat the contents of a variable as a command to execute. Here's an example to illustrate how it works.
First I'll store the command that I want to run in a variable, for simplicities sake I'll use the "Get-ChildItem" cmdlet which we store in the "functionToCall" variable as shown below.
Next I try to call the cmdlet by using the variable name alone which results in the text "Get-ChildItem" being printed; this makes sense after checking the type of our variable - it's a String, therefore PowerShell is outputting the contents of our string to screen.
Now if instead we put the ampersand in front of our variable, voila! We are now executing the contents of our variable and calling "Get-ChildItem" returning the contents of the working directory.
Within the function this is used to take the functionName value passed in by the user and execute it.
Adding some help text
Writing help text is a bit of an art, I can't say I'm great at it but it is valuable to help your end user out by providing information on how to use your function. PowerShell has the "Get-Help" cmdlet which can be used to pull help information for any cmdlet. The information returned will vary, some information is autogenerated e.g. which parameters are mandatory, whereas others such as full examples need to be specified by the author or will be blank.
In my case I'm specifying three types of help:
Synopsis - general summary of the function
Parameter guidance - what each of the parameters mean with an example value
A full example calling the function
With any luck that's enough for someone to be able to use the function effectively.
Any help text must be specified within a comment at the top of your function, the specific keywords e.g. ".PARAMETER <Parameter-Name>" tell PowerShell to treat them as the various help values rather than a normal comment. For completeness, all of the possible help items you can specify along with descriptions of what they mean can be found here. Below is a cut down sample from the full script showing how this looks in practice:
Also worth noting is the amount of help text returned by "Get-Help" varies so to view everything use "Get-Help -full iteratePages".
The Full Code
Adding in some error handling and we have the following code:
Now to call the function with a test case using "Get-QuarantineMessage" which our old function wouldn't have been able to handle.
Piping the results into grid view we can check everything is present and correct. Now all that is left to do is to check out my exclusive access to Wimbledon 2022, which I will, of course, be attending in a personalised shirt.