All-in-one Get-User cmdlet: Part 6 – Formatting & Mailbox Statistics

It's pretty obvious that our Get-MITUser cmdlet isn't yet as visually usable as Microsoft's cmdlets. You can always modify the formatting of a cmdlet by piping it to a Format-* command. First of all this is inconvenient to have to do each time, but also the output of this isn't your original object but an entirely different formatting object. You can no longer operate on it as an object. For example try obtaining the DisplayName from an object that has been formatted - it won't work.

Object Formatting

It’s pretty obvious that our Get-MITUser cmdlet isn’t yet as visually usable as Microsoft’s cmdlets.

PS> Get-MsolUser -UserPrincipalName Vicki@milneitlab01.onmicrosoft.com

UserPrincipalName                  DisplayName isLicensed
-----------------                  ----------- ----------
Vicki@milneitlab01.onmicrosoft.com Vicki Marsh True      

PS> Get-Mailbox -Identity Vicki@milneitlab01.onmicrosoft.com

Name                      Alias                ServerName       ProhibitSendQuota
----                      -----                ----------       -----------------
Vicki                     Vicki                mm1p12301mb1609  99 GB (106,300,440,576 bytes)

PS> Get-MITUser -Identity Vicki@milneitlab01.onmicrosoft.com

DisplayName        : Vicki Marsh
UserPrincipalName  : Vicki@milneitlab01.onmicrosoft.com
PrimarySmtpAddress : Vicki@milneitlab01.onmicrosoft.com
ObjectId           : fe491646-d83c-4043-a6a5-d40b0a3075eb
MsolUser           : Microsoft.Online.Administration.User
MsolUserFound      : True
MsolUserError      : 
Mailbox            : Vicki Marsh
MailboxFound       : True
MailboxError       : 

PS>

You can always modify the formatting of a cmdlet by piping it to a Format-* command. First of all this is inconvenient to have to do each time, but also the output of this isn’t your original object but an entirely different formatting object. You can no longer operate on it as an object. For example try obtaining the DisplayName from an object that has been formatted – it won’t work.

PS> $UserFormatted = Get-MITUser -Identity Vicki@milneitlab01.onmicrosoft.com | Format-Table DisplayName,UserPrincipalName,PrimarySmtpAddress,MsolUserFound,MailboxFound 

PS> $UserFormatted

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound
----------- -----------------                  ------------------                 ------------- ------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com          True         True

PS> $UserFormatted.DisplayName

PS> $User = Get-MITUser -Identity Vicki@milneitlab01.onmicrosoft.com

PS> $User.DisplayName
Vicki Marsh

PS>

The proper way to handle this is to set a default format, which purely affects the display of the object on the console and does not affect how we can otherwise use the object. To achieve this we need to do the following:

  • Give our objects their own unique type names.
  • Create a format.ps1xml file that specifies the formatting for our object’s unique type names.
  • Create a PowerShell Module Manifest that specifically loads the formatting XML file.

You have a lot of freedom when choosing typenames, but it’s sensible to follow a hierarchical structure similar to .NET types. I am going with MilneIT.MITUser.User. I will use MilneIT for all of my custom PowerShell objects in all of my modules, MITUser for all objects relating to our custom user functions, and User specifically for the normal output of Get-MITUser.

First, we need to modify our code to have a typename for our custom object. This is easy enough; we just need to change the part where we define and output our custom object, from this:

    [pscustomobject]@{
        DisplayName        = $DisplayName
        UserPrincipalName  = $UserPrincipalName
        PrimarySmtpAddress = $PrimarySmtpAddress
        ObjectId           = $ObjectId
        MsolUser           = $MsolObj.MsolUser
        MsolUserFound      = $MsolObj.MsolUserFound
        MsolUserError      = $MsolObj.MsolUserError
        Mailbox            = $MailboxObj.Mailbox
        MailboxFound       = $MailboxObj.MailboxFound
        MailboxError       = $MailboxObj.MailboxError
    } #end [pscustomobject]

