by David | Jul 10, 2023 | Information Technology, PowerShell
Last week we discussed sending emails with Graph API. You can read about it here. Today we will be taking that script and making it so it can be automated. On the backend, you will need to create an Azure App. You can read about how to do that here. The following code only works in Powershell 7 and above. Automating with Graph API works best in PowerShell 7. You will need to set up your App with Users.Read.All and Mail.Send as the minimal. levels.
The Script
import-module Microsoft.Graph.Users
Import-module Microsoft.Graph.Users.Actions
$EmailToSend = "A Cloud Email @ your domain"
$EmailToReceive = "Any Email"
$AppID = "This is your App ID"
$SecuredPassword = "This is your Password"
$tenantID = "This is your tenant ID"
$SecuredPasswordPassword = ConvertTo-SecureString -String $SecuredPassword -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $SecuredPasswordPassword
Connect-MgGraph -TenantId $tenantID -ClientSecretCredential $ClientSecretCredential
#Connect-MgGraph -Scopes "User.Read.All, Mail.Send"
$users = Get-MgUser -filter "accountenabled eq false"
$ReturnString = ""
foreach ($user in $users) {
if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
[pscustomobject][ordered]@{
UPN = $user.UserPrincipalName
Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
}
$ReturnString = $ReturnString + "$($user.UserPrincipalName): $((Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", ")`n"
}
}
$body = @"
<html>
<header>Licenses</header>
<body>
<center>
<h1>Disabled Users</h1>
<h2>With Licenses</h2>
</center>
$ReturnString
</body>
</html>
"@
$params = @{
message = @{
subject = "Disabled Users with Licenses"
body = @{
contentType = "HTML"
content = $body
}
toRecipients = @(
@{
emailAddress = @{
address = $EmailToReceive
}
}
)
}
saveToSentItems = "false"
}
# A UPN can also be used as -UserId.
Send-MgUserMail -UserId $EmailToSend -BodyParameter $params
The Breakdown
This script is the same as last week’s except for how it connects and how you feed the email addresses. We are using the Client Secret Credential flag, which is only available in Powershell 7, to trigger the connect command. You need some basic information first. This information will allow Automating with Graph API to work.
$AppID = "This is your App ID"
$SecuredPassword = "This is your Password"
$tenantID = "This is your tenant ID"
The App is the application ID from the azure app you created. the tenant ID is also the tenant ID of the azure app you created. Remember, I stated to keep the secret key value. This is where you will use it. Place it in the Secure Password area. Next, we need to convert this information into a secure object.
$SecuredPasswordPassword = ConvertTo-SecureString -String $SecuredPassword -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $SecuredPasswordPassword
Now, we need to convert the plain text to a secure string. We do this with the convertto-securestring command. We enter the string and force it with the force tag. Once we have done that, we want to create a credential object. We use the new-object command to create an automation pscredential object. We feed it the appID and the password we created above. This gives us the ps object that we will use for the next part.
Connect-MgGraph -TenantId $tenantID -ClientSecretCredential $ClientSecretCredential
Using the connect-mggraph command we connect to our tenant and pass the app id and password as a single object. This will connect us directly to Graph API. Later we will run this script through the task scheduler. The remainder of the script will stay the same. Finally, we supply the email addresses. Automating with Graph API couldn’t be easier. So Enjoy!
Additional Links
by David | Jul 5, 2023 | Information Technology, PowerShell
Last week we spoke about finding disabled users with licensing using PowerShell and graph API. Today, we will be expanding from that blog. We are going to send the results ourselves. Next week, we will create this into an automation using application rights and azure apps. However, today we will send Emails with Graph API and Powershell.
The Script
import-module Microsoft.Graph.Users
Import-module Microsoft.Graph.Users.Actions
Connect-MgGraph -Scopes "User.Read.All, Mail.Send"
$users = Get-MgUser -filter "accountenabled eq false"
$ReturnString = ""
foreach ($user in $users) {
if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
[pscustomobject][ordered]@{
UPN = $user.UserPrincipalName
Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
}
$ReturnString = $ReturnString + "$($user.UserPrincipalName): $((Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", ")`n"
}
}
$EmailSend = Read-Host "Email Address to send (Cloud Only)"
$Emailreceive = Read-Host "Email Address to Receive"
$body = @"
<html>
<header>Licenses</header>
<body>
<center>
<h1>Disabled Users</h1>
<h2>With Licenses</h2>
</center>
$ReturnString
</body>
</html>
"@
$params = @{
message = @{
subject = "Disabled Users with Licenses"
body = @{
contentType = "HTML"
content = $body
}
toRecipients = @(
@{
emailAddress = @{
address = $Emailreceive
}
}
)
}
saveToSentItems = "false"
}
# A UPN can also be used as -UserId.
Send-MgUserMail -UserId $EmailSend -BodyParameter $params
The Breakdown
The same
Like last week, we are using the Microsoft Graph PowerShell module. The first part of this code is the same as last week. So you can take a good read on that one. The only difference is our scope. We are adding mail.send. Mail.Send allows us to send emails from cloud users. It will not work with inactive, soft-deleted, or on-premise-hosted devices. Thus the connect-mggraph will look like the below
Connect-MgGraph -Scopes "User.Read.All, Mail.Send"
The only other thing we have added to the original script is a return string. We initialize the return string with a $returnstring = “” and then we build the string out. Using the same as before, we grab the SKU part number. Finally, we use the join. The difference is we wrap the command in a bubble, $(), for our string. Then we put the sting, the new information, and a line break, `n, into the string.
$ReturnString = $ReturnString + "$($user.UserPrincipalName): $((Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", ")`n"
Emails with Graph API
The first thing we want to know is Who we are sending the email to and Who is sending the email. The one sending the email has limitations. First, it can’t be an inactive user. The user can’t be in a soft-deleted state. Finally, it has to be hosted in the cloud. The person we are sending to has to have an email box.
The next part is where we create the email we are going to send. Remember that returnstring we made a few moments ago, it’s time to use that. We are using a here string. Here strings allows large string data like an HTML page, to be placed into a string. Here strings are set apart using the @ symbol. Take a look at the $body below.
$body = @"
<html>
<header>Licenses</header>
<body>
<center>
<h1>Disabled Users</h1>
<h2>With Licenses</h2>
</center>
$ReturnString
</body>
</html>
"@
Params
Please note that some PowerShell ide does not like the tabbing inside a here-string. The next part is the parameters of the email system. We are sending a message. Additional documentation can be found here. We are going to use the following tags, subject, body, to recipient, and save to sent items. All of these items are setup as a Json file as the API uses Json as well.
- Subject: What is the subject of the email
- Body: body contains the content type and the content. Here is where we will be using our $body.
- To Recipients: This is where the email addresses will go. We set up an email address and have it as an array.
- Save to Sent Items: Finally, we determine if we want this item to stay in our sent items.
$params = @{
message = @{
subject = "Disabled Users with Licenses"
body = @{
contentType = "HTML"
content = $body
}
toRecipients = @(
@{
emailAddress = @{
address = $Emailreceive
}
}
)
}
saveToSentItems = "false"
}
Finally, we use the send-mgusermail command. This is where we use the send email. It will be the UPN of the target user we want to send email from. The body parameter will be the parameters we just built. Once you do this, you will see the email come in accordingly. That’s how you can send Emails with Graph API.
by David | Jun 26, 2023 | Information Technology, PowerShell
Microsoft licensing can cost a lot of money. It’s not logical to have a disabled account have licenses. Some licenses can cost up to $25 USD a month. So if you have 4 of these disabled accounts with licenses, that could easily be 100 per month. Let us Find Disabled Users with Graph API using PowerShell and find which ones have licenses.
Today’s post will be using the Microsoft.graph.users module. We can connect to graph API via the API, or we can use the Powershell module. In previous posts, I have shown how to connect to the API and pull information. This is still one of the best methods to use as the Graph API module has no way to search for things like shared mailboxes. I will cover that in a later post. So, let’s dive into the graph users module. You can read the documentation here.
The script
import-module Microsoft.Graph.Users
Connect-MgGraph -Scopes "User.Read.All"
$users = Get-MgUser -filter "accountenabled eq false"
foreach ($user in $users) {
if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
[pscustomobject][ordered]@{
UPN = $user.UserPrincipalName
Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
}
}
}
Disconnect-mggraph
The Breakdown
First of all, look at how little code there is compared to the previous post. The Connect-MGGraph removes so much back end work. When you run this command it will prompt you to grant it permissions. Once you disconnect, those permissions disappear. if you have a global admin account, if you don’t put in a scope, you gain access to everything. Which is super nice and scary. I prefer to have scope so I don’t break things.
If you don’t have the “Microsoft.Graph.Users” module, install it. You can install it by using the install-module commandlet.
Connect-MgGraph -Scopes "User.Read.All"
Like I said before, The connect-MGgraph command allows you to do scopes. This is very important. Back in the day we used msol. When we connected we had full control unless we limited our accounts which caused issues. In this case we are creating a temporary azure app. See the previous post about how to make an azure app. You will see how time saver this command can be. That azure app will have the scopes that you give it. If you don’t give it a scope, it has your rights. So, if you are a global admin, you can do damage on accident. It’s nice not to be in the scary ages anymore. So give it scope. In this case we are using User.Read.All as our scope. The User.Read.All permissions will help us Find Disabled Users.
$users = Get-MgUser -filter "accountenabled eq false"
The next part is where we grab all the disabled accounts. Using the Get-MgUser commandlet, we trigger the filter option. We want only accounts that are not enabled. thus the account enabled is equal to false. Very simple idea. If you run users[1] you can see the items you can search with. I do not suggest searching for anything to crazy.
The loop
Now we have all the disabled accounts we want to find the Licensed ones. We need to create a for each loop. Foreach loops are a staple for data controls. Without it… it’s just a pain. As we loop, we want to find the ones with a licenses. We need the user id from the account. So the best thing to do is do an if statement.
foreach ($user in $users) {
if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
#Do Something
}
}
In this if statement we pull the licenses using the Get-MgUserLicenseDetail with the users id. If there is nothing that comes from this command it will return a null. So we test null against the command. It’s slightly faster than testing the command against null. Every user inside this if statement that is true will have a licensing. We want to display that information.
[pscustomobject][ordered]@{
UPN = $user.UserPrincipalName
Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
}
Here we create a PowerShell custom object. We want to display the User Principal Name, also known as, the sign name. We do this by using the $user from the foreach loop and just tag the user principal name. Next, we want to display all the licenses. The licenses come as an array. For example, my test account has 3 licenses. I want all that as a string. So, we use the Get-mguserlicensedetail command. We pull out the SKU part number. Then we do some array magic. The -join “, ” converts the array into a string. At the end of each line it adds a “,” and a space. Which makes it easier to read. The cool part about this is if it’s just one license, we it will not add the “, ” to the end. This makes it super readable.
One catch though, thanks to how Microsoft likes to hide things, the SKU is going to be coded. For example, it might say spe_f1. which means it’s an F1 license for Microsoft office. while of1 could mean the same thing but purchased from a different location. I use to try to keep a living list, however, these sku change daily and finding them will be hard. This is where Google is your friend.
Finally, we disconnect from graph API. We don’t want that app to stay there do we? Yeah, we disconnect like good admins we are. Once you are disconnected, you can review all the information this script provided. I am not a big fan of automating the removal of licenses through this method because many times other admins will licenses disabled accounts to keep email alive as a shared mailbox or other oddities. Right now, graph API poweshell module just doesn’t work with shared mailboxes. It is on the workbooks though.
Now go, Find Disabled Users With Graph API and Powershell, and Enjoy your day.
by David | May 5, 2023 | Exchange, Information Technology, PowerShell
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
by David | Apr 28, 2023 | Information Technology, PowerShell
Windows 10 and 11 have a unique feature called telemetry. This feature allows Microsoft to track your device. For security reasons, it’s best to disable this, however, for a home level, this feature is useful. So, let’s Set Telemetry with Powershell.
Registry Keys and Services
There are 4 registry keys that can do this.
- HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection\AllowTelemetry
- HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection\AllowDeviceNameInTelemetry
- HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection\AllowTelemetry
- HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies\DataCollection\AllowTelemetry
Along with the registry keys you will need to work with a service called “Diagtrack“. This service while active, can track your computer. To disable telemetry, we must disable diagtrack from the startup and we can do that with Powershell.
Disable Telemetry
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowTelemetry" -Type DWord -Value 0
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowDeviceNameInTelemetry" -Type DWord -Value 0
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" -Name "AllowTelemetry" -Type DWord -Value 0
Set-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies\DataCollection" -Name "AllowTelemetry" -Type DWord -Value 0
Get-Service -Name "DiagTrack" | Stop-Service -NoWait -Force
Get-Service -Name "DiagTrack" | set-service -StartupType Disabled
Here we are adding different values. We are setting our 4 registry keys to 0. Then we stop the diagtrack server. Then we set the startup to disabled, This way the service can’t be restarted.
Doing this ensures that the service doesn’t come back to life and if it does, it has no idea what to do.
Enable Telemetry
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowTelemetry"
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowDeviceNameInTelemetry"
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" -Name "AllowTelemetry"
Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies\DataCollection" -Name "AllowTelemetry"
Get-Service -Name "DiagTrack" | set-service -StartupType Manual
Get-Service -Name "DiagTrack" | start-service
To enable telemetry, we are simply doing the opposite. We remove the registry keys. Then we enable the services. Finally, we start the service.
There you have it, We have Set telemetry with Powershell
Additional Reading
by David | Apr 14, 2023 | Exchange, Information Technology, PowerShell
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.
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.