O365 Mobile Devices

O365 Mobile Devices

I needed to grab all the mobile devices that had emails attached to them not too long ago. I could have gone through the GUI, and spent a few days trying my best to document everything, or I could use Powershell to pull the O365 Mobile Devices. Can you guess which one I did? Yep, PowerShell is a way of thinking nowadays in the system admin world.

The Script

Connect-ExchangeOnline
$Mailboxes = get-mailbox -all
$Devices = foreach ($Mail in $Mailboxes) {
    $MobileDevices = Get-MobileDevice -Mailbox $mail.userprincipalname
    foreach ($Device in $MobileDevices) {
        $Stats = Get-MobileDeviceStatistics -Identity $Device.Identity
        [pscustomobject]@{
            User = $mail.userprincipalname
            FriendlyName                  = $Device.FriendlyName
            name                          = $Device.name
            Identity                      = $Device.Identity
            DeviceID                      = $Device.DeviceId
            DeviceOS                      = $Device.DeviceOS
            DeviceType                    = $Device.DeviceType
            DeviceModel                   = $Device.DeviceModel 
            ClientType                    = $Stats.ClientType
            FirstSyncTime                 = $Device.FirstSyncTime
            WhenChanged                   = $Device.WhenChanged
            LastPolicyUpdateTime          = $Stats.LastPolicyUpdateTime
            LastSyncAttemptTime           = $Stats.LastSyncAttemptTime
            LastSuccessSync               = $stats.LastSuccessSync
            DeviceAccessState             = $Device.DeviceAccessState
            DeviceAccessStateReason       = $Device.DeviceAccessStateReason
            IsValid                       = $Device.IsValid
            Status                        = $Stats.Status
            DevicePolicyApplied           = $stats.DevicePolicyApplied
            DevicePolicyApplicationStatus = $stats.DevicePolicyApplicationStatus
        }
    }
}
$Devices

The Break Down

This script is pretty straightforward. We first connect to exchange online. Currently, this script is designed to work with MFA. Then we grab all the mailboxes from O365.

Connect-ExchangeOnline
$Mailboxes = get-mailbox -all

Next, we loop through these mailboxes. We drop all the information get gather from this loop into a variable. I called my devices. Once again, nothing great or grand.

$Devices = foreach ($Mail in $Mailboxes) {}

For each mailbox we have, we pull all of the devices from that mailbox. Since a single mailbox can have more than one device, we loop through these devices. For each loop, we want to grab that device’s stats to gain more insights.