Into this:

    $obj = [pscustomobject]@{
        DisplayName        = $DisplayName
        UserPrincipalName  = $UserPrincipalName
        PrimarySmtpAddress = $PrimarySmtpAddress
        ObjectId           = $ObjectId
        MsolUser           = $MsolObj.MsolUser
        MsolUserFound      = $MsolObj.MsolUserFound
        MsolUserError      = $MsolObj.MsolUserError
        Mailbox            = $MailboxObj.Mailbox
        MailboxFound       = $MailboxObj.MailboxFound
        MailboxError       = $MailboxObj.MailboxError
    } #end [pscustomobject]

    $obj.PSObject.TypeNames.Insert(0,'MilneIT.MITUser.User')

    $obj

Now we need to create our format.ps1xml file. This should be created in the same folder as the .psm1 module.

The best way to understand how to create the XML file – the different XML tags and how they should be layered, etc. – is to look at the existing format.ps1xml files that Windows uses for modules that come with Windows. These can generally be found at C:\Windows\System32\WindowsPowerShell\v1.0\, including the Modules subfolder.

It is a bit daunting though, particularly as some formats are more complex than others. For example, the format for the output of Get-Process displays the Working Set as WS(K) in Kilobytes, but if you pipe Get-Process to Get-Member you will see that WS(K) isn’t a property of the Process object. If you look at the View for System.Diagnostics.Process in C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml, you will see that WS(K) is calculated from the WS property – it divides it by 1024 to convert bytes into kilobytes as it is formatted.

        <View>
            <Name>process</Name>
            <ViewSelectedBy>
                <TypeName>System.Diagnostics.Process</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableHeaders>
                    <TableColumnHeader>
                        <Label>Handles</Label>
                        <Width>7</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>NPM(K)</Label>
                        <Width>7</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>PM(K)</Label>
                        <Width>8</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>WS(K)</Label>
                        <Width>10</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>VM(M)</Label>
                        <Width>5</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>CPU(s)</Label>
                        <Width>8</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>6</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>3</Width>
                        <Alignment>right</Alignment>
                    </TableColumnHeader>
                    <TableColumnHeader />
                </TableHeaders>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>HandleCount</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <ScriptBlock>[long]($_.NPM / 1024)</ScriptBlock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <ScriptBlock>[long]($_.PM / 1024)</ScriptBlock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <ScriptBlock>[long]($_.WS / 1024)</ScriptBlock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <ScriptBlock>[long]($_.VM / 1048576)</ScriptBlock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <ScriptBlock>
if ($_.CPU -ne $()) 
{ 
    $_.CPU.ToString("N") 
}
				</ScriptBlock>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Id</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>SI</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>ProcessName</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                </TableRowEntries>
            </TableControl>
        </View>

We don’t need any complex functionality. The following file will format our sole custom object as a simple table:

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
    <ViewDefinitions>
        <View>
            <Name>Default</Name>
            <ViewSelectedBy>
                <TypeName>MilneIT.MITUser.User</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>DisplayName</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>UserPrincipalName</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>PrimarySmtpAddress</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>MsolUserFound</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>MailboxFound</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                </TableRowEntries>
            </TableControl>
        </View>
    </ViewDefinitions>
</Configuration>

Now we need to create our Module Manifest, again in the same folder as the .psm1 module and the .formats.ps1xml formats. Rather than create it from scratch, we can use the New-ModuleManifest cmdlet to create one from a template. We can either pass parameters to New-ModuleManifest, or we can edit the file it creates, or a combination of the two. In this case we specify everything on the command line. The important parts are the -RootModule (otherwise loading the module won’t load any code) and the -FormatsToProcess (otherwise the formats aren’t loaded and this whole exercise is pointless!)

PS> New-ModuleManifest -Path "C:\Users\Ryan\OneDrive\Scripts\Milne.IT\PowerShell\Modules\MilneIT\MilneIT.psd1" -RootModule "MilneIT.psm1" -FormatsToProcess 'MilneIT.format.ps1xml' -RequiredModules 'MSOnline' -CmdletsToExport '*' -Author 'Ryan Milne' -CompanyName 'Ryan Milne'

PS>

Now, unload and reload your module, and try it out. You will see that the output is now formatted as a table, with fewer properties displayed. But we can always work with the now-“hidden” properties exactly as normal. We can access them with Select-Object or $Variable.Property, we can filter with Where-Object, etc.

