Grappling with Items Returned by Cmdlets
I find writing a PowerShell script to be a peaceful activity. It focuses the mind into a gainful activity. Until something goes wrong, that is. Take the example of the script to create a report of managers and their direct reports in a tenant. Simple, I thought. Shouldn’t take longer than an hour, even to make it pretty. Then I lost myself in examining why the reported number of direct reports for a manager showed up as a blank even when I knew that the manager had a direct report. It’s amazing how deep down the rabbit hole the mind will take you if allowed.
I solved the problem by typing the variable used to accept the data returned by the cmdlet to find a manager’s direct report. The problem then is to understand why the issue exists and if it is related to a single cmdlet or all cmdlets. As it turns out, the answer is more complicated than I first thought.
Finding Direct Reports
Take this example. We have details of a manager’s Azure AD account stored in a variable.
$Manager | Ft DisplayName, ExternalDirectoryObjectId DisplayName ExternalDirectoryObjectId ----------- ------------------------- Oisin Johnston c6133be4-71d4-47c4-b109-e37c0c93f8d3 $Dn = $Manager.DistinguishedName
We now call the Get-User cmdlet to find the manager’s direct reports. The output goes to an untyped variable called $R.
$R = Get-User -Filter "Manager -eq '$Dn'" $R.Count
At first glance, the call doesn’t return any data and the $R variable doesn’t report a count property. However, I think some data should be there. If I type the variable to make it an array and try again, PowerShell returns the Count property as expected.
[array]$R = Get-User -Filter "Manager -eq '$Dn'" $R = Get-User -Filter "Manager -eq '$Dn'" $R.count 1 $R | ft DisplayName DisplayName ----------- Brian Weakliam (Operations)
Apparently, when PowerShell returns a single item, it unpacks the item and returns an object instead of an array. Using the GetType() method to examine the object, you’ll see that it’s a System.Object rather than a System.Array.
$R.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True PSObject System.Object
Forcing the variable to be an array means that PowerShell doesn’t unpack the single item to become an object. Because the $R variable continues to be an array, the count property is available. According to Microsoft documentation, arrays with one or zero items have count properties from PowerShell 3.0 on.
The same result happens with other Exchange Online cmdlets like Get-ExoMailbox and Get-UnifiedGroup. Here’s an example with Get-ExoMailbox:
$X = Get-ExoMailbox -Identity John.Smith $X.Count $X.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True PSObject System.Object
Exceptions with Azure AD Cmdlets
Great. We think we understand what’s happening and it seems like an excellent idea to type variables used to receive returns from a cmdlet. However, if I repeat the operation to return the same data as found with Get-User above using the Get-AzureADUserDirectReport cmdlet to an untyped variable, PowerShell reports the correct count!
$Q = Get-AzureADUserDirectReport -ObjectId c6133be4-71d4-47c4-b109-e37c0c93f8d3 $Q.count 1 $Q | Ft DisplayName DisplayName ----------- Brian Weakliam (Operations)
Examining the variable to see its type, we discover that the Azure AD cmdlet returns a directory object.
$Q.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- ------- True False User Microsoft.Open.AzureAD.Model.DirectoryObject
The same is true for other Azure AD cmdlets like Get-AzureADUser and Get-AzureADGroup.
Apart from generating an array instead of a directory object, typing the output variable as an array has no effect on the Azure AD cmdlets. They work as expected.
Typing is Goodness
It would be nice if all the PowerShell modules used a across Microsoft 365 apps were consistent, but as proven by the team template management cmdlets included in the Microsoft Teams V2.0 module, that’s not always the case. Testing should reveal issues like odd numbers reported for returned items, but who tests everything, especially these days? This old dog learned a new trick and now I type all variables used for returned data. Just in case.