$MobileDevices = Get-MobileDevice -Mailbox $mail.userprincipalname
    foreach ($Device in $MobileDevices) {
        $Stats = Get-MobileDeviceStatistics -Identity $Device.Identity

Finally, from there we gather the information into a single object. These commands produce a lot of information. It’s best to parse it down some. That’s what I did with the next part. That’s how I am able to get all the O365 Mobile Devices using PowerShell.

Additional Reading

Exchange Mailbox Rules

Exchange Mailbox Rules

At one of my previous Jobs, I had to write a script to help me understand rules. Well, I pulled that script out of the vault to do the same thing recently. The following script will allow you to select one user or multiple users to grab their rules. Or you can just forget the flag and get all of the users at once. The idea of the script is to get basic information from the rule, not all the information as it’s not all useful. So let’s look at the script then we will do a breakdown. Here comes Exchange Mailbox Rules through PowerShell.

The script

function Get-MailboxInboxRules {
    [cmdletbinding()]
    param (
        [string[]]$UserPrincipalName,
        [switch]$StayConnected
    )
    begin {
        #Begining tests if the module is installed, loaded, and connected. Correcting each one at each level. 

        #Installs required modules
        Write-Verbose "Installing required modules"
        if (!(Get-InstallEdModule ExchangeOnlineManagement)) { 
            try {
                Install-Module ExchangeOnlineManagement
            }catch {
                Write-Error $_
                exit
            } 
        }
 
        Write-Verbose "Checking and importing required modules"
        # Starts importanting required modules
        if (!(Get-Command Connect-ExchangeOnline)) { 
            try {
                Import-Module ExchangeOnlineManagement
            } catch {
                Write-Error $_
                exit
            }
        }

        #Tests if Exchange Online is connected, If not, we trigger a connection
        if (!(Get-PSSession | Where-Object { $_.Name -match 'ExchangeOnline' -and $_.Availability -eq 'Available' })) { 
            try {
                Connect-ExchangeOnline}
            catch {
                Write-Error $_
                exit
            } 
        }
        if (!(Get-PSSession | Where-Object { $_.Name -match 'ExchangeOnline' -and $_.Availability -eq 'Available' })) {
            Write-Error "Connection failed"
            exit
        }
    }
    Process {
        $mailboxes = @()
        if (!($PSBoundParameters.ContainsKey("UserPrincipalName"))) {
            $Mailboxes = Get-mailbox
        }
        else {
            foreach ($Username in $UserPrincipalName) {
                try {
                    $Mailboxes += get-mailbox $username -ErrorAction SilentlyContinue
                }
                catch {
                    Write-Warning "$username has no mailbox"
                }
            }
        }

        #It has been proven to speed up the script if we drop the output into a return value
        $RulesReturn = Foreach ($Mail in $Mailboxes) {
            
            Write-Verbose "Scanning: $($Mail.UserPrincipalName)"
            #We are now trying to catch the rule in question. 
            #if the mailbox has no rules, which will be presented as an error, then we null out the rules. 
            try {
                $Rules = Get-InboxRule -Mailbox $Mail.Userprincipalname
            }
            catch {
                $Rules = $null  
            }

            #Now that the rules either have value or null, we can test. 
            if ($null -ne $Rules) {
                #Write-Host "$($mail.userprincipalname)"

                #Now we know we have rules, it's time to start working with those rules
                foreach ($Rule in $Rules) {

                    #From my testing I discover that some rules will be null but the rule itself isn't null. 
                    #Thus we need to test if this is the case. 
                    if ($null -ne $rule) {

                        #Now we have confirmed the rule is not empty, we need to test the form
                        #This is because some rules are based on subjects and such and not from people. 
                        if ($null -ne $rule.from) {
                            
                            #since form is not empty we split the string and we get the from information. 
                            $froms = $Rule.from.split('[')[0]
                        }
                        else {

                            #if it is, we just blank string the value. 
                            $froms = ""
                        }

                        #Next we want the description to be on a single line. this way we can export to a csv. 
                        if ($null -ne $rule.description) {

                            #This is programmed for the standard 5.1 powershell. Join-String was introduced in 6.2
                            #to combat this, we create a null return
                            #Then we  split the description rule. 
                            #Then we for each that list and push it back into the return.
                            
                            $dereturn = $Null
                            $rule.description.split("`n") | foreach-object { $dereturn = "$dereturn $($_.trim())" }
                            $description = $dereturn.trim()
                        }
                        else {
                            $description = ""
                        }
                        #Next we create our ps object with items we need for basic level audits. 
                        [pscustomobject]@{
                            Username    = $Mail.Userprincipalname
                            Identity    = $Rule.Identity
                            Enabled     = $Rule.Enabled
                            Name        = $Rule.Name
                            from        = $froms
                            Description = $description
                        } 
                    }
                }
            }
        }
    }
    end {

        #At the end we return
        $RulesReturn

        #Then we disconnect if the user didn't say stay connected. 
        if (!($StayConnected)) { Disconnect-ExchangeOnline -Confirm:$false }
    }
}

The Breakdown

Before we begin, I want to point out, at this point, there is no graph API calls for exchange. Not yet, its promised, but as of right now, it’s not reality yet. Security is a big thing to think about with scripts like these. MFA is a big piece of security. We always want some mfa going. This is why you will not see credentials in the parameters.

Parameters

Our parameters are simple. We have a list of strings of user principal names and we have a switch to determine if we need to stay connected. If we leave the user principal name blank, then we are targeting every mailbox. It may take some time. The stay connected is designed to keep exchange connected. This way once you have the exchange mailbox rules, you can run other commands. You can also leave this blank, if you do, it disconnects from exchange. Good security thinking here.

param (
        [string[]]$UserPrincipalName,
        [switch]$StayConnected
)

Begin

Next is our begin. We are doing some house keeping here. First, We need to know if the exchange online management is installed. if it’s not, we then install it. Next, test if the exchange online management is imported. if not, we import the module. Finally we test if we have an exchange online connection, if not, we use connect-exchange. We use the connect-exchangeonline because it will do the MFA for us. Which is nice.

To test if the module is installed, we use the Get-InstalledModule. This command searches your modules and lets you know if it is installed. Older Powershells do not know this command. If the powershell is not elevated, then this code will error out and exits with the exit code.

Write-Verbose "Installing required modules"
if (!(Get-InstallEdModule ExchangeOnlineManagement)) { 
        try {
            Install-Module ExchangeOnlineManagement
        }catch {
            Write-Error $_
            exit
        } 
}

If we haven’t exited out yet, we then see if the module is loaded by using the get-command command. We are looking for the command Connect-ExchangeOnline. If the command exists, we will continue on, if not, we will use the import-module command and import the exchangeonlinemanagement module. Of course, if we run into an error, we exit with the error.

Write-Verbose "Checking and importing required modules"
# Starts importanting required modules
if (!(Get-Command Connect-ExchangeOnline)) { 
    try {
        Import-Module ExchangeOnlineManagement
    } catch {
        Write-Error $_
        exit
    }
}

Finally, we are going to pull all the PowerShell Sessions currently on this computer. This is where we will gain the ability to pull Exchange Mailbox Rules. The first command is get-pssession. We search the output for a session with the name of ExchangeOnline that is available. If we don’t find one, we connect to exchange using the connect-exchangeonline. Next, if connection errors out, we present the error and disconnect. Now, if the connection is successful, we test once more for a connection. If it’s just not there, we give up and say bye.

#Tests if Exchange Online is connected, If not, we trigger a connection
        if (!(Get-PSSession | Where-Object { $_.Name -match 'ExchangeOnline' -and $_.Availability -eq 'Available' })) { 
            try {
                Connect-ExchangeOnline}
            catch {
                Write-Error $_
                exit
            } 
        }
        if (!(Get-PSSession | Where-Object { $_.Name -match 'ExchangeOnline' -and $_.Availability -eq 'Available' })) {
            Write-Error "Connection failed"
            exit
        }

Process – Exchange Mailbox Rules

Check Mailboxes

Now we are connected to exchange online, it’s time to start grabbing the needed information. Before we start, we need to establish an array for possible mailboxes. We will call these mailboxes to make life easy.

$mailboxes = @()

Now, whether or not the user used the “UserPrincipalName” parameter or not, we are ready. The next step is to test if we used the parameter and grab those mailboxes. We do this by using the $PSBoundParameters variable. Searching the Contains key allows you to see what parameters are being passed to the script. in our case we will be searching for the user principal name. If there is no UPN, we just grab all the mailboxes and dump it into the $mailboxes variable we made.

Now, if we did use the upn, we will loop through the UPN and grab each mailbox accordingly. if there is an issue, we let the user know that that username was an issue. This will give us the mailbox information we need. We do this separately because it causes fewer errors. Best way to describe it is using this method is the bouncer. It bounces issues out the door before we start looking at the inbox rules.

$mailboxes = @()
        if (!($PSBoundParameters.ContainsKey("UserPrincipalName"))) {
            $Mailboxes = Get-mailbox
        }
        else {
            foreach ($Username in $UserPrincipalName) {
                try {
                    $Mailboxes += get-mailbox $username -ErrorAction SilentlyContinue
                }
                catch {
                    Write-Warning "$username has no mailbox"
                }
            }
        }

Exchange Mailbox Rules

The next step is to grab the rules themselves. We start off by making a for each loop. The loop of course is going to drop into a variable. We do this because it has been found that dropping the output of a foreach loop into a variable is a faster method than rebuilding or appending an array.

$RulesReturn = Foreach ($Mail in $Mailboxes) {}

Now, it’s time to hang onto your hats. The rules are tricky as they error out. We see errors because the end user will create a rule, and abandon it. It has nothing to do with your code. It is purely what it is.

The first thing we need to do inside our loop is grab the rules for the mailbox. The command is Get-InboxRule. We will be placing the rules into a variable called Rules. Now here is the catch. This command will produce something. So throw it in a try catch. So if it produces an error, which happens, you can set the Rules to null.

Inbox Rules

try {
    $Rules = Get-InboxRule -Mailbox $Mail.Userprincipalname
} catch {
    $Rules = $null  
}

Next, we test if the Rules are null, if not, we start the loop. Here is another fun part… If the rule is misconfigured, it will present as null from time to time. So we need to test for the null again per rule.

if ($null -ne $Rules) {
    Write-Verbose "$($mail.userprincipalname)"
    foreach ($Rule in $Rules) {
        if ($null -ne $rule) {}
    }
}

Parsing the data

Some rules are based on subject, some rules are based on email. It’s best that we grab useful information. Things like the description, and from are presented in string format with unique structures. However, sometimes those can be null strings and that will explode the results as well. So we have to test them. First we will test the from. if the from is not null, we want to split the from, from the bracket and select the first item. However, if the from is null, we want to give the from a blank string as a psobject doesn’t like null.

if ($null -ne $rule.from) {
    $froms = $Rule.from.split('[')[0]
} else {
    $froms = ""
}

After the From, we need to grab the description. However, I need this description on a single line. As most people are using PowerShell 5, Join-String is not available. Which is sad. So, I built my own join-string. To do this, first create a null return. Then split the Rules description by the enter, `n. Next we do a foreach-object loop, taking that null return value and added itself to itself with a nice trim. Finally, I dropped that information into the description. If the description was null to begin with we drop a blank string into the description variable.

if ($null -ne $rule.description) {
    $dereturn = $Null
    $rule.description.split("`n") | foreach-object { $dereturn = "$dereturn $($_.trim())" }
    $description = $dereturn.trim()
} else {
    $description = ""
}