PS> Remove-Module MilneIT
PS> Import-Module MilneIT
PS> Get-MITUser Will@milneitlab01.onmicrosoft.com

DisplayName  UserPrincipalName                  PrimarySmtpAddress                MsolUserFound MailboxFound
-----------  -----------------                  ------------------                ------------- ------------
Will Cook    Will2@milneitlab01.onmicrosoft.com Will@milneitlab01.onmicrosoft.com True          True        
Will Richard Will@milneitlab01.onmicrosoft.com                                    True          False           

PS> Get-MITUser -Identity Roy@milneitlab01.onmicrosoft.com | Select-Object -ExpandProperty MsolUser

UserPrincipalName                DisplayName   isLicensed
-----------------                -----------   ----------
Roy@milneitlab01.onmicrosoft.com Roy Hitchcock True                                                             

PS> $Vicki = Get-MITUser -Identity Vicki
PS> $Vicki.Mailbox

Name                      Alias                ServerName       ProhibitSendQuota
----                      -----                ----------       -----------------
Vicki                     Vicki                mm1p12301mb1609  99 GB (106,300,440,576 bytes)

PS>

Mailbox Statistics

I said previously that it would be good to include Mailbox Statistics in the output as well. I mean, the intention of this cmdlet is that it’s an all-in-one utility function to be easy to use and to save time. The only concession we will make is that we will make this optional; controllable by a switch. We will do this because calling Get-MailboxStatistics does increase the time our cmdlet takes to finish.

First, let’s make a function exactly like GetMailbox2, but for MailboxStatistics instead. The only real difference is that a different Regex is used for determining whether we’ve encountered a “not found” error. (We put the regex in a single-quoted here-string, as it’s the best way to handle a string that includes both double and single quotes).

$MailboxStatisticsErrorRegex = @'
^The specified mailbox ".*" doesn't exist.
'@

Function GetMailboxStatistics2 {
    [cmdletbinding()]
    Param(
        [string]
        $Identity
    )
    try {

        $MailboxStatistics = $null
        $MailboxStatisticsError = $null
    
        # Exchange cmdlets do not pay attention to the -ErrorAction parameter,
        # but they do pay attention to $ErrorActionPreference.
        # Capture the old $ErrorActionPreference first as we should set it back once done.
        $ErrorActionPreferenceOld = $ErrorActionPreference
        $global:ErrorActionPreference = 'Stop'
        Write-Debug "[GetMailboxStatistics2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
    
        $MailboxStatistics = Get-MailboxStatistics -Identity $Identity

        # Double check that we have actually obtained an MailboxStatistics.
        if ($MailboxStatistics) {
            Write-Debug '[GetMailboxStatistics2] MailboxStatistics found.'
            $MailboxStatisticsFound = $true
        } else {
            Write-Debug '[GetMailboxStatistics2] MailboxStatistics not found.'
            $MailboxStatisticsFound = $false
        } #end else
 
    } catch [System.Management.Automation.RemoteException] {
        Write-Debug '[GetMailboxStatistics2] Get-MailboxStatistics error.'
        # We may need this message for later.    
        $MailboxStatisticsError = $_.Exception.Message

        if ($MailboxStatisticsError -match $MailboxStatisticsErrorRegex) {
            # We can handle a MailboxStatistics not found, so no need to throw.
            $MailboxStatisticsFound = $false
        } else {
            # We don't (yet) know how to handle this error, so rethrow for the user to see.
            throw
        } #end else
 
    } finally {

        # Set $ErrorActionPreference back to its previous value.
        $global:ErrorActionPreference = $ErrorActionPreferenceOld
        Write-Debug "[GetMailboxStatistics2] `$global:ErrorActionPreference set to $global:ErrorActionPreference"
    } #end finally

    [pscustomobject]@{
        MailboxStatistics=$MailboxStatistics
        MailboxStatisticsFound=$MailboxStatisticsFound
        MailboxStatisticsError=$MailboxStatisticsError
    }
} #end Function GetMailboxStatistics2

