-
Notifications
You must be signed in to change notification settings - Fork 342
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into CalLogAugTweaks
- Loading branch information
Showing
16 changed files
with
766 additions
and
25 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
|
||
<# | ||
.NOTES | ||
Name: Measure-EmailDelayInMTL.ps1 | ||
Requires: User Rights | ||
Major Release History: | ||
08/05/2024 - Initial Release | ||
.SYNOPSIS | ||
Generates a report of the maximum message delay for all messages in an Message Tracking Log. | ||
.DESCRIPTION | ||
Gather message tracking log details of all message to / from a given recipient for a given time range. | ||
Recommend using Start-HistoricalSearch in EXO. | ||
The script will provide an output of all unique message ids with the following information: | ||
MessageID | ||
Time Sent | ||
Total Time in transit | ||
Useful for determining if a "slow" message was a one off or a pattern. | ||
.PARAMETER MTLFile | ||
MTL File to process. | ||
.PARAMETER ReportPath | ||
Folder path for the output file. | ||
.OUTPUTS | ||
CSV File with the following information. | ||
MessageID ID of the Message | ||
TimeSent First time we see the message in the MTL | ||
TimeReceived Last delivery time in the MTL | ||
MessageDelay How long before the message was delivered | ||
Default Output File: | ||
$PSScriptRoot\MTL_report.csv | ||
.EXAMPLE | ||
.\Measure-EmailDelayInMTL -MTLPath C:\temp\MyMtl.csv | ||
Generates a report from the MyMtl.csv file. | ||
#> | ||
|
||
[CmdletBinding()] | ||
param ( | ||
[Parameter()] | ||
[string] | ||
$MTLFile, | ||
[Parameter()] | ||
[string] | ||
$ReportPath = $PSScriptRoot | ||
) | ||
|
||
. $PSScriptRoot\..\Shared\ScriptUpdateFunctions\Test-ScriptVersion.ps1 | ||
|
||
function Test-CSVData { | ||
param( | ||
[array]$CSV, | ||
[array]$ColumnsToCheck | ||
) | ||
|
||
# Check to make sure we have data in the CSV | ||
if (($null -eq $CSV) -or !($CSV.count -gt 0)) { | ||
Write-Error "Provided CSV null or empty" -ErrorAction Stop | ||
return $false | ||
} | ||
|
||
# Read thru the data and make sure we have the needed columns | ||
$ColumnHeaders = ($CSV | Get-Member -MemberType NoteProperty).Name | ||
foreach ( $ColumnToCheck in $ColumnsToCheck) { | ||
if (!($ColumnHeaders.ToLower().Contains($ColumnToCheck.ToLower())) ) { | ||
return $false | ||
} | ||
} | ||
return $true | ||
} | ||
|
||
if (Test-ScriptVersion -AutoUpdate) { | ||
# Update was downloaded, so stop here. | ||
Write-Host "Script was updated. Please rerun the command." | ||
return | ||
} | ||
|
||
# make sure out output variable is null | ||
$output = $Null | ||
|
||
# Test for the provided file and load it. | ||
# Need to make sure the MTL file is there and if so load it. | ||
# Straight from EXO it will be in Unicode. Onprem and modified files are not. | ||
# First verify the file | ||
if (!(Test-Path $MTLFile)) { | ||
Write-Error "Unable to find the specified file" -ErrorAction Stop | ||
} | ||
|
||
# Make sure the path for the output is good | ||
if (!(Test-Path $ReportPath)) { | ||
Write-Error ("Unable to find report path " + $ReportPath) | ||
} | ||
|
||
# Try to load the file with Unicode since we need to start somewhere. | ||
$mtl = Import-Csv $MTLFile -Encoding Unicode | ||
|
||
# If it is null then we need to try without Unicode | ||
if ($null -eq $mtl) { | ||
Write-Host "Failed to Load as Unicode; trying normal load" | ||
$mtl = Import-Csv $MTLFile | ||
# If we still have nothing then log an error and fail | ||
if ($null -eq $mtl) { | ||
Write-Error "Failed to load CSV" -ErrorAction Stop | ||
} | ||
# Need to know that we loaded without Unicode. | ||
else { | ||
Write-Host "Loaded CSV without Unicode" | ||
} | ||
} else { | ||
Write-Host "Loaded MTL with Unicode" | ||
} | ||
|
||
# Detecting if this is an onprem MTL | ||
if (Test-CSVData -CSV $mtl -ColumnsToCheck "eventid", "source", "messageId", "timestamp") { | ||
Write-Host "On Prem message trace detected; Updating property names" | ||
$mtl = $mtl | Select-Object -Property @{N = "date_time_utc"; E = { $_.timestamp } }, @{N = "message_id"; E = { $_.messageID } }, source, @{N = "event_id"; E = { $_.EventId } } | ||
} | ||
|
||
# Making sure the MTL contains the fields we want. | ||
if (!(Test-CSVData -CSV $mtl -ColumnsToCheck "event_id", "source", "message_id", "date_time_utc")) { | ||
Write-Error "MTL is missing one or more required fields." -ErrorAction Stop | ||
} | ||
|
||
# Converting our strings into [DateTime] | ||
Write-Host "Converting date_time_utc values" | ||
for ($i = 0; $i -lt $mtl.Count; $i++) { | ||
$mtl[$i].date_time_utc = Get-Date($mtl[$i].date_time_utc) | ||
} | ||
|
||
# get all of the unique message IDs in the file. | ||
[array]$uniqueMessageIDs = $mtl | Select-Object -ExpandProperty message_id | Sort-Object | Get-Unique | ||
|
||
if ($uniqueMessageIDs.count -eq 0) { | ||
Write-Error "No Unique MessageIDs found in data." | ||
} | ||
|
||
# Carve the data up into smaller collections | ||
# Most of what is in the MTL we don't need | ||
$SMTPReceive = $mtl | Where-Object { ($_.event_id -eq 'Receive') -and ($_.source -eq 'SMTP') } | ||
$StoreDeliver = $mtl | Where-Object { ($_.event_id -eq 'Deliver') -and ($_.source -eq 'StoreDriver') } | ||
$SMTPDeliver = $mtl | Where-Object { ($_.event_id -eq 'SendExternal') -and ($_.source -eq 'SMTP') } | ||
|
||
# Loop thru each unique messageID | ||
foreach ($id in $uniqueMessageIDs) { | ||
|
||
# make sure we aren't carrying anything over from the previous foreach. | ||
$AllSentTimes = $Null | ||
$AllStoreDeliverTimes = $Null | ||
$AllRemoteDeliverTimes = $Null | ||
|
||
# extract the times for a message ID ... there can be more than one of each of these. | ||
[array]$AllSentTimes = ($SMTPReceive | Where-Object { ($_.message_id -eq $id) }).date_time_utc | ||
[array]$AllStoreDeliverTimes = ($StoreDeliver | Where-Object { ($_.message_id -eq $id) }).date_time_utc | ||
[array]$AllRemoteDeliverTimes = ($SMTPDeliver | Where-Object { ($_.message_id -eq $id) }).date_time_utc | ||
|
||
# If we didn't find any sent information then drop the messageID | ||
if ($AllSentTimes.count -eq 0) { | ||
Write-Warning ($id.ToString() + " unable to find sent time. Discarding messageID") | ||
continue | ||
} | ||
|
||
# If we didn't find any delivery information then drop the messageID | ||
if ($AllStoreDeliverTimes.count -eq 0 -and $AllRemoteDeliverTimes.count -eq 0) { | ||
Write-Warning ($id + " not able to find delivery time in MTL. Discarding messageID") | ||
continue | ||
} | ||
|
||
# Get the newest time sent that we found | ||
$SortedTimeSent = Get-Date ($AllSentTimes | Sort-Object | Select-Object -First 1) | ||
|
||
# Combine all of the delivery times and grab the newest one | ||
$SortedTimeDelivered = (($AllStoreDeliverTimes + $AllRemoteDeliverTimes) | Sort-Object | Select-Object -Last 1) | ||
|
||
# Build report object | ||
[array]$output += [PSCustomObject]@{ | ||
MessageID = $id | ||
TimeSent = $SortedTimeSent | ||
TimeReceived = $SortedTimeDelivered | ||
MessageDelay = $SortedTimeDelivered - $SortedTimeSent | ||
} | ||
} | ||
|
||
# Make sure we have something to output | ||
if ($null -eq $output) { | ||
Write-Error "No output generated" -ErrorAction Stop | ||
} else { | ||
|
||
# Export the data to the output file | ||
$outputFile = (Join-Path -Path $ReportPath -ChildPath ("MTL_Latency_Report_" + (Get-Date -Format FileDateTime).ToString() + ".csv")) | ||
$output | Sort-Object -Property MessageDelay -Descending | Export-Csv -IncludeTypeInformation:$false -Path $outputFile | ||
Write-Output ("Report written to file " + $outputFile) | ||
|
||
# Gather general statistical data and output to the screen | ||
$Stats = ($output.MessageDelay.TotalMilliseconds | Measure-Object -Average -Maximum -Minimum) | ||
|
||
$GeneralData = [PSCustomObject]@{ | ||
EmailCount = $Stats.Count | ||
MaximumDelay = [TimeSpan]::FromMilliseconds($Stats.Maximum) | ||
MinimumDelay = [TimeSpan]::FromMilliseconds($Stats.Minimum) | ||
AverageDelay = [TimeSpan]::FromMilliseconds($Stats.Average) | ||
} | ||
|
||
Write-Output $GeneralData | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,22 +2,37 @@ | |
|
||
Download the latest release: [MDOThreatPolicyChecker.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/MDOThreatPolicyChecker.ps1) | ||
|
||
This script checks which Microsoft Defender for Office 365 and Exchange Online Protection threat policies cover a particular user, including anti-malware, anti-phishing, inbound and outbound anti-spam, as well as Safe Attachments and Safe Links policies in case these are licensed for your tenant. In addition, the script can check for threat policies that have inclusion and/or exclusion settings that may be redundant or confusing and lead to missed coverage of users or coverage by an unexpected threat policy. | ||
Use this script to find inconsistencies or redundancies in user membership and policy application of Microsoft Defender for Office 365 and Exchange Online Protection threat policies that lead to missed or unexpected coverage of users by the policy. If issues are found, the script provides guidance on how to resolve them. | ||
|
||
It also includes an option to show all the actions and settings of the policies that apply to a user. | ||
The script also helps you identify which threat policies cover a particular user, including anti-malware, anti-phishing, inbound and outbound anti-spam, as well as Safe Attachments and Safe Links policies in case these are licensed for your tenant. | ||
|
||
## Common Usage | ||
The script uses Exchange Online cmdlets from Exchange Online module and Microsoft.Graph cmdLets from Microsoft.Graph.Authentication, Microsoft.Graph.Groups and Microsoft.Graph.Users modules. | ||
The script can help with such questions as: | ||
|
||
To run the PowerShell Graph cmdlets used in this script, you need only the following modules from the Microsoft.Graph PowerShell SDK: | ||
- Microsoft.Graph.Groups: Contains cmdlets for managing groups, including `Get-MgGroup` and `Get-MgGroupMember`. | ||
- Microsoft.Graph.Users: Includes cmdlets for managing users, such as `Get-MgUser`. | ||
- Microsoft.Graph.Authentication: Required for authentication purposes and to run any cmdlet that interacts with Microsoft Graph. | ||
- Are there confusing policies with conditions that lead to unexpected coverage or coverage gaps? | ||
|
||
- Which threat policies apply to a recipient, **or should have applied** but did not? **No actual detection or Network Message ID needed.** | ||
|
||
- Which actions would be taken on an email for each policy matched? | ||
|
||
The script runs only in Read mode from Exchange Online and Microsoft Graph PowerShell. It does not modify any policies, and only provides actionable guidance for administrators for remediation. | ||
|
||
## Prerequisites | ||
The script uses Powershell cmdlets from the Exchange Online module and from the Microsoft.Graph.Authentication, Microsoft.Graph.Groups, and Microsoft.Graph.Users modules. | ||
|
||
To run the Graph cmdlets used in this script, you only need the following modules from the Microsoft.Graph PowerShell SDK: | ||
|
||
- Microsoft.Graph.Groups: for managing groups, including `Get-MgGroup` and `Get-MgGroupMember`. | ||
|
||
- Microsoft.Graph.Users: for managing users, such as `Get-MgUser`. | ||
|
||
- Microsoft.Graph.Authentication: for authentication purposes and to run any cmdlet that interacts with Microsoft Graph. | ||
|
||
You can find the Microsoft Graph modules in the following link:<br> | ||
https://www.powershellgallery.com/packages/Microsoft.Graph/<br> | ||
|
||
https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation?view=graph-powershell-1.0#installation | ||
|
||
|
||
Here's how you can install the required submodules for the PowerShell Graph SDK cmdlets: | ||
|
||
```powershell | ||
|
@@ -31,12 +46,12 @@ Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser | |
Remember to run these commands in a PowerShell session with the appropriate permissions. The -Scope CurrentUser parameter installs the modules for the current user only, which doesn't require administrative privileges. | ||
|
||
|
||
In the Graph connection you will need the following scopes 'Group.Read.All','User.Read.All'<br> | ||
In the Graph connection, you will need the following scopes 'Group.Read.All','User.Read.All'<br> | ||
```powershell | ||
Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All' | ||
``` | ||
<br><br> | ||
You need as well an Exchange Online session.<br> | ||
You also need an Exchange Online session.<br> | ||
```powershell | ||
Connect-ExchangeOnline | ||
``` | ||
|
@@ -46,16 +61,40 @@ You can find the Exchange module and information in the following links:<br> | |
https://www.powershellgallery.com/packages/ExchangeOnlineManagement | ||
|
||
|
||
## Examples: | ||
To check all threat policies for potentially confusing user inclusion and/or exclusion conditions and print them out for review, run the following:<br> | ||
```powershell | ||
.\MDOThreatPolicyChecker.ps1 | ||
``` | ||
## Parameters and Use Cases: | ||
Run the script without any parameters to review all threat protection policies and to find inconsistencies with user inclusion and/or exclusion conditions: | ||
|
||
To provide a CSV input file with email addresses and see only EOP policies, run the following:<br> | ||
```powershell | ||
.\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv] | ||
``` | ||
!['No Logical inconsistencies found'](img/No-Logical-Inconsistencies.png) | ||
|
||
**Script Output 1: No logical inconsistencies found** message if the policies are configured correctly, and no further corrections are required. | ||
|
||
![Potentially illogical inclusions found.](img/Logical-Inconsistency-Found.png) | ||
|
||
**Script Output 2: Logical inconsistencies found**. Inconsistencies found in the antispam policy named 'Custom antispam policy', and consequent recommendations shown -- illogical inclusions as both users and groups are specified. This policy will only apply to the users who are also members of the specified group. | ||
|
||
- IncludeMDOPolicies | ||
|
||
Add the parameter -IncludeMDOPolicies to view Microsoft Defender for Office 365 Safe Links and Safe Attachments policies: | ||
|
||
![Policies, including MDO.](img/Show-Policies-Including-MDO.png) | ||
|
||
**Script Output 3: Parameters -EmailAddress and -IncludeMDOPoliciesEOP** specified to validate Microsoft Defender for Office 365 Safe Attachments and Safe Links policies, on top of Exchange Online Protection policies. | ||
|
||
- ShowDetailedPolicies | ||
|
||
To see policy details, run the script with the -ShowDetailedPolicies parameter: | ||
|
||
![Show policy actions.](img/Show-Detailed-Policies-1.png) | ||
|
||
![Show policy actions.](img/Show-Detailed-Policies-2.png) | ||
|
||
![Show policy actions.](img/Show-Detailed-Policies-3.png) | ||
|
||
![Show policy actions.](img/Show-Detailed-Policies-4.png) | ||
|
||
**Script Output 4: Policy actions**. Use -ShowDetailedPolicies to see the details and actions for each policy. | ||
|
||
## Additional examples | ||
|
||
To provide multiple email addresses by command line and see only EOP policies, run the following:<br> | ||
```powershell | ||
|
@@ -72,11 +111,6 @@ To provide an email address and see only MDO (Safe Attachment and Safe Links) po | |
.\MDOThreatPolicyChecker.ps1 -EmailAddress [email protected] -OnlyMDOPolicies | ||
``` | ||
|
||
To see the details of the policies applied to mailbox in a CSV file for both EOP and MDO, run the following:<br> | ||
```powershell | ||
.\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv] -IncludeMDOPolicies -ShowDetailedPolicies | ||
``` | ||
|
||
To get all mailboxes in your tenant and print out their EOP and MDO policies, run the following:<br> | ||
```powershell | ||
.\MDOThreatPolicyChecker.ps1 -IncludeMDOPolicies -EmailAddress @(Get-ExOMailbox -ResultSize unlimited | Select-Object -ExpandProperty PrimarySmtpAddress) | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.