Bringing it together

Finally, we have all the information we need. It’s time to create the PS Custom Object. Here we will be presenting the username, the ID of the rule, if the rule is enabled, the from, and the description. Since we joined the strings of the description before, this output can be exported to a csv file. Remember at the beginning of the main loop, we are placing all the output of that loop into a variable called Rules Return. Well, this is what will be going into that variable.

[pscustomobject]@{
    Username    = $Mail.Userprincipalname
    Identity    = $Rule.Identity
    Enabled     = $Rule.Enabled
    Name        = $Rule.Name
    from        = $froms
    Description = $description
} 

End

Finally, we reach the end of this script. We are doing two things at the end of this script. First, we are presenting the data. Then we are testing if we need to stay connected. We present the variable Rules Return. Then we check if Stay Connected is true. If it isn’t we disconnect from exchange with a confirmation of false. If you set the flag to stay connected when you executed the code, then this part only shows the rules. No need to disconnect. I always love having this option as exchange online is a pain with multiple MFA layers.

$RulesReturn
if (!($StayConnected)) { Disconnect-ExchangeOnline -Confirm:$false }

Continue Reading:

As always, I like to present some items at the end to encourage you to continue reading.

Find Forwarding Rules

Find Forwarding Rules

Not too long ago, I needed to do some rule auditing for forwarders in a client’s exchange online. They believed someone had a rule in their exchange account that was forwarded to a spammer. They believed this because new employees were receiving emails within a few days of creation. So, it’s time for some PowerShell magic to save the day. It’s time to Find Forwarding Rules in your mailboxes with PowerShell.