Now, let’s modify our NewMITUserObj function to accept the additional object.

Function NewMITUserObj {
    [cmdletbinding()]
    Param(
        [Parameter(mandatory=$true)]
        $MsolObj,

        [Parameter(mandatory=$true)]
        $MailboxObj,

        $MailboxStatisticsObj
    )
    # ...

And then use Add-Member to add additional properties to our output object, if MailboxStatisticsObj was given.

    # ...

    $obj = [pscustomobject]@{
        DisplayName        = $DisplayName
        UserPrincipalName  = $UserPrincipalName
        PrimarySmtpAddress = $PrimarySmtpAddress
        ObjectId           = $ObjectId
        MsolUser           = $MsolObj.MsolUser
        MsolUserFound      = $MsolObj.MsolUserFound
        MsolUserError      = $MsolObj.MsolUserError
        Mailbox            = $MailboxObj.Mailbox
        MailboxFound       = $MailboxObj.MailboxFound
        MailboxError       = $MailboxObj.MailboxError
    } #end [pscustomobject]

    if ($MailboxStatisticsObj) {
        
        # Additional properties regardless of whether we found the Mailbox Statistics or not.
        $additional = [ordered]@{
            MailboxStatistics      = $MailboxStatisticsObj.MailboxStatistics
            MailboxStatisticsFound = $MailboxStatisticsObj.MailboxStatisticsFound
            MailboxStatisticsError = $MailboxStatisticsObj.MailboxStatisticsError
        } #end [ordered]

        # Additional properties dependent on whether we found the mailbox statistics or not.
        # The somewhat roundabout way of doing it is so we do not have an error with StrictMode.

        if ($MailboxStatisticsObj.MailboxStatisticsFound) {
            $additional2 = [ordered]@{
                ItemCount     = $MailboxStatisticsObj.MailboxStatistics.ItemCount
                TotalItemSize = $MailboxStatisticsObj.MailboxStatistics.TotalItemSize.Value
            } #end [ordered]
        } else {
            $additional2 = [ordered]@{
                ItemCount     = $null
                TotalItemSize = $null
            } #end [ordered]
        } #end else

        $obj | Add-Member -NotePropertyMembers $additional
        $obj | Add-Member -NotePropertyMembers $additional2

    } #end if ($MailboxStatisticsObj)

    $obj.PSObject.TypeNames.Insert(0,'MilneIT.MITUser.User')

    $obj

} #end Function NewMITUserObj

Now onto Get-MITUser. We add a switch parameter for whether MailboxStatistics are included in the output. We have it default to false as, again, Get-MailboxStatistics takes time and we only want that if the user has asked for it.

