In the final part in this series (for now anyway), we will be adding Skype for Business Online support to the Get-MITUser cmdlet. The cmdlet will now offer a combined view of three objects: MsolUsers, Mailboxes, and CSOnlineUsers.
The previous design of the cmdlet heavily relies on (a) The fact we are only matching pairs of objects (MsolUsers and Mailboxes), and (b) The fact that Get-MsolUser only ever returns one result. Expanding this logic to include another object type is complicated.
There’s a Maths joke you can find floating around the internet (for example) that I think equally applies to code, particularly this scenario, that goes as follows:
A Mathematician and an Engineer attend a lecture by a Physicist. The topic concerns Kulza-Klein theories involving physical processes that occur in spaces with dimensions of 9, 12 and even higher. The Mathematician is sitting, clearly enjoying the lecture, while the Engineer is frowning and looking generally confused and puzzled. By the end the Engineer has a terrible headache. At the end, the Mathematician comments about the wonderful lecture. The Engineer says “How do you understand this stuff?”
Mathematician: “I just visualize the process”
Engineer: “How can you POSSIBLY visualize something that occurs in 9-dimensional space?”
Mathematician: “Easy, first visualize it in N-dimensional space, then let N go to 9”
In this case, to some extent we want to write code for N different types of user objects and then “let N go to 3”. What I have written is not quite generalised like that; but it’s nonetheless more modular and thus a lot simpler to create, read, and maintain; at the cost of possibly being less efficient. It will be a lot easier to expand in the future. (Get-SPOUser perhaps?).
Basically we go through each object type one-by-one (MsolUser, Mailbox, CSOnlineUser), try to obtain the object(s) from that Identity, exclude any we have already found in a previous step (using the ObjectId), and use the remaining object(s) to attempt to match with other object types.
It makes more sense in the revised flowchart:
First, we are going to modify our internal GetMsolUser2 and GetMailbox2 functions as follows:
- We now return multiple MsolUsers/Mailboxes as separate objects, rather than a single object.
- We include the ObjectId as part of the output object. This will simplify things as you will see later on. Note that Get-MsolUser returns the ObjectId as a Guid object, whereas Get-Mailbox returns it as a string.
We will cast the string from Get-Mailbox to a Guid for consistency using the-as
operator.
Function GetMsolUser2 { [cmdletbinding()] Param( [string] $Identity ) try { $MsolUser = $null $MsolUserError = $null $ObjectId = $null # Try converting $Identity to a Guid. # Note that Get-MsolUser attempts this exact process automatically # when called with the -ObjectId parameter. $Guid = $Identity -as [System.Guid] # If it can convert to a Guid, there's no way it's a UserPrincipalName, # if nothing else for the lack of @ sign. So we only need to try one command. if ($Guid) { Write-Debug '[GetMsolUser2] Trying as GUID.' $MsolUser = Get-MsolUser -ObjectId $Guid -ErrorAction Stop } else { Write-Debug '[GetMsolUser2] Trying as UPN.' $MsolUser = Get-MsolUser -UserPrincipalName $Identity -ErrorAction Stop } #end else # Double check that we have actually obtained an MsolUser. if ($MsolUser) { Write-Debug '[GetMsolUser2] MsolUser found.' $MsolUserFound = $true } else { Write-Debug '[GetMsolUser2] MsolUser not found.' $MsolUserFound = $false } #end else } catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException] { Write-Debug '[GetMsolUser2] Get-MsolUser Error.' # We may need this message for later. $MsolUserError = $_.Exception.Message if ($MsolUserError -match "^User Not Found.") { # We can handle a user not found, so no need to throw. $MsolUserFound = $false } else { # We don't (yet) know how to handle this error, so rethrow for the user to see. throw } #end else } #end catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException] if ($MsolUserFound) { # Output each found MsolUser. foreach ($ms in $MsolUser) { [pscustomobject]@{ MsolUser=$ms MsolUserFound=$MsolUserFound MsolUserError=$MsolUserError Identity=$Identity ObjectId=$ms.ObjectId } } #end foreach ($ms in $MsolUser) } else { # Output an single object with the error. [pscustomobject]@{ MsolUser=$MsolUser MsolUserFound=$MsolUserFound MsolUserError=$MsolUserError Identity=$Identity ObjectId=$ObjectId } } #end else } #end Function GetMsolUser2 Function GetMailbox2 { [cmdletbinding()] Param( [string] $Identity ) try { $Mailbox = $null $MailboxError = $null $ObjectId = $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 "[GetMailbox2] `$global:ErrorActionPreference set to $global:ErrorActionPreference" $Mailbox = Get-Mailbox -Identity $Identity # Double check that we have actually obtained a Mailbox. if ($Mailbox) { Write-Debug '[GetMailbox2] Mailbox found.' $MailboxFound = $true } else { Write-Debug '[GetMailbox2] Mailbox not found.' $MailboxFound = $false } #end else } catch [System.Management.Automation.RemoteException] { Write-Debug '[GetMailbox2] Get-Mailbox error.' # We may need this message for later. $MailboxError = $_.Exception.Message if ($MailboxError -match $MailboxErrorRegex) { # We can handle a mailbox not found, so no need to throw. $MailboxFound = $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 "[GetMailbox2] `$global:ErrorActionPreference set to $global:ErrorActionPreference" } #end finally if ($MailboxFound) { # Output each found Mailbox. foreach ($mbx in $Mailbox) { [pscustomobject]@{ Mailbox=$mbx MailboxFound=$MailboxFound MailboxError=$MailboxError Identity=$Identity # Exchange outputs the ObjectId as a string, so we cast as Guid ObjectId=$mbx.ExternalDirectoryObjectId -as [Guid] } } #end foreach ($mbx in $Mailbox) } else { # Output a single object with the error. [pscustomobject]@{ Mailbox=$Mailbox MailboxFound=$MailboxFound MailboxError=$MailboxError Identity=$Identity ObjectId=$null } } #end else } #end Function GetMailbox2
Then we create our GetCSOnlineUser2 internal function in the same manner as GetMsolUser2 and GetMailbox2. It’s most similar to GetMailbox2, asides from the ObjectId doesn’t need to be cast to a Guid.
$CSOnlineUserErrorRegex = @' ^Management object not found for identity ".*"\.$ '@
Function GetCSOnlineUser2 { [cmdletbinding()] Param( [string] $Identity ) try { $CSOnlineUser = $null $CSOnlineUserError = $null $ObjectId = $null # Capture the old $ErrorActionPreference first as we should set it back once done. $ErrorActionPreferenceOld = $ErrorActionPreference $global:ErrorActionPreference = 'Stop' Write-Debug "[GetCSOnlineUser2] `$global:ErrorActionPreference set to $global:ErrorActionPreference" $CSOnlineUser = Get-CSOnlineUser -Identity $Identity # Double check that we have actually obtained a CSOnlineUser. if ($CSOnlineUser) { Write-Debug '[GetCSOnlineUser2] CSOnlineUser found.' $CSOnlineUserFound = $true $ObjectId = $CSOnlineUser.ObjectId } else { Write-Debug '[GetCSOnlineUser2] CSOnlineUser not found.' $CSOnlineUserFound = $false } #end else } catch [System.Management.Automation.RemoteException] { Write-Debug '[GetCSOnlineUser2] Get-CSOnlineUser error.' # We may need this message for later. $CSOnlineUserError = $_.Exception.Message if ($CSOnlineUserError -match $CSOnlineUserErrorRegex) { # We can handle a CSOnlineUser not found, so no need to throw. $CSOnlineUserFound = $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 "[GetCSOnlineUser2] `$global:ErrorActionPreference set to $global:ErrorActionPreference" } #end finally [pscustomobject]@{ CSOnlineUser=$CSOnlineUser CSOnlineUserFound=$CSOnlineUserFound CSOnlineUserError=$CSOnlineUserError ObjectId=$ObjectId } } #end Function GetCSOnlineUser2
Then we modify our NewMITUserObj internal function:
- We add support for the CSOnlineUser object – take it as input, output its properties.
- I also redo how the individual properties are set – rather than grouping by preferred object source, group by individual variable instead.
- Additionally, to avoid repetition in the various stages of Get-MITUser, I move the logic to obtain MailboxStatistics (if requested) into this function.
$MailboxStatisticsEmptyObj = [pscustomobject]@{ MailboxStatistics=$null MailboxStatisticsFound=$false MailboxStatisticsError=$null } #end [pscustomobject]
Function NewMITUserObj { [cmdletbinding()] Param( [Parameter(mandatory=$true)] $MsolObj, [Parameter(mandatory=$true)] $MailboxObj, [Parameter(mandatory=$true)] $CSOnlineUserObj, [switch] $IncludeMailboxStatistics=$false ) $DisplayName = if ($MsolObj.MsolUserFound) { $MsolObj.MsolUser.DisplayName } elseif ($MailboxObj.MailboxFound) { $MailboxObj.Mailbox.DisplayName } elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.DisplayName } $UserPrincipalName = if ($MsolObj.MsolUserFound) { $MsolObj.MsolUser.UserPrincipalName } elseif ($MailboxObj.MailboxFound) { $MailboxObj.Mailbox.UserPrincipalName } elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.UserPrincipalName } $ObjectId = if ($MsolObj.MsolUserFound) { $MsolObj.ObjectId } elseif ($MailboxObj.MailboxFound) { $MailboxObj.ObjectId } elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.ObjectId } # For the PrimarySMTPAddress we prefer Exchange over Skype for Business over Office 365 $PrimarySmtpAddress = if ($MailboxObj.MailboxFound) { $MailboxObj.Mailbox.PrimarySmtpAddress } elseif ($CSOnlineUserObj.CSOnlineUserFound) { $CSOnlineUserObj.CSOnlineUser.ProxyAddresses | ForEach-Object { if ($_ -cmatch '^SMTP:(.*)$') { $Matches[1] } } } elseif ($MsolObj.MsolUserFound) { $MsolObj.MsolUser.ProxyAddresses | ForEach-Object { if ($_ -cmatch '^SMTP:(.*)$') { $Matches[1] } } } $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 CSOnlineUser = $CSOnlineUserObj.CSOnlineUser CSOnlineUserFound = $CSOnlineUserObj.CSOnlineUserFound CSOnlineUserError = $CSOnlineUserObj.CSOnlineUserError } #end [pscustomobject] if ($IncludeMailboxStatistics) { $TypeName = 'MilneIT.MITUser.UserWithMailboxStatistics' if ($MailboxObj.MailboxFound) { # If the mailbox is found, use the Mailbox Guid to obtain the MailboxStatistics. $MailboxStatisticsObj = GetMailboxStatistics2 -Identity $MailboxObj.Mailbox.Guid.Guid } else { # If the mailbox isn't found, use the empty object instead. $MailboxStatisticsObj = $MailboxStatisticsEmptyObj } #end else # 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 } else { $TypeName = 'MilneIT.MITUser.User' } #end else $obj.PSObject.TypeNames.Insert(0,$TypeName) $obj } #end Function NewMITUserObj
Now we put this together in our reworked Get-MITUser cmdlet. Just like in the flowchart:
- It tries to find MsolUsers from the identity, and then gets Mailboxes and CSOnlineUsers from that.
- It tries to find Mailboxes from the identity, excludes anything we have already found (by ObjectId), and then finds the MsolUsers and CSOnlineUsers from those.
- It tries to find CSOnlineUsers from the identity, excludes anything we have already found (by ObjectId), and then find the MsolUsers and Mailboxes from those.
- It then spits it all out as output.
While we’re at it, I also add support for receiving multiple objects and input from the pipeline in true Microsoft cmdlet style. This required a few things:
- For the Identity parameter, setting
ValueFromPipeline
to$true
. - For the Identity parameter, changing its type from
[string]
to an array of strings[string[]]
. - Wrapping the main logic in a foreach loop (so it runs once for each object specified as a parameter).
- Wrapping that foreach loop in a Process block (so it runs once for each object passed through the pipeline).
$MITUserErrorText = @' Unable to find user {0}. MsolUserError: {1} MailboxError: {2} CSOnlineUserError: {3} '@
Function Get-MITUser { <# .SYNOPSIS Obtains an MITUser object which combines Office 365 MsolUser, Exchange Online Mailbox, and CSOnlineUser. .PARAMETER Identity The Identity of the user you wish to obtain. You can use any of the following: From Get-MsolUser: - UserPrincipalName - ObjectId From Get-Mailbox: - Name - Display name - Alias - Distinguished name (DN) - Canonical DN - <domain name>\<account name> - Email address - GUID - LegacyExchangeDN - SamAccountName - User ID or user principal name (UPN) From Get-CSOnlineUser: - SIP Address - UserPrincipalName - <domain name>\<account name> - Display name - Distinguished name (DN) .PARAMETER IncludeMailboxStatistics Whether to include MailboxStatistics properties. This will increase the time the cmdlet takes per user. #> [cmdletbinding()] Param( [Parameter( mandatory=$true, ValueFromPipeline=$true )] [string[]] $Identity, [switch] $IncludeMailboxStatistics=$false ) Process { foreach ($id in $Identity) { # An empty array we will use to store and then output all combined objects. $MITUsers = @() ### # MsolUser ### # Try to get the MsolUser from the provided identity. $MsolObjects = GetMsolUser2 -Identity $id # At present only one object should be returned, but for the sake of future proofing # we will allow for GetMsolUser2 to give us multiple objects. # Obtain Mailbox and CSOnlineUser from MsolUser. foreach ($MsolObj in ($MsolObjects | Where-Object MsolUserFound) ) { $MailboxObj = GetMailbox2 -Identity $MsolObj.ObjectId $CSOnlineUserObj = GetCSOnlineUser2 -Identity $MsolObj.ObjectId Write-Debug ('[Get-MITUser] MsolUser: {0}.' -F $MsolObj.ObjectId) Write-Debug ('[Get-MITUser] MailboxFound: {0}.' -F $MailboxObj.MailboxFound) Write-Debug ('[Get-MITUser] CSOnlineUserFound: {0}.' -F $CSOnlineUserObj.CSOnlineUserFound) $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics } #end foreach ($MsolObj in $MsolObjects) ### # Mailbox ### # Try to get the Mailbox from the provided identity. $MailboxObjects = GetMailbox2 -Identity $id # Filter out any we already have. if ($MITUsers) { $MailboxObjects = $MailboxObjects | Where-Object { $_.ObjectId -notin $MITUsers.ObjectId } } #end if ($MITUsers) # Obtain MsolUser and CSOnlineUser from Mailbox. foreach ($MailboxObj in ($MailboxObjects | Where-Object MailboxFound) ) { $MsolObj = GetMsolUser2 -Identity $MailboxObj.ObjectId $CSOnlineUserObj = GetCSOnlineUser2 -Identity $MailboxObj.ObjectId Write-Debug ('[Get-MITUser] Mailbox: {0}.' -F $MailboxObj.ObjectId) Write-Debug ('[Get-MITUser] MsolUserFound: {0}.' -F $MsolObj.MsolUserFound) Write-Debug ('[Get-MITUser] CSOnlineUserFound: {0}.' -F $CSOnlineUserObj.CSOnlineUserFound) $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics } ### # CSOnlineUser ### # Try to get the CSOnlineUser from the provided identity. $CSOnlineUserObjects = GetCSOnlineUser2 -Identity $id # Filter out any we already have. if ($MITUsers) { $CSOnlineUserObjects = $CSOnlineUserObjects | Where-Object { $_.ObjectId -notin $MITUsers.ObjectId } } #end if ($MITUsers) # Obtain MsolUser and Mailbox from CSOnlineUser. foreach ($CSOnlineUserObj in ($CSOnlineUserObjects | Where-Object CSOnlineUserFound) ) { $MsolObj = GetMsolUser2 -Identity $CSOnlineUserObj.ObjectId $MailboxObj = GetMailbox2 -Identity $CSOnlineUserObj.ObjectId Write-Debug ('[Get-MITUser] CSOnlineUser: {0}.' -F $CSOnlineUserObj.ObjectId) Write-Debug ('[Get-MITUser] MsolUserFound: {0}.' -F $MsolObj.MsolUserFound) Write-Debug ('[Get-MITUser] MailboxFound: {0}.' -F $MailboxObj.MailboxFound) $MITUsers += NewMITUserObj -MsolObj $MsolObj -MailboxObj $MailboxObj -CSOnlineUserObj $CSOnlineUserObj -IncludeMailboxStatistics:$IncludeMailboxStatistics } ### # Finish up ### # If our array is still empty, then we should throw. if (-not $MITUsers) { Write-Debug '[Get-MITUser] Neither MsolUser nor Mailbox found.' # Note that if we are here, we know that $(Msol|Mailbox|CsOnlineUser)Objects # contains exactly one object. So we don't need to handle arrays at all. # This object basically says nothing is found and gives us the error. $ErrorText = $MITUserErrorText -F $id,$MsolObjects.MsolUserError,$MailboxObjects.MailboxError,$CSOnlineUserObjects.CSOnlineUserError throw ($ErrorText) } # Output all users found. $MITUsers } #end foreach ($id in $Identity) } #end Process } #end Function Get-MITUser
One final change – remember our cmdlet formatting in MilneIT.format.ps1xml? If we don’t make changes here, our objects will include new properties but they won’t be displayed by default. All we need to do is add the CSOnlineUserFound property to each object type:
... <TableColumnItem> <PropertyName>MailboxFound</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>CSOnlineUserFound</PropertyName> </TableColumnItem> ...
Now let’s run a few tests. Remember our sample users from Part 5? Nothing has changed since then, and they are all currently enabled for Skype for Business Online.
Let’s set our $DebugPreference
to Continue so that debug messages are displayed but the command isn’t paused. Then let’s begin with our simple example Vicki.
PS> $DebugPreference = 'Continue' PS> Get-MITUser -Identity 'Vicki@milneitlab01.onmicrosoft.com' DEBUG: [Get-MITUser] MsolUser: fe491646-d83c-4043-a6a5-d40b0a3075eb. DEBUG: [Get-MITUser] MailboxFound: True. DEBUG: [Get-MITUser] CSOnlineUserFound: True. DisplayName UserPrincipalName PrimarySmtpAddress MsolUserFound MailboxFound CSOnlineUserFound ----------- ----------------- ------------------ ------------- ------------ ----------------- Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True True True PS> Get-MITUser -Identity 'Vicki' DEBUG: [Get-MITUser] Mailbox: fe491646-d83c-4043-a6a5-d40b0a3075eb. DEBUG: [Get-MITUser] MsolUserFound: True. DEBUG: [Get-MITUser] CSOnlineUserFound: True. DisplayName UserPrincipalName PrimarySmtpAddress MsolUserFound MailboxFound CSOnlineUserFound ----------- ----------------- ------------------ ------------- ------------ ----------------- Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True True True PS> Get-MITUser -Identity 'SIP:Vicki@milneitlab01.onmicrosoft.com' DEBUG: [Get-MITUser] CSOnlineUser: fe491646-d83c-4043-a6a5-d40b0a3075eb. DEBUG: [Get-MITUser] MsolUserFound: True. DEBUG: [Get-MITUser] MailboxFound: True. DisplayName UserPrincipalName PrimarySmtpAddress MsolUserFound MailboxFound CSOnlineUserFound ----------- ----------------- ------------------ ------------- ------------ ----------------- Vicki Marsh Vicki@milneitlab01.onmicrosoft.com Vicki@milneitlab01.onmicrosoft.com True True True
We can see that in all cases, the output object is same. However the Debug output shows us that in the first case, it was first matched on MsolUser (by UserPrincipalName), in the second case it was first matched on the Mailbox (by Alias), and in the third case it was first matched on the CSOnlineUser (by the SIP address).
How does it handle the two Wills? Also let’s pass this from the pipeline this time.
PS> 'Will' | Get-MITUser DEBUG: [Get-MITUser] Mailbox: 709f2ce8-4cbd-4f5d-b0b6-dc56f337c753. DEBUG: [Get-MITUser] MsolUserFound: True. DEBUG: [Get-MITUser] CSOnlineUserFound: True. DEBUG: [Get-MITUser] CSOnlineUser: d5bffab9-3855-44d2-82ab-4b75ba62e67a. DEBUG: [Get-MITUser] MsolUserFound: True. DEBUG: [Get-MITUser] MailboxFound: False. DisplayName UserPrincipalName PrimarySmtpAddress MsolUserFound MailboxFound CSOnlineUserFound ----------- ----------------- ------------------ ------------- ------------ ----------------- Will Cook Will2@milneitlab01.onmicrosoft.com Will@milneitlab01.onmicrosoft.com True True True Will Richard Will@milneitlab01.onmicrosoft.com True False True
This is good, we can now get both of them from just their first name. Will Cook is first matched by Mailbox, by his alias ‘Will’. Will Richard is matched by the CSOnlineUser. Interestingly this doesn’t match anything that Get-CSOnlineUser officially matches – the exact string “Will” is not his SIP address, UPN, domain\username, Display Name, or Distinguished Name.
Without further investigation, my best guess is that Get-CSOnlineUser also matches Aliases, which is a property of CSOnlineUser as you can see below. The only other exactly equal match is his First Name, which is unlikely to be used in Get-CSOnlineUser’s matching process.
PS> $WillR = Get-MITUser 'Will Richard' PS> $WillR.CSOnlineUser | Format-Table DisplayName,Alias,FirstName,LastName,ObjectId DisplayName Alias FirstName LastName ObjectId ----------- ----- --------- -------- -------- Will Richard Will Will Richard d5bffab9-3855-44d2-82ab-4b75ba62e67a
And finally, how does it look now when someone doesn’t exist?
PS> Get-MITUser -Identity 'DoesNotExist'
DEBUG: [Get-MITUser] Neither MsolUser nor Mailbox found.
Unable to find user DoesNotExist.
MsolUserError: User Not Found. User: DoesNotExist.
MailboxError: The operation couldn't be performed because object 'DoesNotExist' couldn't be found on 'MMXP123A001DC05.GBRP123A001.PROD.OUTLOOK.COM'.
CSOnlineUserError: Management object not found for identity "DoesNotExist".
At C:\Users\Ryan\OneDrive\Scripts\Milne.IT\PowerShell\Modules\milneit\MilneIT.psm1:830 char:21
+ throw ($ErrorText)
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Unable to find ..."DoesNotExist".:String) [], RuntimeException
+ FullyQualifiedErrorId : Unable to find user DoesNotExist.
MsolUserError: User Not Found. User: DoesNotExist.
MailboxError: The operation couldn't be performed because object 'DoesNotExist' couldn't be found on 'MMXP123A001DC05.GBRP123A001.PROD.OUTLOOK.COM'.
CSOnlineUserError: Management object not found for identity "DoesNotExist".
Good, it shows us all three errors, which makes for easier troubleshooting.
The full code as of this blog post can be viewed on Bitbucket here. You can always view the latest version of the full repository here.