The Script

Connect-ExchangeOnline
$Mailboxes = Get-Mailbox -ResultSize Unlimited
$ForwarderRules = foreach ($Mailbox in $Mailboxes) {
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
    foreach ($rule in $rules) {
        if (($null -ne $rule.ForwardTo) -or ($null -ne $rule.ForwardAsAttachmentTo)) {
            [pscustomobject][ordered]@{
                Username = $Mailbox.Alias
                Rule = $Rule.name
                ID = $Rule.RuleIdentity
                Enabled = $rule.enabled
                ForwardTo = $rule.ForwardTo | where-object {$_ -like "*@*"}
                ForwardAsAttachmentTo = $rule.ForwardAsAttachmentTo | where-object {$_ -like "*@*"}
            }
        }
    }
}
$ats = $ForwarderRules | where-object {($null -ne $_.ForwardTo) -or ($null -ne $_.ForwardAsAttachmentTo)}
$ats

The Breakdown

The script today requires the Exchange Online Module to be installed. If you don’t have it, go install it. Once you have it, you will need to connect using the Connect-ExchangeOnline commandlet.

Connect-ExchangeOnline

By doing it this way, MFA will be triggered and we want MFA to be at this level. Security first yall. This brings me to my next point, soon exo 3 will come out and security will be improved greatly.

