All-in-one Get-User cmdlet: Part 7 – Skype for Business Online

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:

The final flowchart for the Get-MITUser function.

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:

  1. We add support for the CSOnlineUser object – take it as input, output its properties.
  2. I also redo how the individual properties are set – rather than grouping by preferred object source, group by individual variable instead.
  3. 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:

  1. It tries to find MsolUsers from the identity, and then gets Mailboxes and CSOnlineUsers from that.
  2. 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.
  3. 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.
  4. 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:

  1. For the Identity parameter, setting ValueFromPipeline to $true.
  2. For the Identity parameter, changing its type from [string] to an array of strings [string[]].
  3. Wrapping the main logic in a foreach loop (so it runs once for each object specified as a parameter).
  4. 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.

Leave a Comment

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