Function Get-MITUser {
    [cmdletbinding()]
    Param(
        [Parameter(mandatory=$true)]
        [string]
        $Identity,

        [switch]
        $IncludeMailboxStatistics=$false
    )

    # ...

Then we rewrite the foreach loop to include the MailboxStatistics dependent on the switch. Note the following:

  1. Unlike Get-Mailbox, Get-MailboxStatistics’s -Identity does not accept the ObjectId. (The output of the cmdlet when you try it suggests this is a bug.) At the point we call it we already have the Mailbox, so we will use the Mailbox’s Guid to obtain the MailboxStatistics. This Guid is another globally unique attribute which is not the same as the ObjectId. We could also use the ExchangeGuid among other things.
  2. At this point, our if statements will get very messy due to the multiple combinations we would have to call $MITUsers += NewMITUserObj. So instead, we rewrite this block using the PowerShell technique splatting. This is where instead of passing parameters directly to a cmdlet, we put them in a variable as a hashtable, and then “splat” those parameters onto the cmdlet (by using @variable instead of $variable). One valuable advantage of this is that we can create the hashtable with some parameters, and then add other parameters into this hashtable as we go along – which we can do based on conditional statements. This greatly simplifies our code by reducing the need for nested if statements for every combination. We now only need a single line that invokes NewMITUserObj, but the parameters we splat will vary depending on the preceding conditional statements.
    foreach ($Mailbox in $MailboxesObj.Mailbox) {
        Write-Debug ('[Get-MITUser] Mailbox: {0}.' -F $Mailbox.ExternalDirectoryObjectId)
        
        $MailboxObj = [pscustomobject]@{
                Mailbox=$Mailbox
                MailboxFound=$true
                MailboxError=$null
        }

        <#
            If $MsolObj.MsolUser.ObjectId.Guid does not exist, referencing it
            would cause an error if StrictMode is set in PowerShell.

            $MsolObj.MsolUser.ObjectId.Guid will definitely be set if $MsolObj.MsolUserFound is true.

            PowerShell processes boolean operators from left to right.  If
            $MsolObj.MsolUserFound is $false, it does not process past the first
            -and because it knows the result must be $false.  This prevents any
            StrictMode errors.
        #>
        $MsolMailboxMatch = $MsolObj.MsolUserFound -and ($MsolObj.MsolUser.ObjectId.Guid -eq $Mailbox.ExternalDirectoryObjectId)

        $NewMITUserObjParams = @{
            MailboxObj=$MailboxObj
        }

        if ($IncludeMailboxStatistics) {
            $MailboxStatisticsObj = GetMailboxStatistics2 -Identity $Mailbox.Guid.Guid

            $NewMITUserObjParams.Add('MailboxStatisticsObj',$MailboxStatisticsObj)
        } #end if ($IncludeMailboxStatistics)


        if ($MsolMailboxMatch) {
            # We have matched, so create the MITUser object and add it to our final output.
            Write-Debug ('[Get-MITUser] Mailbox {0} matched with MsolUser.' -F $Mailbox.ExternalDirectoryObjectId)

            $NewMITUserObjParams.Add('MsolObj',$MsolObj)

            $MsolMailboxMatchFound = $true
        } else {
            Write-Debug ('[Get-MITUser] Mailbox did not match with MsolUser.  Obtaining MsolUser using {0}' -F $Mailbox.ExternalDirectoryObjectId)

            # We have not matched, so see if we can find an MsolUser for this mailbox.
            $LoopMsolObj = GetMsolUser2 -Identity $MailboxObj.Mailbox.ExternalDirectoryObjectId

            $NewMITUserObjParams.Add('MsolObj',$LoopMsolObj)

        } #end else

        $MITUsers += NewMITUserObj @NewMITUserObjParams

    } #end foreach ($MailboxObj in $MailboxesObj)

We do similar for the second phase where we have not matched the MsolUser with a mailbox yet. Unlike above where we had a Mailbox to begin with, here we have the MsolUser and may not actually obtain the Mailbox (i.e. if there isn’t one). So we have to handle the case where there is no Mailbox by creating a largely empty $MailboxStatisticsObj.

    if ($MsolObj.MsolUserFound -and ($MsolMailboxMatchFound -eq $false) ) {
        Write-Debug ('[Get-MITUser] MsolUser found but did not match any Mailbox.  Obtaining Mailbox using {0}.' -F $MsolObj.MsolUser.ObjectId)
        $MailboxObj = GetMailbox2 -Identity $MsolObj.MsolUser.ObjectId

        $NewMITUserObjParams = @{
            MsolObj    = $MsolObj
            MailboxObj = $MailboxObj
        }

        if ($IncludeMailboxStatistics) {
            if ($MailboxObj.MailboxFound) {
                $MailboxStatisticsObj = GetMailboxStatistics2 -Identity $MailboxObj.Mailbox.Guid.Guid
            } else {
                $MailboxStatisticsObj = [pscustomobject]@{
                    MailboxStatistics=$null
                    MailboxStatisticsFound=$false
                    MailboxStatisticsError=$null
                } #end [pscustomobject]
            } #end else
            $NewMITUserObjParams.Add('MailboxStatisticsObj',$MailboxStatisticsObj)
        } #end if ($IncludeMailboxStatistics)

        $MITUsers += NewMITUserObj @NewMITUserObjParams
    }

    $MITUsers

} #end Function Get-MITUser

Now, if we re-import our module, we can see that the MailboxStatistics properties are now included.

PS> Get-MITUser -Identity Vicki -IncludeMailboxStatistics | Format-List *