Once you are connected, we need now to pull all the mailboxes from the system. This command can take some time if you have a large company. In fact, this script with only 300 users took around an hour. The Larger your company is, the longer it will take. Plan ahead accordingly.

$Mailboxes = Get-Mailbox -ResultSize Unlimited

Now we have all the mailboxes, we need to go through each mailbox and get the inbox rules for that mailbox. We start a for each loop of the mailboxes.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) { 

}

Next, we will need to grab the inbox rules for that mailbox. We do this with the Get-InboxRule commandlet and we feed it the mailbox alias.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) { 
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
}

Normally a mailbox has more than one rule. Thus, we need to make another for each loop for the rules inside our main foreach loop.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) { 
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
    foreach ($rule in $rules) {
    
    }
}

Afterward, we need to pull the data out of the rules and make it useful. The amount of output is large, breaking it down and making it useful is important. That’s the whole goal of this. We want to find out who has forwarders and we want to know if those forwarders are forwarding out to someone else. I want to break it up as well so I can look at all the forwarders and just the ones with email addresses.

Gathering Information

Firstly, we need to ask the question, Are we forwarding to someone as an email or an attachment? The properties we want to look at are, forwardto and forwardasattachmentto. If either of these are not null, then we want to look at that information. This allows us to Find Forwarding Rules.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) { 
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
    foreach ($rule in $rules) {
        if (($null -ne $rule.ForwardTo) -or ($null -ne $rule.ForwardAsAttachmentTo)) {
        
        }
    }
}

Now we are looking at a rule object that has a forwarder of some sort. It’s time to let the end user know. Next, we will create a PowerShell Custom Object. Almost every get command I have come across has produced one of these objects.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) { 
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
    foreach ($rule in $rules) {
        if (($null -ne $rule.ForwardTo) -or ($null -ne $rule.ForwardAsAttachmentTo)) {
            [pscustomobject][ordered]@{

            }
        }
    }
}

The object is ready for us. It’s time to fill it in with useful information. We need the mailbox name, the rule name, the rule’s id, if it’s enabled, and finally the forwarder information. The forwarder information is broken up into two. The “ForwardTo” and the “ForwardAsAttachmentTo”. The first forwards the email to a person. The second wraps up the email into an attachment and sends it to the person. We need to see both.

These items are arrays of email addresses and references. If the forwarder points to an external email address it will contain the @ symbol like most email addresses do. If the forwarder points to an internal address like bob in accounting, then it will not have an @ symbol unless told otherwise. This is useful. We can use a where object to pull out the lines with an @ symbol.

$ForwarderRules = foreach ($Mailbox in $Mailboxes) {
    $rules = Get-InboxRule -mailbox $Mailbox.Alias
    foreach ($rule in $rules) {
        if (($null -ne $rule.ForwardTo) -or ($null -ne $rule.ForwardAsAttachmentTo)) {
            [pscustomobject][ordered]@{
                Username = $Mailbox.Alias
                Rule = $Rule.name
                ID = $Rule.RuleIdentity
                Enabled = $rule.enabled
                ForwardTo = $rule.ForwardTo | where-object {$_ -like "*@*"}
                ForwardAsAttachmentTo = $rule.ForwardAsAttachmentTo | where-object {$_ -like "*@*"}
            }
        }
    }
}