DisplayName            : Vicki Marsh
UserPrincipalName      : Vicki@milneitlab01.onmicrosoft.com
PrimarySmtpAddress     : Vicki@milneitlab01.onmicrosoft.com
ObjectId               : fe491646-d83c-4043-a6a5-d40b0a3075eb
MsolUser               : Microsoft.Online.Administration.User
MsolUserFound          : True
MsolUserError          : 
Mailbox                : Vicki Marsh
MailboxFound           : True
MailboxError           : 
MailboxStatistics      : Microsoft.Exchange.Management.MapiTasks.Presentation.MailboxStatistics
MailboxStatisticsFound : True
MailboxStatisticsError : 
ItemCount              : 116
TotalItemSize          : 646.2 KB (661,673 bytes)

PS> Get-MITUser -Identity vicki -IncludeMailboxStatistics | Select -ExpandProperty MailboxStatistics

DisplayName               ItemCount    StorageLimitStatus                              LastLogonTime
-----------               ---------    ------------------                              -------------
Vicki Marsh               116                                                    01/03/2017 14:05:23

But if the Get-MITUser formatted output doesn’t include any MailboxStatistics, because those properties are not in our format.

PS> Get-MITUser -Identity vicki -IncludeMailboxStatistics

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound
----------- -----------------                  ------------------                 ------------- ------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True        

How should we resolve this? If MailboxStatistics are not requested, we don’t want to display empty columns for TotalItemSize and ItemCount, as that would be very misleading.

The way I like to handle this is to introduce a separate type name and format view for when Mailbox Statistics are included. In MilneIT.format.ps1xml, right after we define end the View tag for MilneIT.MITUser.User, we create a new one for MilneIT.MITUser.UserWithMailboxStatistics. It’s largely the same but with two extra fields.

        <View>
            <Name>Default</Name>
            <ViewSelectedBy>
                <TypeName>MilneIT.MITUser.UserWithMailboxStatistics</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>DisplayName</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>UserPrincipalName</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>PrimarySmtpAddress</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>MsolUserFound</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>MailboxFound</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>ItemCount</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>TotalItemSize</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                </TableRowEntries>
            </TableControl>
        </View>

Then we just modify our NewMITUserObj function to choose the type name based on whether MailboxStatistics are included or not.

    if ($MailboxStatisticsObj) {
        $TypeName = 'MilneIT.MITUser.UserWithMailboxStatistics'
                
        # ...
        
    } else {
        $TypeName = 'MilneIT.MITUser.User'
    } #end else
    
    $obj.PSObject.TypeNames.Insert(0,$TypeName)
    
    $obj

If we reimport the module, the result is that we now have a different format based on whether we have included mailbox statistics or not.

PS> Get-MITUser -Identity Vicki

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound
----------- -----------------                  ------------------                 ------------- ------------
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True        

PS> Get-MITUser -Identity Vicki -IncludeMailboxStatistics

DisplayName UserPrincipalName                  PrimarySmtpAddress                 MsolUserFound MailboxFound ItemCount TotalItemSize           
----------- -----------------                  ------------------                 ------------- ------------ --------- -------------           
Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True          True         116       646.2 KB (661,673 bytes)

We can see that it was definitely worth making the inclusion of MailboxStatistics optional, as it roughly doubles how long it takes the command to execute.

PS> Measure-Command {Get-MITUser Will@milneitlab01.onmicrosoft.com }

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 737
Ticks             : 7374708
TotalDays         : 8.53554166666667E-06
TotalHours        : 0.000204853
TotalMinutes      : 0.01229118
TotalSeconds      : 0.7374708
TotalMilliseconds : 737.4708

PS> Measure-Command {Get-MITUser Will@milneitlab01.onmicrosoft.com -IncludeMailboxStatistics}

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 1
Milliseconds      : 515
Ticks             : 15155650
TotalDays         : 1.75412615740741E-05
TotalHours        : 0.000420990277777778
TotalMinutes      : 0.0252594166666667
TotalSeconds      : 1.515565
TotalMilliseconds : 1515.565

The full MilneIT.psm1 module as of this blog post can be viewed on Bitbucket here. You can always view the latest version of the full repository here.

Leave a Comment

Your email address will not be published. Required fields are marked *