Sorting the Sorted Information

Now it’s time to sort the sorted information. First why? Why not add it to the loop above? Two reasons. First is the time it takes to process. Second, I want to run $ForwarderRules to get information and I want to run the next line of code to see the more focused information. I like having options. Now we will take the forwarder rules we created and filter out the nulls of the forwarders. Finally, we want to display the information.

$ats = $ForwarderRules | where-object {($null -ne $_.ForwardTo) -or ($null -ne $_.ForwardAsAttachmentTo)}
$ats

Finally, you have all the email addresses and rules that have a forwarder that forwards to a real email address. You can run through each one and audit them for security.

Future Reading

Images created with Mid Journey AI

Test Microsoft Service Connections

Test Microsoft Service Connections

This past week I have had multiple terminals up with different clients on different terminals connected to different Microsoft services. I quickly realized that I needed to know if I was already connected or not to each Microsoft service. I knew that Get-PPSession was a key to this, but what surprised me was azure and msols didn’t have a PPSession to review. I use 4 different connections on a daily basis. The Msol, Exchange Online, Compliance, and Azure AD. Here is how you can quickly test each service to see if it’s connected.

Msol Service

With Msol we want to test a command to see if it works. We output that command to null. The command I found to be the quickest for this is Get-MsolDomain. Once we run the command we check to see how successful that command was with $?. I like to put things into arrays for future use. So, I dropped it into a $Results and then present those display those results.

Get-MsolDomain -Erroraction SilentlyContinue | out-null
$Results = $?
$Results

Exchange Online

With Exchange online, we can use the Get-PSSession. We search the connection uri for outlook.office365. We also look at the state to be opened. We then ask if how many sessions there are. if it’s greater than 0, then we are good to go. Believe it or not, this is a one-liner.

(Get-PSSession | Where-object { ($_.ConnectionURI -like "*outlook.office365.com*") -and ($_.State -like "Opened")}).count -gt 0

Compliance

With Compliance, it’s similar to the exchange online. We are using the Get-PSSession again. This time we are looking for the word compliance and the state open as well. Once again, this can be a one-liner.

(Get-PSSession | where-object { ($_.ConnectionUri -like "*compliance*") -and ($_.State -like "Opened") } ).count -gt 0 

Azure AD

Finally, we come to the Azure AD, Just like the Msol, we have to check it using a command. I have seen people use the Get-AzureADTenantDetail commandlet. So we will use that commandlet and pipe it into out-null. Then we confirm if it worked with the $? command.

Get-AzureADTenantDetail -ErrorAction SilentlyContinue | out-null
$Results = $?
$Results

The Function

Let’s combine it together into a function.

function Test-SHDo365ServiceConnection {
    <#
    .SYNOPSIS
        Tests to see if you are connected to verious of services. 
    .DESCRIPTION
        Tests to see if you are connected to Microsoft Online Services, Exhcange Online, Complience Center, and Azure AD.
    .PARAMETER MsolService
        [switch] - Connects to Microsoft Online Services
    .PARAMETER ExchangeOnline
        [switch] - Connects to Exchange Online
    .PARAMETER Complience
        [switch] - Connects to complience services
    .PARAMETER AzureAD
        [switch] - Connects to azure AD
    .EXAMPLE
        PS> Test-SHDo365ServiceConnection -MsolService 
        Gives a trur or False statement on weither connected or not. 
    .OUTPUTS
        [pscustomobject]
    .NOTES
        Author: David Bolding

    .LINK
        https://github.com/rndadhdman/PS_Super_Helpdesk
    #>
    [cmdletbinding()]
    param (
        [switch]$MsolService,
        [switch]$ExchangeOnline,
        [Switch]$Complience,
        [switch]$AzureAD
    )
    if ($MsolService) {
        Get-MsolDomain -Erroraction SilentlyContinue | out-null; $Results = $?
        [pscustomobject]@{
            Service   = "MsolService"
            Connected = $Results
        }
    }
    if ($ExchangeOnline) {
        $Results = (Get-PSSession | Where-object { ($_.ConnectionUri -like "*outlook.office365.com*") -and ($_.State -like "Opened")}).count -gt 0
        [pscustomobject]@{
            Service   = "ExchangeOnline"
            Connected = $Results
        }
    }
    if ($Complience) {
        $Results = (Get-PSSession | where-object { ($_.ConnectionUri -like "*compliance*") -and ($_.State -like "Opened") } ).count -gt 0 
        [pscustomobject]@{
            Service   = "Complience"
            Connected = $Results
        }
    }
    if ($AzureAD) {
        Get-AzureADTenantDetail -ErrorAction SilentlyContinue | out-null; $Results = $?
        [pscustomobject]@{
            Service   = "AzureAD"
            Connected = $Results
        }
    }
} #Review
Disable Shared Mailbox Sign-Ins

Disable Shared Mailbox Sign-Ins

As part of the exchange online hardening process, one must disable the sign-in ability of shared mailboxes. This process is simple. You will need to exchange Online and the MS Online modules. First, we will grab all the shared mailboxes using the exchange online. Then using the user principal name, we grab the user info from MS Online module. From there we search for each MS user who isn’t blocked and set them to be blocked. Finally, we report back on the shared mailboxes that are blocked. Now we have the concept, let us break it down a little more. FYI, this can be done in a single line, but that would make it confusing. So, we will make it into a more functional script. Let us start up our VS code and get started.

First, connect to our need services with connect-exchangeonline and connect-msolservice. We live in the world of MFA, so I will assume you will complete the MFA process for these two commands.

Next, we will grab all the shared mailboxes by filtering the get-mailbox command. We are looking for the Recipient Type Details to be equal to the shared mailbox.

 $SharedMailboxes = Get-Mailbox -Filter { recipienttypedetails -eq "SharedMailbox" }

Then we will grab the MS user information from each Shared Mailbox. Once we get the MS user information we only want the user principal name and the block credential.

$Accounts = $SharedMailbox | Get-MsolUser | Select-Object userprincipalname, blockcredential

Now loop through the accounts checking each account to see if the credentials are blocked. If it isn’t, aka false, then we set the block credentials to true with set-MsolUser.

if ($Account.blockcredential -eq $False) {
     Set-MsolUser -UserPrincipalName $Account.Userprincipalname -BlockCredential $true        
 }

Next, we confirm every shared mailbox is set to true for blocking credentials. We are basically repeating the above command again as a single line. We use Get-Mailbox with the filter for the recipient type details to be equal shared mailbox. Pipe that into Get-MsolUser and then select the user principal name and block credentials.

Get-Mailbox -Filter { recipienttypedetails -eq "SharedMailbox" } | Get-MsolUser | Select-Object userprincipalname, blockcredential

Finally, we use our disconnects to disconnect from exchange and MS online. Let’s combine it all together and see how the script unfolds.

The Script

Connect-ExchangeOnline
Connect-MsolService
$SharedMailboxes = Get-Mailbox -Filter { recipienttypedetails -eq "SharedMailbox" }
Foreach ($SharedMailbox in $SharedMailboxes) {
     $Accounts = $SharedMailbox | Get-MsolUser | Select-Object userprincipalname, blockcredential
     foreach ($Account in $Accounts) {
          if ($Account.blockcredential -eq $False) {
                    Write-Host "Blocking $($Account.Userprincipalname)"
                    Set-MsolUser -UserPrincipalName $Account.Userprincipalname -BlockCredential $true        
          }
     }
}
Get-Mailbox -Filter { recipienttypedetails -eq "SharedMailbox" } | Get-MsolUser | Select-Object userprincipalname, blockcredential
        
Disconnect-ExchangeOnline
Get-PSSession | Remove-PSSession -Confirm:$false