From d219d757a3ca68f596b6f59917b3b5b40dda20ee Mon Sep 17 00:00:00 2001
From: Ignacio Serrano <103440830+iserrano76@users.noreply.github.com>
Date: Tue, 2 Jul 2024 17:17:33 +0200
Subject: [PATCH 1/7] COntinuation of #2097
---
.build/cspell-words.txt | 2 +
M365/MDO/MDOThreatPolicyChecker.ps1 | 926 ++++++++++++++++++++++++
docs/M365/MDO/MDOThreatPolicyChecker.md | 94 +++
mkdocs.yml | 2 +
4 files changed, 1024 insertions(+)
create mode 100644 M365/MDO/MDOThreatPolicyChecker.ps1
create mode 100644 docs/M365/MDO/MDOThreatPolicyChecker.md
diff --git a/.build/cspell-words.txt b/.build/cspell-words.txt
index 8bb4f8b2dc..2e5e09aabf 100644
--- a/.build/cspell-words.txt
+++ b/.build/cspell-words.txt
@@ -19,6 +19,7 @@ contoso
CTMM
Datacenter
dcom
+DMARC
Dsamain
DTLS
dumptidset
@@ -29,6 +30,7 @@ EICAR
eicar
Emotet
emsmdb
+Entra
EOMT
Eseback
Eventlog
diff --git a/M365/MDO/MDOThreatPolicyChecker.ps1 b/M365/MDO/MDOThreatPolicyChecker.ps1
new file mode 100644
index 0000000000..4c6998fc3a
--- /dev/null
+++ b/M365/MDO/MDOThreatPolicyChecker.ps1
@@ -0,0 +1,926 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+#Requires -Modules Microsoft.Graph.Authentication
+#Requires -Modules Microsoft.Graph.Users
+#Requires -Modules Microsoft.Graph.Groups
+#Requires -Modules ExchangeOnlineManagement -Version 3.0.0
+
+<#
+.SYNOPSIS
+Evaluates user coverage and potential redundancies in Microsoft Defender for Office 365 and Exchange Online Protection threat policies, including anti-malware, anti-phishing, and anti-spam policies, as well as Safe Attachments and Safe Links policies if licensed.
+
+.DESCRIPTION
+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.
+
+.PARAMETER CsvFilePath
+ Allows you to specify a CSV file with a list of email addresses to check.
+.PARAMETER EmailAddress
+ Allows you to specify email address or multiple addresses separated by commas.
+.PARAMETER IncludeMDOPolicies
+ Checks both EOP and MDO (Safe Attachment and Safe Links) policies for user(s) specified in the CSV file or EmailAddress parameter.
+.PARAMETER OnlyMDOPolicies
+ Checks only MDO (Safe Attachment and Safe Links) policies for user(s) specified in the CSV file or EmailAddress parameter.
+.PARAMETER ShowDetailedPolicies
+ In addition to the policy applied, show any policy details that are set to True, On, or not blank.
+.PARAMETER SkipConnectionCheck
+ Skips connection check for Graph and Exchange Online.
+.PARAMETER SkipVersionCheck
+ Skips the version check of the script.
+.PARAMETER ScriptUpdateOnly
+ Just updates script version to latest one.
+
+.EXAMPLE
+ .\MDOThreatPolicyChecker.ps1
+ To check all threat policies for potentially confusing user inclusion and/or exclusion conditions and print them out for review.
+
+.EXAMPLE
+ .\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv]
+ To provide a CSV input file with email addresses and see only EOP policies.
+
+.EXAMPLE
+ .\MDOThreatPolicyChecker.ps1 -EmailAddress user1@contoso.com,user2@fabrikam.com
+ To provide multiple email addresses by command line and see only EOP policies.
+
+.EXAMPLE
+ .\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv] -IncludeMDOPolicies
+ To provide a CSV input file with email addresses and see both EOP and MDO policies.
+
+.EXAMPLE
+ .\MDOThreatPolicyChecker.ps1 -EmailAddress user1@contoso.com -OnlyMDOPolicies
+ To provide an email address and see only MDO (Safe Attachment and Safe Links) policies.
+#>
+
+[CmdletBinding(DefaultParameterSetName = 'AppliedTenant')]
+param(
+ [ValidateScript({ Test-Path $_ -PathType Leaf })]
+ [Parameter(Mandatory = $true, ParameterSetName = 'AppliedCsv')]
+ [Parameter(Mandatory = $true, ParameterSetName = 'AppliedMDOCsv')]
+ [string]$CsvFilePath,
+
+ [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'AppliedEmail')]
+ [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'AppliedMDOEmail')]
+ [string[]]$EmailAddress,
+
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedCsv')]
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedEmail')]
+ [switch]$IncludeMDOPolicies,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'AppliedMDOCsv')]
+ [Parameter(Mandatory = $true, ParameterSetName = 'AppliedMDOEmail')]
+ [switch]$OnlyMDOPolicies,
+
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedCsv')]
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedEmail')]
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedMDOCsv')]
+ [Parameter(Mandatory = $false, ParameterSetName = 'AppliedMDOEmail')]
+ [switch]$ShowDetailedPolicies,
+
+ [Parameter(Mandatory = $false)]
+ [switch]$SkipConnectionCheck,
+
+ [Parameter(Mandatory = $false)]
+ [switch]$SkipVersionCheck,
+
+ [Parameter(Mandatory = $true, ParameterSetName = "ScriptUpdateOnly")]
+ [switch]$ScriptUpdateOnly
+)
+
+begin {
+
+ . $PSScriptRoot\..\..\Shared\ScriptUpdateFunctions\Test-ScriptVersion.ps1
+ . $PSScriptRoot\..\..\Shared\LoggerFunctions.ps1
+ . $PSScriptRoot\..\..\Shared\OutputOverrides\Write-Verbose.ps1
+ . $PSScriptRoot\..\..\Shared\OutputOverrides\Write-Warning.ps1
+ . $PSScriptRoot\..\..\Shared\OutputOverrides\Write-Host.ps1
+
+ # Cache to reduce calls to Get-MgGroup
+ $groupCache = @{}
+ # Cache of members to reduce number of calls to Get-MgGroupMember
+ $memberCache = @{}
+
+ function Get-GroupObjectId {
+ [OutputType([string])]
+ param(
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [MailAddress]$GroupEmail
+ )
+
+ $stGroupEmail = $GroupEmail.ToString()
+ # Check the cache first
+ Write-Verbose "Looking Group $stGroupEmail in cache"
+ if ($groupCache.ContainsKey($stGroupEmail)) {
+ Write-Verbose "Group $stGroupEmail found in cache"
+ return $groupCache[$stGroupEmail]
+ }
+
+ # Get the group
+ $group = $null
+ Write-Verbose "Getting $stGroupEmail"
+ try {
+ $group = Get-MgGroup -Filter "mail eq '$stGroupEmail'" -ErrorAction Stop
+ } catch {
+ Write-Host "Error getting group $stGroupEmail`: $_" -ForegroundColor Red
+ return $null
+ }
+
+ if ($group -and $group.id) {
+ if ($group.Id.GetType() -eq [string]) {
+ # Cache the result
+ Write-Verbose "Added to cache Group $stGroupEmail with Id $($group.Id)"
+ $groupCache[$stGroupEmail] = $group.Id
+
+ # Return the Object ID of the group
+ return $group.Id
+ } else {
+ Write-Host "Wrong type for $($group.ToString()): $group.Id.GetType().Name" -ForegroundColor Red
+ return $null
+ }
+ } else {
+ Write-Host "The EmailAddress of group $stGroupEmail was not found" -ForegroundColor Red
+ return $null
+ }
+ }
+
+ # Function to check if an email is in a group
+ function Test-IsInGroup {
+ [OutputType([bool])]
+ param(
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [MailAddress]$Email,
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [string]$GroupObjectId
+ )
+
+ # Check the cache first
+ $stEmail = $Email.ToString()
+ $cacheKey = "$stEmail|$GroupObjectId"
+ Write-Verbose "Looking for $stEmail|$GroupObjectId in cache"
+ if ($memberCache.ContainsKey($cacheKey)) {
+ Write-Verbose "Found $stEmail|$GroupObjectId in cache"
+ return $memberCache[$cacheKey]
+ }
+
+ # Get the group members
+ $groupMembers = $null
+ Write-Verbose "Getting $GroupObjectId"
+ try {
+ $groupMembers = Get-MgGroupMember -GroupId $GroupObjectId -ErrorAction Stop
+ } catch {
+ Write-Host "Error getting group members for $GroupObjectId`: $_" -ForegroundColor Red
+ return $null
+ }
+
+ # Check if the email address is in the group
+ if ($null -ne $groupMembers) {
+ foreach ($member in $groupMembers) {
+ # Check if the member is a user
+ if ($member['@odata.type'] -eq '#microsoft.graph.user') {
+ if ($member.Id) {
+ # Get the user object by Id
+ Write-Verbose "Getting user with Id $($member.Id)"
+ try {
+ $user = Get-MgUser -UserId $member.Id -ErrorAction Stop
+ } catch {
+ Write-Host "Error getting user with Id $($member.Id): $_" -ForegroundColor Red
+ return $null
+ }
+ # Compare the user's email address with the $email parameter
+ if ($user.Mail -eq $Email.ToString()) {
+ # Cache the result
+ $memberCache[$cacheKey] = $true
+ return $true
+ }
+ } else {
+ Write-Host "The user with Id $($member.Id) does not have an email address." -ForegroundColor Red
+ }
+ }
+ # Check if the member is a group
+ elseif ($member['@odata.type'] -eq '#microsoft.graph.group') {
+ Write-Verbose "Nested group $($member.Id)"
+ # Recursive call to check nested groups
+ $isInNestedGroup = Test-IsInGroup -Email $Email -GroupObjectId $member.Id
+ if ($isInNestedGroup) {
+ # Cache the result
+ Write-Verbose "Cache group $cacheKey"
+ $memberCache[$cacheKey] = $true
+ return $true
+ }
+ }
+ }
+ } else {
+ Write-Verbose "The group with Object ID $GroupObjectId does not have any members."
+ }
+
+ # Cache the result
+ $memberCache[$cacheKey] = $false
+ return $false
+ }
+
+ function Test-EmailAddress {
+ [OutputType([MailAddress])]
+ param(
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [string]$EmailAddress,
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [string[]]$AcceptedDomains
+ )
+
+ try {
+ $tempAddress = $null
+ Write-Verbose "Casting $EmailAddress"
+ $tempAddress = [MailAddress]$EmailAddress
+ } catch {
+ Write-Host "The EmailAddress $EmailAddress cannot be validated. Please provide a valid email address." -ForegroundColor Red
+ return $null
+ }
+ $recipient = $null
+ Write-Verbose "Getting $EmailAddress"
+ try {
+ $recipient = Get-EXORecipient $EmailAddress -ErrorAction Stop
+ } catch {
+ Write-Host "Error getting recipient $EmailAddress`: $_" -ForegroundColor Red
+ return $null
+ }
+
+ if ($null -eq $recipient) {
+ Write-Host "$EmailAddress is not a recipient in this tenant" -ForegroundColor Red
+ return $null
+ } else {
+ $domain = $tempAddress.Host
+ Write-Verbose "Checking domain $domain"
+ if ($AcceptedDomains -contains $domain) {
+ Write-Verbose "Verified domain $domain for $tempAddress"
+ return $tempAddress
+ } else {
+ Write-Host "The domain $domain is not an accepted domain in your organization. Please provide a valid email address: $tempAddress " -ForegroundColor Red
+ return $null
+ }
+ }
+ }
+
+ # Function to check rules
+ function Test-Rules {
+ param(
+ [Parameter(Mandatory = $true)]
+ $Rules,
+ [Parameter(Mandatory = $true)]
+ [MailAddress]$Email,
+ [Parameter(Mandatory = $false)]
+ [switch]$Outbound
+ )
+
+ foreach ($rule in $Rules) {
+ $senderOrReceiver = $exceptSenderOrReceiver = $memberOf = $exceptMemberOf = $domainsIs = $exceptIfDomainsIs = $null
+ $emailInRule = $emailExceptionInRule = $groupInRule = $groupExceptionInRule = $domainInRule = $domainExceptionInRule = $false
+
+ if ($Outbound) {
+ Write-Verbose "Checking outbound rule $($rule.Name)"
+ $requestedProperties = 'From', 'ExceptIfFrom', 'FromMemberOf', 'ExceptIfFromMemberOf', 'SenderDomainIs', 'ExceptIfSenderDomainIs'
+ $senderOrReceiver = $rule.From
+ $exceptSenderOrReceiver = $rule.ExceptIfFrom
+ $memberOf = $rule.FromMemberOf
+ $exceptMemberOf = $rule.ExceptIfFromMemberOf
+ $domainsIs = $rule.SenderDomainIs
+ $exceptIfDomainsIs = $rule.ExceptIfSenderDomainIs
+ } else {
+ Write-Verbose "Checking inbound rule $($rule.Name)"
+ $requestedProperties = 'SentTo', 'ExceptIfSentTo', 'SentToMemberOf', 'ExceptIfSentToMemberOf', 'RecipientDomainIs', 'ExceptIfRecipientDomainIs'
+ $senderOrReceiver = $rule.SentTo
+ $exceptSenderOrReceiver = $rule.ExceptIfSentTo
+ $memberOf = $rule.SentToMemberOf
+ $exceptMemberOf = $rule.ExceptIfSentToMemberOf
+ $domainsIs = $rule.RecipientDomainIs
+ $exceptIfDomainsIs = $rule.ExceptIfRecipientDomainIs
+ }
+
+ $Policy.PSObject.Properties | ForEach-Object {
+ if ($requestedProperties -contains $_.Name) {
+ Write-Host "`t`t$($_.Name): $($_.Value)"
+ }
+ }
+ Write-Verbose " "
+
+ if ($senderOrReceiver -and $Email -in $senderOrReceiver) {
+ Write-Verbose "emailInRule"
+ $emailInRule = $true
+ }
+ if ($exceptSenderOrReceiver -and $Email -in $exceptSenderOrReceiver) {
+ Write-Verbose "emailExceptionInRule"
+ $emailExceptionInRule = $true
+ }
+
+ if ($memberOf) {
+ foreach ($groupEmail in $memberOf) {
+ Write-Verbose "Checking member in $groupEmail"
+ $groupObjectId = Get-GroupObjectId -GroupEmail $groupEmail
+ if ([string]::IsNullOrEmpty($groupObjectId)) {
+ Write-Host "The group in $($rule.Name) with email address $groupEmail does not exist." -ForegroundColor Yellow
+ } else {
+ $groupInRule = Test-IsInGroup -Email $Email -GroupObjectId $groupObjectId
+ if ($groupInRule) {
+ Write-Verbose "groupInRule $($Email.ToString()) - $($groupObjectId)"
+ break
+ }
+ }
+ }
+ }
+
+ if ($exceptMemberOf) {
+ foreach ($groupEmail in $exceptMemberOf) {
+ Write-Verbose "Checking member in exception $groupEmail"
+ $groupObjectId = Get-GroupObjectId -GroupEmail $groupEmail
+ if ([string]::IsNullOrEmpty($groupObjectId)) {
+ Write-Host "The group in $($rule.Name) with email address $groupEmail does not exist." -ForegroundColor Yellow
+ } else {
+ $groupExceptionInRule = Test-IsInGroup -Email $Email -GroupObjectId $groupObjectId
+ if ($groupExceptionInRule) {
+ Write-Verbose "groupExceptionInRule $($Email.ToString()) - $($groupObjectId)"
+ break
+ }
+ }
+ }
+ }
+
+ $temp = $Email.Host
+
+ while ($temp.IndexOf(".") -gt 0) {
+ if ($temp -in $domainsIs) {
+ Write-Verbose "domainInRule: $temp"
+ $domainInRule = $true
+ }
+ if ($temp -in $exceptIfDomainsIs) {
+ Write-Verbose "domainExceptionInRule: $temp"
+ $domainExceptionInRule = $true
+ }
+ $temp = $temp.Substring($temp.IndexOf(".") + 1)
+ }
+
+ # Check for explicit inclusion in any user, group, or domain that are not empty, and account for 3 empty inclusions
+ # Also check for any exclusions as user, group, or domain. Nulls don't need to be accounted for and this is an OR condition for exclusions
+ if ((($emailInRule -or (-not $senderOrReceiver)) -and
+ ($domainInRule -or (-not $domainsIs)) -and
+ ($groupInRule -or (-not $memberOf))) -and
+ ($emailInRule -or $domainInRule -or $groupInRule)) {
+ if ((-not $emailExceptionInRule) -and
+ (-not $groupExceptionInRule) -and
+ (-not $domainExceptionInRule)) {
+ Write-Verbose "Return Rule $($rule.Name)"
+ Write-Verbose "emailInRule: $emailInRule domainInRule: $domainInRule groupInRule: $groupInRule "
+ Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
+ return $rule
+ }
+ }
+
+ if (-not $Outbound) {
+ # Check for implicit inclusion (no mailboxes included at all), which is possible for Presets and SA/SL. They are included if not explicitly excluded.
+ if ((-not $senderOrReceiver) -and (-not $domainsIs) -and (-not $memberOf)) {
+ if ((-not $emailExceptionInRule) -and
+ (-not $groupExceptionInRule) -and
+ (-not $domainExceptionInRule)) {
+ Write-Verbose "Return Rule $($rule.Name)"
+ Write-Verbose "senderOrReceiver: $senderOrReceiver domainsIs: $domainsIs memberOf: $memberOf "
+ Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
+ return $rule
+ }
+ }
+ }
+ }
+ return $null
+ }
+
+ function Show-DetailedPolicy {
+ param (
+ [Parameter(Mandatory = $true)]
+ $Policy
+ )
+ Write-Host "`n`tProperties of the policy that are True, On, or not blank:"
+ $excludedProperties = 'Identity', 'Id', 'Name', 'ExchangeVersion', 'DistinguishedName', 'ObjectCategory', 'ObjectClass', 'WhenChanged', 'WhenCreated', `
+ 'WhenChangedUTC', 'WhenCreatedUTC', 'ExchangeObjectId', 'OrganizationalUnitRoot', 'OrganizationId', 'OriginatingServer', 'ObjectState', 'Priority', 'ImmutableId', `
+ 'Description', 'HostedContentFilterPolicy', 'AntiPhishPolicy', 'MalwareFilterPolicy', 'SafeAttachmentPolicy', 'SafeLinksPolicy', 'HostedOutboundSpamFilterPolicy'
+
+ $Policy.PSObject.Properties | ForEach-Object {
+ if ($null -ne $_.Value -and `
+ (($_.Value.GetType() -eq [Boolean] -and $_.Value -eq $true) `
+ -or ($_.Value -ne '{}' -and $_.Value -ne 'Off' -and $_.Value -ne $true -and $_.Value -ne '' -and $excludedProperties -notcontains $_.Name))) {
+ Write-Host "`t`t$($_.Name): $($_.Value)"
+ } else {
+ Write-Verbose "`t`tExcluded property:$($_.Name): $($_.Value)"
+ }
+ }
+ Write-Host " "
+ }
+
+ function Get-Policy {
+ param(
+ $Rule = $null,
+ $PolicyType = $null
+ )
+
+ if ($null -eq $Rule) {
+ if ($PolicyType -eq "Anti-phish") {
+ $policyDetails = "`n$PolicyType (Impersonation, Mailbox/Spoof Intelligence, Honor DMARC):`n`tThe Default policy."
+ } elseif ($PolicyType -eq "Anti-spam") {
+ $policyDetails = "`n$PolicyType (includes phish & bulk actions):`n`tThe Default policy."
+ } else {
+ $policyDetails = "`n${PolicyType}:`n`tThe Default policy."
+ }
+ } else {
+ if ($PolicyType -eq "Anti-phish") {
+ $policyDetails = "`n$PolicyType (Impersonation, Mailbox/Spoof Intelligence, Honor DMARC):`n`tName: {0}`n`tPriority: {1}" -f $Rule.Name, $Rule.Priority
+ } elseif ($PolicyType -eq "Anti-spam") {
+ $policyDetails = "`n$PolicyType (includes phish & bulk actions):`n`tName: {0}`n`tPriority: {1}" -f $Rule.Name, $Rule.Priority
+ } else {
+ $policyDetails = "`n${PolicyType}:`n`tName: {0}`n`tPriority: {1}" -f $Rule.Name, $Rule.Priority
+ }
+ }
+ return $policyDetails
+ }
+
+ function Test-GraphContext {
+ [OutputType([bool])]
+ param (
+ [Parameter(Mandatory = $true)]
+ [string[]]$Scopes,
+ [Parameter(Mandatory = $true)]
+ [string[]]$ExpectedScopes
+ )
+
+ $validScope = $true
+ foreach ($expectedScope in $ExpectedScopes) {
+ if ($Scopes -contains $expectedScope) {
+ Write-Verbose "Scopes $expectedScope is present."
+ } else {
+ Write-Host "The following scope is missing: $expectedScope" -ForegroundColor Red
+ $validScope = $false
+ }
+ }
+ return $validScope
+ }
+
+ function Write-DebugLog ($message) {
+ if (![string]::IsNullOrEmpty($message)) {
+ $Script:DebugLogger = $Script:DebugLogger | Write-LoggerInstance $message
+ }
+ }
+
+ function Write-HostLog ($message) {
+ if (![string]::IsNullOrEmpty($message)) {
+ $Script:HostLogger = $Script:HostLogger | Write-LoggerInstance $message
+ }
+ # all write-host should be logged in the debug log as well.
+ Write-DebugLog $message
+ }
+
+ Import-Module Microsoft.Graph.Authentication
+ Import-Module ExchangeOnlineManagement
+
+ $LogFileName = "MDOThreatPolicyChecker"
+ $StartDate = Get-Date
+ $StartDateFormatted = ($StartDate).ToString("yyyyMMddhhmmss")
+ $Script:DebugLogger = Get-NewLoggerInstance -LogName "$LogFileName-Debug-$StartDateFormatted" -LogDirectory $PSScriptRoot -AppendDateTimeToFileName $false -ErrorAction SilentlyContinue
+ $Script:HostLogger = Get-NewLoggerInstance -LogName "$LogFileName-Results-$StartDateFormatted" -LogDirectory $PSScriptRoot -AppendDateTimeToFileName $false -ErrorAction SilentlyContinue
+ SetWriteHostAction ${Function:Write-HostLog}
+ SetWriteVerboseAction ${Function:Write-DebugLog}
+ SetWriteWarningAction ${Function:Write-HostLog}
+
+ $BuildVersion = ""
+
+ Write-Host ("MDOThreatPolicyChecker.ps1 script version $($BuildVersion)") -ForegroundColor Green
+
+ if ($ScriptUpdateOnly) {
+ switch (Test-ScriptVersion -AutoUpdate -VersionsUrl "https://aka.ms/MDOThreatPolicyChecker-VersionsURL" -Confirm:$false) {
+ ($true) { Write-Host ("Script was successfully updated") -ForegroundColor Green }
+ ($false) { Write-Host ("No update of the script performed") -ForegroundColor Yellow }
+ default { Write-Host ("Unable to perform ScriptUpdateOnly operation") -ForegroundColor Red }
+ }
+ return
+ }
+
+ if ((-not($SkipVersionCheck)) -and (Test-ScriptVersion -AutoUpdate -VersionsUrl "https://aka.ms/MDOThreatPolicyChecker-VersionsURL" -Confirm:$false)) {
+ Write-Host ("Script was updated. Please re-run the command") -ForegroundColor Yellow
+ return
+ }
+}
+
+process {
+ if (-not $SkipConnectionCheck) {
+ #Validate EXO PS Connection
+ $exoConnection = $null
+ try {
+ $exoConnection = Get-ConnectionInformation -ErrorAction Stop
+ } catch {
+ Write-Host "Error checking EXO connection: $_" -ForegroundColor Red
+ Write-Host "Verify that you have ExchangeOnlineManagement module installed" -ForegroundColor Yellow
+ Write-Host "You need a connection To Exchange Online, you can use:" -ForegroundColor Yellow
+ Write-Host "Connect-ExchangeOnline" -ForegroundColor Yellow
+ Write-Host "Exchange Online Powershell Module is required" -ForegroundColor Red
+ exit
+ }
+ if ($null -eq $exoConnection) {
+ Write-Host "Not connected to EXO" -ForegroundColor Red
+ Write-Host "You need a connection To Exchange Online, you can use:" -ForegroundColor Yellow
+ Write-Host "Connect-ExchangeOnline" -ForegroundColor Yellow
+ Write-Host "Exchange Online Powershell Module is required" -ForegroundColor Red
+ exit
+ } elseif ($exoConnection.count -eq 1) {
+ Write-Host " "
+ Write-Host "Connected to EXO"
+ Write-Host "Session details"
+ Write-Host "Tenant Id: $($exoConnection.TenantId)"
+ Write-Host "User: $($exoConnection.UserPrincipalName)"
+ } else {
+ Write-Host "You have more than one EXO sessions. Please use just one session" -ForegroundColor Red
+ exit
+ }
+
+ if ($PSCmdlet.ParameterSetName -ne "AppliedTenant") {
+ #Validate Graph is connected
+ $graphConnection = $null
+ Write-Host " "
+ try {
+ $graphConnection = Get-MgContext -ErrorAction Stop
+ } catch {
+ Write-Host "Error checking Graph connection: $_" -ForegroundColor Red
+ Write-Host "Verify that you have Microsoft.Graph.Users and Microsoft.Graph.Groups modules installed and loaded" -ForegroundColor Yellow
+ Write-Host "You could use:" -ForegroundColor Yellow
+ Write-Host "Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All'" -ForegroundColor Yellow
+ exit
+ }
+ if ($null -eq $graphConnection) {
+ Write-Host "Not connected to Graph" -ForegroundColor Red
+ Write-Host "Verify that you have Microsoft.Graph.Users and Microsoft.Graph.Groups modules installed and loaded" -ForegroundColor Yellow
+ Write-Host "You could use:" -ForegroundColor Yellow
+ Write-Host "Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All'" -ForegroundColor Yellow
+ exit
+ } elseif ($graphConnection.count -eq 1) {
+ $expectedScopes = "GroupMember.Read.All", 'User.Read.All'
+ if (Test-GraphContext -Scopes $graphConnection.Scopes -ExpectedScopes $expectedScopes) {
+ Write-Host "Connected to Graph"
+ Write-Host "Session details"
+ Write-Host "TenantID: $(($graphConnection).TenantId)"
+ Write-Host "Account: $(($graphConnection).Account)"
+ } else {
+ Write-Host "We cannot continue without Graph Powershell session without Expected Scopes" -ForegroundColor Red
+ Write-Host "Verify that you have Microsoft.Graph.Users and Microsoft.Graph.Groups modules installed and loaded" -ForegroundColor Yellow
+ Write-Host "You could use:" -ForegroundColor Yellow
+ Write-Host "Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All'" -ForegroundColor Yellow
+ exit
+ }
+ } else {
+ Write-Host "You have more than one Graph sessions. Please use just one session" -ForegroundColor Red
+ exit
+ }
+ if (($graphConnection.TenantId) -ne ($exoConnection.TenantId) ) {
+ Write-Host "`nThe Tenant Id from Graph and EXO are different. Please use the same tenant" -ForegroundColor Red
+ exit
+ }
+ }
+ }
+
+ if ($PSCmdlet.ParameterSetName -eq "AppliedTenant") {
+ # Define the cmdlets to retrieve policies from and their corresponding policy types
+ $cmdlets = @{
+ "Get-HostedContentFilterRule" = "Anti-spam Policy"
+ "Get-HostedOutboundSpamFilterRule" = "Outbound Spam Policy"
+ "Get-MalwareFilterRule" = "Malware Policy"
+ "Get-AntiPhishRule" = "Anti-phishing Policy"
+ "Get-SafeLinksRule" = "Safe Links Policy"
+ "Get-SafeAttachmentRule" = "Safe Attachment Policy"
+ "Get-ATPBuiltInProtectionRule" = "Built-in protection preset security Policy"
+ { Get-EOPProtectionPolicyRule -Identity 'Strict Preset Security Policy' } = "EOP"
+ { Get-EOPProtectionPolicyRule -Identity 'Standard Preset Security Policy' } = "EOP"
+ { Get-ATPProtectionPolicyRule -Identity 'Strict Preset Security Policy' } = "MDO (Safe Links / Safe Attachments)"
+ { Get-ATPProtectionPolicyRule -Identity 'Standard Preset Security Policy' } = "MDO (Safe Links / Safe Attachments)"
+ }
+
+ $foundIssues = $false
+
+ Write-Host " "
+ # Loop through each cmdlet
+ foreach ($cmdlet in $cmdlets.Keys) {
+ # Retrieve the policies
+ $policies = & $cmdlet
+
+ # Loop through each policy
+ foreach ($policy in $policies) {
+ # Initialize an empty list to store issues
+ $issues = New-Object System.Collections.Generic.List[string]
+
+ # Check the logic of the policy and add issues to the list
+ if ($policy.SentTo -and $policy.ExceptIfSentTo) {
+ $issues.Add("`t`t-> User inclusions and exclusions. `n`t`t`tExcluding and including Users individually is redundant and confusing as only the included Users could possibly be included.`n")
+ }
+ if ($policy.RecipientDomainIs -and $policy.ExceptIfRecipientDomainIs) {
+ $issues.Add("`t`t-> Domain inclusions and exclusions. `n`t`t`tExcluding and including Domains is redundant and confusing as only the included Domains could possibly be included.`n")
+ }
+ if ($policy.SentTo -and $policy.SentToMemberOf) {
+ $issues.Add("`t`t-> Illogical inclusions of Users and Groups. `n`t`t`tThe policy will only apply to Users who are also members of any Groups you have specified. `n`t`t`tThis makes the Group inclusion redundant and confusing.`n`t`t`tSuggestion: use one or the other type of inclusion.`n")
+ }
+ if ($policy.SentTo -and $policy.RecipientDomainIs) {
+ $issues.Add("`t`t-> Illogical inclusions of Users and Domains. `n`t`t`tThe policy will only apply to Users whose email domains also match any Domains you have specified. `n`t`t`tThis makes the Domain inclusion redundant and confusing.`n`t`t`tSuggestion: use one or the other type of inclusion.`n")
+ }
+
+ # If there are any issues, print the policy details once and then list all the issues
+ if ($issues.Count -gt 0) {
+ if ($policy.State -eq "Enabled") {
+ $color = [console]::ForegroundColor
+ } else {
+ $color = "Yellow"
+ }
+ Write-Host ("Policy $($policy.Name):")
+ Write-Host ("`tType: $($cmdlets[$cmdlet]).")
+ Write-Host ("`tState: $($policy.State).") -ForegroundColor $color
+ Write-Host ("`tIssues: ") -ForegroundColor Red
+ foreach ($issue in $issues) {
+ Write-Host $issue
+ }
+ $foundIssues = $true
+ }
+ }
+ }
+ if (-not $foundIssues) {
+ Write-Host ("No logical inconsistencies found!") -ForegroundColor DarkGreen
+ }
+ } else {
+ if ($CsvFilePath) {
+ try {
+ # Import CSV file
+ $csvFile = Import-Csv -Path $CsvFilePath
+ # checking 'email' header
+ if ($csvFile[0].PSObject.Properties.Name -contains 'Email') {
+ $EmailAddress = $csvFile | Select-Object -ExpandProperty Email
+ } else {
+ Write-Host "CSV does not contain 'Email' header." -ForegroundColor Red
+ exit
+ }
+ } catch {
+ Write-Host "Error importing CSV file: $_" -ForegroundColor Red
+ exit
+ }
+ }
+
+ $acceptedDomains = $null
+ try {
+ $acceptedDomains = Get-AcceptedDomain -ErrorAction Stop
+ } catch {
+ Write-Host "Error getting Accepted Domains: $_" -ForegroundColor Red
+ exit
+ }
+
+ if ($null -eq $acceptedDomains) {
+ Write-Host "We do not get accepted domains." -ForegroundColor Red
+ exit
+ }
+
+ if ($acceptedDomains.count -eq 0) {
+ Write-Host "No accepted domains found." -ForegroundColor Red
+ exit
+ } else {
+ $acceptedDomainList = New-Object System.Collections.Generic.List[string]
+ $acceptedDomains | ForEach-Object { $acceptedDomainList.Add($_.DomainName.ToString()) }
+ }
+
+ $foundError = $false
+ $validEmailAddress = New-Object System.Collections.Generic.List[MailAddress]
+ foreach ($email in $EmailAddress) {
+ $tempAddress = $null
+ $tempAddress = Test-EmailAddress -EmailAddress $email -AcceptedDomains $acceptedDomainList
+ if ($null -eq $tempAddress) {
+ $foundError = $true
+ } else {
+ $validEmailAddress.Add($tempAddress)
+ }
+ }
+ if ($foundError) {
+ exit
+ }
+
+ $malwareFilterRules = $null
+ $antiPhishRules = $null
+ $hostedContentFilterRules = $null
+ $hostedOutboundSpamFilterRules = $null
+ $eopStrictPresetRules = $null
+ $eopStandardPresetRules = $null
+
+ if ( -not $OnlyMDOPolicies) {
+ $malwareFilterRules = Get-MalwareFilterRule | Where-Object { $_.State -ne 'Disabled' }
+ $antiPhishRules = Get-AntiPhishRule | Where-Object { $_.State -ne 'Disabled' }
+ $hostedContentFilterRules = Get-HostedContentFilterRule | Where-Object { $_.State -ne 'Disabled' }
+ $hostedOutboundSpamFilterRules = Get-HostedOutboundSpamFilterRule | Where-Object { $_.State -ne 'Disabled' }
+ $eopStrictPresetRules = Get-EOPProtectionPolicyRule -Identity 'Strict Preset Security Policy' | Where-Object { $_.State -ne 'Disabled' }
+ $eopStandardPresetRules = Get-EOPProtectionPolicyRule -Identity 'Standard Preset Security Policy' | Where-Object { $_.State -ne 'Disabled' }
+ }
+
+ $safeAttachmentRules = $null
+ $safeLinksRules = $null
+ $mdoStrictPresetRules = $null
+ $mdoStandardPresetRules = $null
+
+ if ($IncludeMDOPolicies -or $OnlyMDOPolicies) {
+ # Get the custom and preset rules for Safe Attachments/Links
+ $safeAttachmentRules = Get-SafeAttachmentRule | Where-Object { $_.State -ne 'Disabled' }
+ $safeLinksRules = Get-SafeLinksRule | Where-Object { $_.State -ne 'Disabled' }
+ $mdoStrictPresetRules = Get-ATPProtectionPolicyRule -Identity 'Strict Preset Security Policy' | Where-Object { $_.State -ne 'Disabled' }
+ $mdoStandardPresetRules = Get-ATPProtectionPolicyRule -Identity 'Standard Preset Security Policy' | Where-Object { $_.State -ne 'Disabled' }
+ }
+
+ foreach ($email in $validEmailAddress) {
+ $stEmailAddress = $email.ToString()
+ # Initialize a variable to capture all policy details
+ $allPolicyDetails = ""
+ Write-Host "`n`nPolicies applied to $stEmailAddress..."
+
+ if ( -not $OnlyMDOPolicies) {
+ # Check the Strict EOP rules first as they have higher precedence
+ $matchedRule = $null
+ if ($eopStrictPresetRules) {
+ $matchedRule = Test-Rules -Rules $eopStrictPresetRules -email $stEmailAddress
+ }
+ if ($eopStrictPresetRules -contains $matchedRule) {
+ $allPolicyDetails += "`nFor malware, spam, and phishing:`n`tName: {0}`n`tPriority: {1}`n`tThe policy actions are not configurable." -f $matchedRule.Name, $matchedRule.Priority
+ Write-Host $allPolicyDetails -ForegroundColor Green
+ $outboundSpamMatchedRule = $null
+ if ($hostedOutboundSpamFilterRules) {
+ $outboundSpamMatchedRule = Test-Rules -Rules $hostedOutboundSpamFilterRules -email $stEmailAddress -Outbound
+ $allPolicyDetails = Get-Policy $outboundSpamMatchedRule "Outbound Spam"
+ Write-Host $allPolicyDetails -ForegroundColor Yellow
+ }
+ } else {
+ # Check the Standard EOP rules secondly
+ $matchedRule = $null
+ if ($eopStandardPresetRules) {
+ $matchedRule = Test-Rules -Rules $eopStandardPresetRules -email $stEmailAddress
+ }
+ if ($eopStandardPresetRules -contains $matchedRule) {
+ $allPolicyDetails += "`nFor malware, spam, and phishing:`n`tName: {0}`n`tPriority: {1}`n`tThe policy actions are not configurable." -f $matchedRule.Name, $matchedRule.Priority
+ Write-Host $allPolicyDetails -ForegroundColor Green
+ $outboundSpamMatchedRule = $allPolicyDetails = $null
+ if ($hostedOutboundSpamFilterRules) {
+ $outboundSpamMatchedRule = Test-Rules -Rules $hostedOutboundSpamFilterRules -Email $stEmailAddress -Outbound
+ $allPolicyDetails = Get-Policy $outboundSpamMatchedRule "Outbound Spam"
+ Write-Host $allPolicyDetails -ForegroundColor Yellow
+ }
+ } else {
+ # If no match in EOPProtectionPolicyRules, check MalwareFilterRules, AntiPhishRules, outboundSpam, and HostedContentFilterRules
+ $allPolicyDetails = " "
+ $malwareMatchedRule = $malwareFilterPolicy = $null
+ if ($malwareFilterRules) {
+ $malwareMatchedRule = Test-Rules -Rules $malwareFilterRules -Email $stEmailAddress
+ if ($null -eq $malwareMatchedRule) {
+ Write-Host "`nMalware:`n`tDefault policy" -ForegroundColor Yellow
+ } else {
+ $malwareFilterPolicy = Get-MalwareFilterPolicy $malwareMatchedRule.Name
+ Write-Host "`nMalware:`n`tName: $($malwareMatchedRule.Name)`n`tPriority: $($malwareMatchedRule.Priority)" -ForegroundColor Yellow
+ if ($malwareFilterPolicy -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $malwareFilterPolicy
+ }
+ }
+ }
+ $antiPhishMatchedRule = $antiPhishPolicy = $null
+ if ($antiPhishRules) {
+ $antiPhishMatchedRule = Test-Rules -Rules $antiPhishRules -Email $stEmailAddress
+ if ($null -eq $antiPhishMatchedRule) {
+ Write-Host "`nAnti-phish:`n`tDefault policy" -ForegroundColor Yellow
+ } else {
+ $antiPhishPolicy = Get-AntiPhishPolicy $antiPhishMatchedRule.Name
+ Write-Host "`nAnti-phish:`n`tName: $($antiPhishMatchedRule.Name)`n`tPriority: $($antiPhishMatchedRule.Priority)" -ForegroundColor Yellow
+ if ($antiPhishPolicy -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $antiPhishPolicy
+ }
+ }
+ }
+ $spamMatchedRule = $hostedContentFilterPolicy = $null
+ if ($hostedContentFilterRules) {
+ $spamMatchedRule = Test-Rules -Rules $hostedContentFilterRules -Email $stEmailAddress
+ if ($null -eq $spamMatchedRule) {
+ Write-Host "`nAnti-spam::`n`tDefault policy" -ForegroundColor Yellow
+ } else {
+ $hostedContentFilterPolicy = Get-HostedContentFilterPolicy $spamMatchedRule.Name
+ Write-Host "`nAnti-spam:`n`tName: $($spamMatchedRule.Name)`n`tPriority: $($spamMatchedRule.Priority)" -ForegroundColor Yellow
+ if ($hostedContentFilterPolicy -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $hostedContentFilterPolicy
+ }
+ }
+ }
+ $outboundSpamMatchedRule = $hostedOutboundSpamFilterPolicy = $null
+ if ($hostedOutboundSpamFilterRules) {
+ $outboundSpamMatchedRule = Test-Rules -Rules $hostedOutboundSpamFilterRules -email $stEmailAddress -Outbound
+ if ($null -eq $outboundSpamMatchedRule) {
+ Write-Host "`nOutbound Spam:`n`tDefault policy" -ForegroundColor Yellow
+ } else {
+ $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy $outboundSpamMatchedRule.Name
+ Write-Host "`nOutbound Spam:`n`tName: $($outboundSpamMatchedRule.Name)`n`tPriority: $($outboundSpamMatchedRule.Priority)" -ForegroundColor Yellow
+ if ($hostedOutboundSpamFilterPolicy -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $hostedOutboundSpamFilterPolicy
+ }
+ }
+ }
+ $allPolicyDetails = $userDetails + "`n" + $allPolicyDetails
+ Write-Host $allPolicyDetails -ForegroundColor Yellow
+ }
+ }
+ }
+
+ if ($IncludeMDOPolicies -or $OnlyMDOPolicies) {
+ $domain = $email.Host
+ $matchedRule = $null
+
+ # Check the MDO Strict Preset rules first as they have higher precedence
+ if ($mdoStrictPresetRules) {
+ $matchedRule = Test-Rules -Rules $mdoStrictPresetRules -Email $stEmailAddress
+ }
+ if ($mdoStrictPresetRules -contains $matchedRule) {
+ Write-Host ("`nFor both Safe Attachments and Safe Links:`n`tName: {0}`n`tPriority: {1}" -f $matchedRule.Name, $matchedRule.Priority) -ForegroundColor Green
+ } else {
+ # Check the Standard MDO rules secondly
+ $matchedRule = $null
+ if ($mdoStandardPresetRules) {
+ $matchedRule = Test-Rules -Rules $mdoStandardPresetRules -Email $stEmailAddress
+ }
+ if ($mdoStandardPresetRules -contains $matchedRule) {
+ Write-Host ("`nFor both Safe Attachments and Safe Links:`n`tName: {0}`n`tPriority: {1}" -f $matchedRule.Name, $matchedRule.Priority) -ForegroundColor Green
+ } else {
+ # No match in preset ATPProtectionPolicyRules, check custom SA/SL rules
+ $SAmatchedRule = $null
+ if ($safeAttachmentRules) {
+ $SAmatchedRule = Test-Rules -Rules $safeAttachmentRules -Email $stEmailAddress
+ }
+ $SLmatchedRule = $null
+ if ($safeLinksRules) {
+ $SLmatchedRule = Test-Rules -Rules $safeLinksRules -Email $stEmailAddress
+ }
+ if ($null -eq $SAmatchedRule) {
+ # Get the Built-in Protection Rule
+ $builtInProtectionRule = Get-ATPBuiltInProtectionRule
+ # Initialize a variable to track if the user is a member of any excluded group
+ $isInExcludedGroup = $false
+ # Check if the user is a member of any group in ExceptIfSentToMemberOf
+ foreach ($groupEmail in $builtInProtectionRule.ExceptIfSentToMemberOf) {
+ $groupObjectId = Get-GroupObjectId -GroupEmail $groupEmail
+ if ((-not [string]::IsNullOrEmpty($groupObjectId)) -and (Test-IsInGroup -Email $stEmailAddress -GroupObjectId $groupObjectId)) {
+ $isInExcludedGroup = $true
+ break
+ }
+ }
+ # Check if the user is returned by ExceptIfSentTo, isInExcludedGroup, or ExceptIfRecipientDomainIs in the Built-in Protection Rule
+ if ($stEmailAddress -in $builtInProtectionRule.ExceptIfSentTo -or
+ $isInExcludedGroup -or
+ $domain -in $builtInProtectionRule.ExceptIfRecipientDomainIs) {
+ Write-Host "`nSafe Attachments:`n`tThe user is excluded from all Safe Attachment protection because they are excluded from Built-in Protection, and they are not explicitly included in any other policy." -ForegroundColor Red
+ } else {
+ Write-Host "`nSafe Attachments:`n`tIf your organization has at least one A5/E5, or MDO license, the user is included in the Built-in policy." -ForegroundColor Yellow
+ }
+ $policy = $null
+ } else {
+ $safeAttachmentPolicy = Get-SafeAttachmentPolicy -Identity $SAmatchedRule.Name
+ Write-Host "`nSafe Attachments:`n`tName: $($SAmatchedRule.Name)`n`tPriority: $($SAmatchedRule.Priority)" -ForegroundColor Yellow
+ if ($SAmatchedRule -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $safeAttachmentPolicy
+ }
+ }
+
+ if ($null -eq $SLmatchedRule) {
+ # Get the Built-in Protection Rule
+ $builtInProtectionRule = Get-ATPBuiltInProtectionRule
+
+ # Initialize a variable to track if the user is a member of any excluded group
+ $isInExcludedGroup = $false
+
+ # Check if the user is a member of any group in ExceptIfSentToMemberOf
+ foreach ($groupEmail in $builtInProtectionRule.ExceptIfSentToMemberOf) {
+ $groupObjectId = Get-GroupObjectId -GroupEmail $groupEmail
+ if ((-not [string]::IsNullOrEmpty($groupObjectId)) -and (Test-IsInGroup -Email $stEmailAddress -GroupObjectId $groupObjectId)) {
+ $isInExcludedGroup = $true
+ break
+ }
+ }
+
+ # Check if the user is returned by ExceptIfSentTo, isInExcludedGroup, or ExceptIfRecipientDomainIs in the Built-in Protection Rule
+ if ($stEmailAddress -in $builtInProtectionRule.ExceptIfSentTo -or
+ $isInExcludedGroup -or
+ $domain -in $builtInProtectionRule.ExceptIfRecipientDomainIs) {
+ Write-Host "`nSafe Links:`n`tThe user is excluded from all Safe Links protection because they are excluded from Built-in Protection, and they are not explicitly included in any other policy." -ForegroundColor Red
+ } else {
+ Write-Host "`nSafe Links:`n`tIf your organization has at least one A5/E5, or MDO license, the user is included in the Built-in policy." -ForegroundColor Yellow
+ }
+ $policy = $null
+ } else {
+ $safeLinkPolicy = Get-SafeLinksPolicy -Identity $SLmatchedRule.Name
+ Write-Host "`nSafe Links:`n`tName: $($SLmatchedRule.Name)`n`tPriority: $($SLmatchedRule.Priority)" -ForegroundColor Yellow
+ if ($SLmatchedRule -and $ShowDetailedPolicies) {
+ Show-DetailedPolicy -Policy $safeLinkPolicy
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ Write-Host " "
+}
diff --git a/docs/M365/MDO/MDOThreatPolicyChecker.md b/docs/M365/MDO/MDOThreatPolicyChecker.md
new file mode 100644
index 0000000000..3a38eb5c94
--- /dev/null
+++ b/docs/M365/MDO/MDOThreatPolicyChecker.md
@@ -0,0 +1,94 @@
+# MDOThreatPolicyChecker
+
+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.
+
+## 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.
+
+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.
+
+You can find the Microsoft Graph modules in the following link:
+ https://www.powershellgallery.com/packages/Microsoft.Graph/
+ 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
+Install-Module -Name Microsoft.Graph.Authentication -Scope CurrentUser
+Install-Module -Name Microsoft.Graph.Groups -Scope CurrentUser
+Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser
+```
+
+!!! warning "NOTE"
+
+ 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'
+```powershell
+Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All'
+```
+
+You need as well an Exchange Online session.
+```powershell
+Connect-ExchangeOnline
+```
+
+You can find the Exchange module and information in the following links:
+ https://learn.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2?view=exchange-ps
+ 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:
+```powershell
+.\MDOThreatPolicyChecker.ps1
+```
+
+To provide a CSV input file with email addresses and see only EOP policies, run the following:
+```powershell
+.\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv]
+```
+
+To provide multiple email addresses by command line and see only EOP policies, run the following:
+```powershell
+.\MDOThreatPolicyChecker.ps1 -EmailAddress user1@contoso.com,user2@fabrikam.com
+```
+
+To provide a CSV input file with email addresses and see both EOP and MDO policies, run the following:
+```powershell
+.\MDOThreatPolicyChecker.ps1 -CsvFilePath [Path\filename.csv] -IncludeMDOPolicies
+```
+
+To provide an email address and see only MDO (Safe Attachment and Safe Links) policies, run the following:
+```powershell
+.\MDOThreatPolicyChecker.ps1 -EmailAddress user1@contoso.com -OnlyMDOPolicies
+```
+
+To see the details of the policies applied to mailbox in a CSV file for both EOP and MDO, run the following:
+```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:
+```powershell
+.\MDOThreatPolicyChecker.ps1 -IncludeMDOPolicies -EmailAddress @(Get-ExOMailbox -ResultSize unlimited | Select-Object -ExpandProperty PrimarySmtpAddress)
+```
+
+## Parameters
+
+Parameter | Description |
+----------|-------------|
+CsvFilePath | Allows you to specify a CSV file with a list of email addresses to check. Csv file must include a first line with header Email.
+EmailAddress | Allows you to specify email address or multiple addresses separated by commas.
+IncludeMDOPolicies | Checks both EOP and MDO (Safe Attachment and Safe Links) policies for user(s) specified in the CSV file or EmailAddress parameter.
+OnlyMDOPolicies | Checks only MDO (Safe Attachment and Safe Links) policies for user(s) specified in the CSV file or EmailAddress parameter.
+ShowDetailedPolicies | In addition to the policy applied, show any policy details that are set to True, On, or not blank.
+SkipConnectionCheck | Skips connection check for Graph and Exchange Online.
+SkipVersionCheck | Skips the version check of the script.
+ScriptUpdateOnly | Just updates script version to latest one.
diff --git a/mkdocs.yml b/mkdocs.yml
index 4c1c58431e..dce6bec2f2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -75,6 +75,8 @@ nav:
- Hybrid:
- Test-HMAEAS: Hybrid/Test-HMAEAS.md
- M365:
+ - MDO:
+ - MDOThreatPolicyChecker: M365/MDO/MDOThreatPolicyChecker.md
- DLT365Groupsupgrade: M365/DLT365Groupsupgrade.md
- Performance:
- ExPerfWiz: Performance/ExPerfWiz.md
From b7c1587cb0a72daa689a91df5502f14370e37d27 Mon Sep 17 00:00:00 2001
From: Ignacio Serrano <103440830+iserrano76@users.noreply.github.com>
Date: Wed, 3 Jul 2024 11:39:24 +0200
Subject: [PATCH 2/7] Update with David Requested Changes
---
M365/MDO/MDOThreatPolicyChecker.ps1 | 107 +++++++++++++---------------
1 file changed, 48 insertions(+), 59 deletions(-)
diff --git a/M365/MDO/MDOThreatPolicyChecker.ps1 b/M365/MDO/MDOThreatPolicyChecker.ps1
index 4c6998fc3a..e239c3afdf 100644
--- a/M365/MDO/MDOThreatPolicyChecker.ps1
+++ b/M365/MDO/MDOThreatPolicyChecker.ps1
@@ -121,7 +121,7 @@ begin {
try {
$group = Get-MgGroup -Filter "mail eq '$stGroupEmail'" -ErrorAction Stop
} catch {
- Write-Host "Error getting group $stGroupEmail`: $_" -ForegroundColor Red
+ Write-Host "Error getting group $stGroupEmail`:`n$_" -ForegroundColor Red
return $null
}
@@ -134,7 +134,7 @@ begin {
# Return the Object ID of the group
return $group.Id
} else {
- Write-Host "Wrong type for $($group.ToString()): $group.Id.GetType().Name" -ForegroundColor Red
+ Write-Host "Wrong type for $($group.ToString()): $($group.Id.GetType().Name)" -ForegroundColor Red
return $null
}
} else {
@@ -170,7 +170,7 @@ begin {
try {
$groupMembers = Get-MgGroupMember -GroupId $GroupObjectId -ErrorAction Stop
} catch {
- Write-Host "Error getting group members for $GroupObjectId`: $_" -ForegroundColor Red
+ Write-Host "Error getting group members for $GroupObjectId`:`n$_" -ForegroundColor Red
return $null
}
@@ -185,7 +185,7 @@ begin {
try {
$user = Get-MgUser -UserId $member.Id -ErrorAction Stop
} catch {
- Write-Host "Error getting user with Id $($member.Id): $_" -ForegroundColor Red
+ Write-Host "Error getting user with Id $($member.Id):`n$_" -ForegroundColor Red
return $null
}
# Compare the user's email address with the $email parameter
@@ -237,31 +237,30 @@ begin {
$tempAddress = [MailAddress]$EmailAddress
} catch {
Write-Host "The EmailAddress $EmailAddress cannot be validated. Please provide a valid email address." -ForegroundColor Red
- return $null
- }
- $recipient = $null
- Write-Verbose "Getting $EmailAddress"
- try {
- $recipient = Get-EXORecipient $EmailAddress -ErrorAction Stop
- } catch {
- Write-Host "Error getting recipient $EmailAddress`: $_" -ForegroundColor Red
+ Write-Host "Error details:`n$_" -ForegroundColor Red
return $null
}
- if ($null -eq $recipient) {
- Write-Host "$EmailAddress is not a recipient in this tenant" -ForegroundColor Red
- return $null
- } else {
- $domain = $tempAddress.Host
- Write-Verbose "Checking domain $domain"
- if ($AcceptedDomains -contains $domain) {
- Write-Verbose "Verified domain $domain for $tempAddress"
- return $tempAddress
- } else {
- Write-Host "The domain $domain is not an accepted domain in your organization. Please provide a valid email address: $tempAddress " -ForegroundColor Red
- return $null
+ $domain = $tempAddress.Host
+ Write-Verbose "Checking domain $domain"
+ if ($AcceptedDomains -contains $domain) {
+ Write-Verbose "Verified domain $domain for $tempAddress"
+ $recipient = $null
+ Write-Verbose "Getting $EmailAddress"
+ try {
+ $recipient = Get-EXORecipient $EmailAddress -ErrorAction Stop
+ if ($null -eq $recipient) {
+ Write-Host "$EmailAddress is not a recipient in this tenant" -ForegroundColor Red
+ } else {
+ return $tempAddress
+ }
+ } catch {
+ Write-Host "Error getting recipient $EmailAddress`:`n$_" -ForegroundColor Red
}
+ } else {
+ Write-Host "The domain $domain is not an accepted domain in your organization. Please provide a valid email address: $tempAddress " -ForegroundColor Red
}
+ return $null
}
# Function to check rules
@@ -348,7 +347,6 @@ begin {
}
$temp = $Email.Host
-
while ($temp.IndexOf(".") -gt 0) {
if ($temp -in $domainsIs) {
Write-Verbose "domainInRule: $temp"
@@ -363,32 +361,23 @@ begin {
# Check for explicit inclusion in any user, group, or domain that are not empty, and account for 3 empty inclusions
# Also check for any exclusions as user, group, or domain. Nulls don't need to be accounted for and this is an OR condition for exclusions
- if ((($emailInRule -or (-not $senderOrReceiver)) -and
- ($domainInRule -or (-not $domainsIs)) -and
- ($groupInRule -or (-not $memberOf))) -and
- ($emailInRule -or $domainInRule -or $groupInRule)) {
- if ((-not $emailExceptionInRule) -and
- (-not $groupExceptionInRule) -and
- (-not $domainExceptionInRule)) {
- Write-Verbose "Return Rule $($rule.Name)"
- Write-Verbose "emailInRule: $emailInRule domainInRule: $domainInRule groupInRule: $groupInRule "
- Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
- return $rule
- }
+ if (((($emailInRule -or (-not $senderOrReceiver)) -and ($domainInRule -or (-not $domainsIs)) -and ($groupInRule -or (-not $memberOf))) -and
+ ($emailInRule -or $domainInRule -or $groupInRule)) -and
+ ((-not $emailExceptionInRule) -and (-not $groupExceptionInRule) -and (-not $domainExceptionInRule))) {
+ Write-Verbose "Return Rule $($rule.Name)"
+ Write-Verbose "emailInRule: $emailInRule domainInRule: $domainInRule groupInRule: $groupInRule "
+ Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
+ return $rule
}
- if (-not $Outbound) {
- # Check for implicit inclusion (no mailboxes included at all), which is possible for Presets and SA/SL. They are included if not explicitly excluded.
- if ((-not $senderOrReceiver) -and (-not $domainsIs) -and (-not $memberOf)) {
- if ((-not $emailExceptionInRule) -and
- (-not $groupExceptionInRule) -and
- (-not $domainExceptionInRule)) {
- Write-Verbose "Return Rule $($rule.Name)"
- Write-Verbose "senderOrReceiver: $senderOrReceiver domainsIs: $domainsIs memberOf: $memberOf "
- Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
- return $rule
- }
- }
+ # Check for implicit inclusion (no mailboxes included at all), which is possible for Presets and SA/SL. They are included if not explicitly excluded. Only inbound
+ if ((-not $Outbound) -and
+ (((-not $senderOrReceiver) -and (-not $domainsIs) -and (-not $memberOf)) -and
+ ((-not $emailExceptionInRule) -and (-not $groupExceptionInRule) -and (-not $domainExceptionInRule)))) {
+ Write-Verbose "Return Rule $($rule.Name)"
+ Write-Verbose "senderOrReceiver: $senderOrReceiver domainsIs: $domainsIs memberOf: $memberOf "
+ Write-Verbose "emailExceptionInRule: $emailExceptionInRule groupExceptionInRule: $groupExceptionInRule domainExceptionInRule: $domainExceptionInRule "
+ return $rule
}
}
return $null
@@ -400,14 +389,14 @@ begin {
$Policy
)
Write-Host "`n`tProperties of the policy that are True, On, or not blank:"
- $excludedProperties = 'Identity', 'Id', 'Name', 'ExchangeVersion', 'DistinguishedName', 'ObjectCategory', 'ObjectClass', 'WhenChanged', 'WhenCreated', `
- 'WhenChangedUTC', 'WhenCreatedUTC', 'ExchangeObjectId', 'OrganizationalUnitRoot', 'OrganizationId', 'OriginatingServer', 'ObjectState', 'Priority', 'ImmutableId', `
- 'Description', 'HostedContentFilterPolicy', 'AntiPhishPolicy', 'MalwareFilterPolicy', 'SafeAttachmentPolicy', 'SafeLinksPolicy', 'HostedOutboundSpamFilterPolicy'
+ $excludedProperties = 'Identity', 'Id', 'Name', 'ExchangeVersion', 'DistinguishedName', 'ObjectCategory', 'ObjectClass', 'WhenChanged', 'WhenCreated',
+ 'WhenChangedUTC', 'WhenCreatedUTC', 'ExchangeObjectId', 'OrganizationalUnitRoot', 'OrganizationId', 'OriginatingServer', 'ObjectState', 'Priority', 'ImmutableId',
+ 'Description', 'HostedContentFilterPolicy', 'AntiPhishPolicy', 'MalwareFilterPolicy', 'SafeAttachmentPolicy', 'SafeLinksPolicy', 'HostedOutboundSpamFilterPolicy'
$Policy.PSObject.Properties | ForEach-Object {
- if ($null -ne $_.Value -and `
- (($_.Value.GetType() -eq [Boolean] -and $_.Value -eq $true) `
- -or ($_.Value -ne '{}' -and $_.Value -ne 'Off' -and $_.Value -ne $true -and $_.Value -ne '' -and $excludedProperties -notcontains $_.Name))) {
+ if ($null -ne $_.Value -and
+ (($_.Value.GetType() -eq [Boolean] -and $_.Value -eq $true) -or
+ ($_.Value -ne '{}' -and $_.Value -ne 'Off' -and $_.Value -ne $true -and $_.Value -ne '' -and $excludedProperties -notcontains $_.Name))) {
Write-Host "`t`t$($_.Name): $($_.Value)"
} else {
Write-Verbose "`t`tExcluded property:$($_.Name): $($_.Value)"
@@ -515,7 +504,7 @@ process {
try {
$exoConnection = Get-ConnectionInformation -ErrorAction Stop
} catch {
- Write-Host "Error checking EXO connection: $_" -ForegroundColor Red
+ Write-Host "Error checking EXO connection:`n$_" -ForegroundColor Red
Write-Host "Verify that you have ExchangeOnlineManagement module installed" -ForegroundColor Yellow
Write-Host "You need a connection To Exchange Online, you can use:" -ForegroundColor Yellow
Write-Host "Connect-ExchangeOnline" -ForegroundColor Yellow
@@ -546,7 +535,7 @@ process {
try {
$graphConnection = Get-MgContext -ErrorAction Stop
} catch {
- Write-Host "Error checking Graph connection: $_" -ForegroundColor Red
+ Write-Host "Error checking Graph connection:`n$_" -ForegroundColor Red
Write-Host "Verify that you have Microsoft.Graph.Users and Microsoft.Graph.Groups modules installed and loaded" -ForegroundColor Yellow
Write-Host "You could use:" -ForegroundColor Yellow
Write-Host "Connect-MgGraph -Scopes 'Group.Read.All','User.Read.All'" -ForegroundColor Yellow
@@ -660,7 +649,7 @@ process {
exit
}
} catch {
- Write-Host "Error importing CSV file: $_" -ForegroundColor Red
+ Write-Host "Error importing CSV file:`n$_" -ForegroundColor Red
exit
}
}
@@ -669,7 +658,7 @@ process {
try {
$acceptedDomains = Get-AcceptedDomain -ErrorAction Stop
} catch {
- Write-Host "Error getting Accepted Domains: $_" -ForegroundColor Red
+ Write-Host "Error getting Accepted Domains:`n$_" -ForegroundColor Red
exit
}
From d941ce168ac1a941c74c2fbc37b46710f09698cb Mon Sep 17 00:00:00 2001
From: Ignacio Serrano <103440830+iserrano76@users.noreply.github.com>
Date: Wed, 3 Jul 2024 15:34:49 +0200
Subject: [PATCH 3/7] Removed Imports
---
M365/MDO/MDOThreatPolicyChecker.ps1 | 3 ---
1 file changed, 3 deletions(-)
diff --git a/M365/MDO/MDOThreatPolicyChecker.ps1 b/M365/MDO/MDOThreatPolicyChecker.ps1
index e239c3afdf..8bab8d90f5 100644
--- a/M365/MDO/MDOThreatPolicyChecker.ps1
+++ b/M365/MDO/MDOThreatPolicyChecker.ps1
@@ -466,9 +466,6 @@ begin {
Write-DebugLog $message
}
- Import-Module Microsoft.Graph.Authentication
- Import-Module ExchangeOnlineManagement
-
$LogFileName = "MDOThreatPolicyChecker"
$StartDate = Get-Date
$StartDateFormatted = ($StartDate).ToString("yyyyMMddhhmmss")
From dba1218240619c4280a6a8afa661e5337f18689c Mon Sep 17 00:00:00 2001
From: Shane Ferrell
Date: Fri, 12 Jul 2024 11:57:37 -0700
Subject: [PATCH 4/7] Change Get-MB to Get-Recipient
---
Calendar/Get-RBASummary.ps1 | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Calendar/Get-RBASummary.ps1 b/Calendar/Get-RBASummary.ps1
index 82884e23d5..ce26bea7b0 100644
--- a/Calendar/Get-RBASummary.ps1
+++ b/Calendar/Get-RBASummary.ps1
@@ -283,10 +283,10 @@ function OutputMBList {
$Org = $Identity.Split('@')[1]
if ($null -ne $Org) {
- $User = Get-Mailbox -Identity $User -organization $Org
+ $User = Get-Recipient -Identity $User -organization $Org
Write-Host " `t `t [$($User.DisplayName)] -- $($User.PrimarySmtpAddress)"
} else {
- $User = Get-Mailbox -Identity $User
+ $User = Get-Recipient -Identity $User
Write-Host " `t `t [$($User.DisplayName)] -- $($User.PrimarySmtpAddress)"
}
}
From 91d43a97883b4afb04ec397c951799cf29a105bb Mon Sep 17 00:00:00 2001
From: DKhrebin <43005759+DKhrebin@users.noreply.github.com>
Date: Mon, 15 Jul 2024 15:55:54 +0100
Subject: [PATCH 5/7] Update Test-ExchAVExclusions.ps1
Nice to have file version to recognize outdated 3rd party software.
---
Diagnostics/AVTester/Test-ExchAVExclusions.ps1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Diagnostics/AVTester/Test-ExchAVExclusions.ps1 b/Diagnostics/AVTester/Test-ExchAVExclusions.ps1
index dcd66cf7f7..262a46fc7f 100644
--- a/Diagnostics/AVTester/Test-ExchAVExclusions.ps1
+++ b/Diagnostics/AVTester/Test-ExchAVExclusions.ps1
@@ -466,7 +466,7 @@ while ($currentDiff -gt 0) {
if ($ProcessModules.count -gt 0) {
foreach ($module in $ProcessModules) {
- $OutString = ("PROCESS: $($process.ProcessName) PID($($process.Id)) UNEXPECTED MODULE: $($module.ModuleName) COMPANY: $($module.Company)`n`tPATH: $($module.FileName)")
+ $OutString = ("PROCESS: $($process.ProcessName) PID($($process.Id)) UNEXPECTED MODULE: $($module.ModuleName) COMPANY: $($module.Company)`n`tPATH: $($module.FileName)`n`tFileVersion: $($module.FileVersion)")
Write-Host "[FAIL] - $OutString" -ForegroundColor Red
if ($process.MainModule.ModuleName -eq "W3wp.exe") {
$SuspiciousW3wpProcessList += $OutString
From 48420007f8ded82146c7bf8146673be2be949126 Mon Sep 17 00:00:00 2001
From: Shane Ferrell
Date: Thu, 11 Jul 2024 15:22:21 -0700
Subject: [PATCH 6/7] Fix PS errors caused by ImportExcel not be installed,
minor tweaks
fix client names
License
Add Sensitivity
Use Get-Recipient where we can instead of Get-MB
Fix Room MB detection when no PII
Formatting
---
Calendar/CalLogHelpers/CalLogCSVFunctions.ps1 | 1 +
.../CalLogHelpers/CalLogExportFunctions.ps1 | 207 -----------------
.../CalLogHelpers/CalLogInfoFunctions.ps1 | 14 +-
.../CalLogHelpers/ExcelModuleInstaller.ps1 | 3 +-
.../CalLogHelpers/ExportToExcelFunctions.ps1 | 212 ++++++++++++++++++
Calendar/CalLogHelpers/Invoke-GetCalLogs.ps1 | 1 +
Calendar/CalLogHelpers/Invoke-GetMailbox.ps1 | 81 ++++---
.../ShortClientNameFunctions.ps1 | 5 +
Calendar/CalLogHelpers/TimelineFunctions.ps1 | 5 +
Calendar/Check-SharingStatus.ps1 | 4 +-
.../Get-CalendarDiagnosticObjectsSummary.ps1 | 10 +-
11 files changed, 292 insertions(+), 251 deletions(-)
create mode 100644 Calendar/CalLogHelpers/ExportToExcelFunctions.ps1
diff --git a/Calendar/CalLogHelpers/CalLogCSVFunctions.ps1 b/Calendar/CalLogHelpers/CalLogCSVFunctions.ps1
index b5bbd0282d..10b10d8e6b 100644
--- a/Calendar/CalLogHelpers/CalLogCSVFunctions.ps1
+++ b/Calendar/CalLogHelpers/CalLogCSVFunctions.ps1
@@ -19,6 +19,7 @@ $script:CalendarItemTypes = @{
'IPM.Schedule.Meeting.Resp.Neg' = "Resp.Neg"
'IPM.Schedule.Meeting.Resp.Tent' = "Resp.Tent"
'IPM.Schedule.Meeting.Resp.Pos' = "Resp.Pos"
+ '(Occurrence Deleted)' = "Exception.Deleted"
}
# ===================================================================================================
diff --git a/Calendar/CalLogHelpers/CalLogExportFunctions.ps1 b/Calendar/CalLogHelpers/CalLogExportFunctions.ps1
index bcf74bc913..469d451527 100644
--- a/Calendar/CalLogHelpers/CalLogExportFunctions.ps1
+++ b/Calendar/CalLogHelpers/CalLogExportFunctions.ps1
@@ -59,22 +59,6 @@ function Export-CalLogCSV {
$script:GCDO | Export-Csv -Path $FilenameRaw -NoTypeInformation -Encoding UTF8
}
-# Export to Excel
-function Export-CalLogExcel {
- Write-Host -ForegroundColor Cyan "Exporting Enhanced CalLogs to Excel Tab [$ShortId]..."
- $ExcelParamsArray = GetExcelParams -path $FileName -tabName $ShortId
-
- $excel = $GCDOResults | Export-Excel @ExcelParamsArray -PassThru
-
- FormatHeader ($excel)
-
- Export-Excel -ExcelPackage $excel -WorksheetName $ShortId -MoveToStart
-
- # Export Raw Logs for Developer Analysis
- Write-Host -ForegroundColor Cyan "Exporting Raw CalLogs to Excel Tab [$($ShortId + "_Raw")]..."
- $script:GCDO | Export-Excel -Path $FileName -WorksheetName $($ShortId + "_Raw") -AutoFilter -FreezeTopRow -BoldTopRow -MoveToEnd
-}
-
function Export-Timeline {
Write-Verbose "Export to Excel is : $ExportToExcel"
@@ -88,194 +72,3 @@ function Export-Timeline {
$script:TimeLineOutput | Export-Csv -Path $script:TimeLineFilename -NoTypeInformation -Encoding UTF8 -Append
}
}
-
-function Export-TimelineExcel {
- Write-Host -ForegroundColor Cyan "Exporting Timeline to Excel..."
- $script:TimeLineOutput | Export-Excel -Path $FileName -WorksheetName $($ShortId + "_TimeLine") -Title "Timeline for $Identity" -AutoSize -FreezeTopRow -BoldTopRow
-}
-
-function GetExcelParams($path, $tabName) {
- if ($script:IsOrganizer) {
- $TableStyle = "Light10" # Orange for Organizer
- $TitleExtra = ", Organizer"
- } elseif ($script:IsRoomMB) {
- Write-Host -ForegroundColor green "Room Mailbox Detected"
- $TableStyle = "Light11" # Green for Room Mailbox
- $TitleExtra = ", Resource"
- } else {
- $TableStyle = "Light12" # Light Blue for normal
- # Dark Blue for Delegates (once we can determine this)
- }
-
- return @{
- Path = $path
- FreezeTopRow = $true
- # BoldTopRow = $true
- Verbose = $false
- TableStyle = $TableStyle
- WorksheetName = $tabName
- TableName = $tabName
- FreezeTopRowFirstColumn = $true
- AutoFilter = $true
- AutoNameRange = $true
- Append = $true
- Title = "Enhanced Calendar Logs for $Identity" + $TitleExtra + " for MeetingID [$($script:GCDO[0].CleanGlobalObjectId)]."
- TitleSize = 14
- ConditionalText = $ConditionalFormatting
- }
-}
-
-# Need better way of tagging cells than the Range. Every time one is updated, you need to update all the ones after it.
-$ConditionalFormatting = $(
- # Client, ShortClientInfoString and LogClientInfoString
- New-ConditionalText "Outlook" -ConditionalTextColor Green -BackgroundColor $null
- New-ConditionalText "OWA" -ConditionalTextColor DarkGreen -BackgroundColor $null
- New-ConditionalText "Transport" -ConditionalTextColor Blue -BackgroundColor $null
- New-ConditionalText "Repair" -ConditionalTextColor DarkRed -BackgroundColor LightPink
- New-ConditionalText "Other ?BA" -ConditionalTextColor Orange -BackgroundColor $null
- New-ConditionalText "Other REST" -ConditionalTextColor DarkRed -BackgroundColor $null
- New-ConditionalText "ResourceBookingAssistant" -ConditionalTextColor Blue -BackgroundColor $null
-
- #LogType
- New-ConditionalText -Range "C3:C9999" -ConditionalType ContainsText -Text "Ignorable" -ConditionalTextColor DarkRed -BackgroundColor $null
- New-ConditionalText -Range "C:C" -ConditionalType ContainsText -Text "Cleanup" -ConditionalTextColor DarkRed -BackgroundColor $null
- New-ConditionalText -Range "C:C" -ConditionalType ContainsText -Text "Sharing" -ConditionalTextColor Blue -BackgroundColor $null
-
- # TriggerAction
- New-ConditionalText -Range "G:G" -ConditionalType ContainsText -Text "Create" -ConditionalTextColor Green -BackgroundColor $null
- New-ConditionalText -Range "G:G" -ConditionalType ContainsText -Text "Delete" -ConditionalTextColor Red -BackgroundColor $null
- # ItemClass
- New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "IPM.Appointment" -ConditionalTextColor Blue -BackgroundColor $null
- New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "Canceled" -ConditionalTextColor Black -BackgroundColor Orange
- New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text ".Request" -ConditionalTextColor DarkGreen -BackgroundColor $null
- New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text ".Resp." -ConditionalTextColor Orange -BackgroundColor $null
- New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "IPM.OLE.CLASS" -ConditionalTextColor Plum -BackgroundColor $null
-
- #FreeBusyStatus
- New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Free" -ConditionalTextColor Red -BackgroundColor $null
- New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Tentative" -ConditionalTextColor Orange -BackgroundColor $null
- New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Busy" -ConditionalTextColor Green -BackgroundColor $null
-
- #Shared Calendar information
- New-ConditionalText -Range "Q3:Q9999" -ConditionalType NotEqual -Text "Not Shared" -ConditionalTextColor Blue -BackgroundColor $null
- New-ConditionalText -Range "R:R" -ConditionalType ContainsText -Text "TRUE" -ConditionalTextColor Blue -BackgroundColor $null
- New-ConditionalText -Range "S:S" -ConditionalType NotEqual -Text "NotFound" -ConditionalTextColor Blue -BackgroundColor $null
-
- #MeetingRequestType
- New-ConditionalText -Range "V:V" -ConditionalType ContainsText -Text "Outdated" -ConditionalTextColor DarkRed -BackgroundColor LightPink
-
- #AppointmentAuxiliaryFlags
- New-ConditionalText -Range "AE3:AE9999" -ConditionalType ContainsText -Text "Copy" -ConditionalTextColor DarkRed -BackgroundColor LightPink
-
- #ResponseType
- New-ConditionalText -Range "AI3:AI9999" -ConditionalType ContainsText -Text "Organizer" -ConditionalTextColor Orange -BackgroundColor $null
-
-)
-
-function FormatHeader {
- param(
- [object] $excel
- )
- $sheet = $excel.Workbook.Worksheets[$ShortId]
- $HeaderRow = 2
- $n = 0
-
- # Static List of Columns for now...
- $sheet.Column(++$n) | Set-ExcelRange -Width 6 -HorizontalAlignment center # LogRow
- Set-CellComment -Text "This is the Enhanced Calendar Logs for [$Identity] for MeetingID `n [$($script:GCDO[0].CleanGlobalObjectId)]." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center #LogTimestamp
- Set-CellComment -Text "LogTimestamp: Time when the change was recorded in the CalLogs. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 11 -HorizontalAlignment center # LogType
- Set-CellComment -Text "LogType: Can this Log be safely ignored?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # SubjectProperty
- Set-CellComment -Text "SubjectProperty: The Subject of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Client
- Set-CellComment -Text "Client: The 'friendly' Client name of the client that made the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 5 -HorizontalAlignment Left # LogClientInfoString
- Set-CellComment -Text "LogClientInfoString: Full Client Info String of client that made the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 12 -HorizontalAlignment Center # TriggerAction
- Set-CellComment -Text "TriggerAction: The action that caused the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 18 -HorizontalAlignment Left # ItemClass
- Set-CellComment -Text "ItemClass: The Class of the Calendar Item" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # Seq:Exp:ItemVersion
- Set-CellComment -Text "Seq:Exp:ItemVersion: The Sequence Version, the Exception Version, and the Item Version. Each type of item has its own count." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Organizer
- Set-CellComment -Text "Organizer: The Organizer of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # From
- Set-CellComment -Text "From: The SMTP address of the Organizer of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 12 -HorizontalAlignment center # FreeBusyStatus
- Set-CellComment -Text "FreeBusyStatus: The FreeBusy Status of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # ResponsibleUser
- Set-CellComment -Text "ResponsibleUser: The Responsible User of the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Sender
- Set-CellComment -Text "Sender: The Sender of the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 16 -HorizontalAlignment Left # LogFolder
- Set-CellComment -Text "LogFolder: The Log Folder that the CalLog was in." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 16 -HorizontalAlignment Left # OriginalLogFolder
- Set-CellComment -Text "OriginalLogFolder: The Original Log Folder that the item was in / delivered to." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 15 -HorizontalAlignment Right # SharedFolderName
- Set-CellComment -Text "SharedFolderName: Was this from a Modern Sharing, and if so what Folder." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsFromSharedCalendar
- Set-CellComment -Text "IsFromSharedCalendar: Is this CalLog from a Modern Sharing relationship?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # ExternalSharingMasterId
- Set-CellComment -Text "ExternalSharingMasterId: If this is not [NotFound], then it is from a Modern Sharing relationship." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # ReceivedBy
- Set-CellComment -Text "ReceivedBy: The Receiver of the Calendar Item. Should always be the owner of the Mailbox." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # ReceivedRepresenting
- Set-CellComment -Text "ReceivedRepresenting: Who the item was Received for, of then the Delegate." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # MeetingRequestType
- Set-CellComment -Text "MeetingRequestType: The Meeting Request Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center # StartTime
- Set-CellComment -Text "StartTime: The Start Time of the Meeting. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center # EndTime
- Set-CellComment -Text "EndTime: The End Time of the Meeting. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 17 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment Left # OriginalStartDate
- Set-CellComment -Text "OriginalStartDate: The Original Start Date of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # TimeZone
- Set-CellComment -Text "TimeZone: The Time Zone of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # Location
- Set-CellComment -Text "Location: The Location of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # CalendarItemType
- Set-CellComment -Text "CalendarItemType: The Calendar Item Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsException
- Set-CellComment -Text "IsException: Is this an Exception?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # RecurrencePattern
- Set-CellComment -Text "RecurrencePattern: The Recurrence Pattern of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Center # AppointmentAuxiliaryFlags
- Set-CellComment -Text "AppointmentAuxiliaryFlags: The Appointment Auxiliary Flags of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Left # DisplayAttendeesAll
- Set-CellComment -Text "DisplayAttendeesAll: List of the Attendees of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # AttendeeCount
- Set-CellComment -Text "AttendeeCount: The Attendee Count." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # AppointmentState
- Set-CellComment -Text "AppointmentState: The Appointment State of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # ResponseType
- Set-CellComment -Text "ResponseType: The Response Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment center # ClientIntent
- Set-CellComment -Text "ClientIntent: The Client Intent of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # AppointmentRecurring
- Set-CellComment -Text "AppointmentRecurring: Is this a Recurring Meeting?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # HasAttachment
- Set-CellComment -Text "HasAttachment: Does this Meeting have an Attachment?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsCancelled
- Set-CellComment -Text "IsCancelled: Is this Meeting Cancelled?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsAllDayEvent
- Set-CellComment -Text "IsAllDayEvent: Is this an All Day Event?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsSeriesCancelled
- Set-CellComment -Text "IsSeriesCancelled: Is this a Series Cancelled Meeting?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Left # SendMeetingMessagesDiagnostics
- Set-CellComment -Text "SendMeetingMessagesDiagnostics: Compound Property to describe why meeting was or was not sent to everyone." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 50 -HorizontalAlignment Left # AttendeeCollection
- Set-CellComment -Text "AttendeeCollection: The Attendee Collection of the Meeting, use -TrackingLogs to get values." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 40 -HorizontalAlignment Center # CalendarLogRequestId
- Set-CellComment -Text "CalendarLogRequestId: The Calendar Log Request ID of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
- $sheet.Column(++$n) | Set-ExcelRange -Width 120 -HorizontalAlignment Left # CleanGlobalObjectId
- Set-CellComment -Text "CleanGlobalObjectId: The Clean Global Object ID of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
-
- # Update header rows after all the others have been set.
- # Title Row
- $sheet.Row(1) | Set-ExcelRange -HorizontalAlignment Left
-
- # Set the Header row to be bold and left aligned
- $sheet.Row($HeaderRow) | Set-ExcelRange -Bold -HorizontalAlignment Left
-}
diff --git a/Calendar/CalLogHelpers/CalLogInfoFunctions.ps1 b/Calendar/CalLogHelpers/CalLogInfoFunctions.ps1
index 90d36ce7ef..fccfcd6897 100644
--- a/Calendar/CalLogHelpers/CalLogInfoFunctions.ps1
+++ b/Calendar/CalLogHelpers/CalLogInfoFunctions.ps1
@@ -32,24 +32,22 @@ function SetIsRoom {
param(
$CalLogs
)
- [bool] $IsRoom = $false
+ # See if we have already determined this is a Room MB.
if ($script:Rooms -contains $Identity) {
- $IsRoom = $true
- return $IsRoom
+ return $true
}
# Simple logic is if RBA is running on the MB, it is a Room MB, otherwise it is not.
foreach ($CalLog in $CalLogs) {
- Write-Verbose "Checking if this is a Room Mailbox. [$($CalLog.ItemType)] [$($CalLog.ExternalSharingMasterId)] [$($CalLog.LogClientInfoString)]"
- if ($CalLog.ItemType -eq "IPM.Appointment" -and
+ Write-Verbose "Checking if this is a Room Mailbox. [$($CalLog.ItemClass)] [$($CalLog.ExternalSharingMasterId)] [$($CalLog.LogClientInfoString)]"
+ if ($CalLog.ItemClass -eq "IPM.Appointment" -and
$CalLog.ExternalSharingMasterId -eq "NotFound" -and
$CalLog.LogClientInfoString -like "*ResourceBookingAssistant*" ) {
- $IsRoom = $true
- return $IsRoom
+ return $true
}
}
- return $IsRoom
+ return $false
}
<#
diff --git a/Calendar/CalLogHelpers/ExcelModuleInstaller.ps1 b/Calendar/CalLogHelpers/ExcelModuleInstaller.ps1
index bac893f8af..3f542ee0c6 100644
--- a/Calendar/CalLogHelpers/ExcelModuleInstaller.ps1
+++ b/Calendar/CalLogHelpers/ExcelModuleInstaller.ps1
@@ -2,7 +2,8 @@
# Licensed under the MIT License.
# ===================================================================================================
-# Excel Functions
+# ImportExcel Functions
+# see https://github.com/dfinke/ImportExcel for information on the module.
# ===================================================================================================
function CheckExcelModuleInstalled {
[CmdletBinding(SupportsShouldProcess=$true)]
diff --git a/Calendar/CalLogHelpers/ExportToExcelFunctions.ps1 b/Calendar/CalLogHelpers/ExportToExcelFunctions.ps1
new file mode 100644
index 0000000000..b7153135a1
--- /dev/null
+++ b/Calendar/CalLogHelpers/ExportToExcelFunctions.ps1
@@ -0,0 +1,212 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# Export to Excel
+function Export-CalLogExcel {
+ Write-Host -ForegroundColor Cyan "Exporting Enhanced CalLogs to Excel Tab [$ShortId]..."
+ $ExcelParamsArray = GetExcelParams -path $FileName -tabName $ShortId
+
+ $excel = $GCDOResults | Export-Excel @ExcelParamsArray -PassThru
+
+ FormatHeader ($excel)
+
+ Export-Excel -ExcelPackage $excel -WorksheetName $ShortId -MoveToStart
+
+ # Export Raw Logs for Developer Analysis
+ Write-Host -ForegroundColor Cyan "Exporting Raw CalLogs to Excel Tab [$($ShortId + "_Raw")]..."
+ $script:GCDO | Export-Excel -Path $FileName -WorksheetName $($ShortId + "_Raw") -AutoFilter -FreezeTopRow -BoldTopRow -MoveToEnd
+}
+
+function Export-TimelineExcel {
+ Write-Host -ForegroundColor Cyan "Exporting Timeline to Excel..."
+ $script:TimeLineOutput | Export-Excel -Path $FileName -WorksheetName $($ShortId + "_TimeLine") -Title "Timeline for $Identity" -AutoSize -FreezeTopRow -BoldTopRow
+}
+
+function GetExcelParams($path, $tabName) {
+ if ($script:IsOrganizer) {
+ $TableStyle = "Light10" # Orange for Organizer
+ $TitleExtra = ", Organizer"
+ } elseif ($script:IsRoomMB) {
+ Write-Host -ForegroundColor green "Room Mailbox Detected"
+ $TableStyle = "Light11" # Green for Room Mailbox
+ $TitleExtra = ", Resource"
+ } else {
+ $TableStyle = "Light12" # Light Blue for normal
+ # Dark Blue for Delegates (once we can determine this)
+ }
+
+ if ($script:CalLogsDisabled) {
+ $TitleExtra += ", WARNING: CalLogs are Turned Off for $Identity! This will be a incomplete story"
+ }
+
+ return @{
+ Path = $path
+ FreezeTopRow = $true
+ # BoldTopRow = $true
+ Verbose = $false
+ TableStyle = $TableStyle
+ WorksheetName = $tabName
+ TableName = $tabName
+ FreezeTopRowFirstColumn = $true
+ AutoFilter = $true
+ AutoNameRange = $true
+ Append = $true
+ Title = "Enhanced Calendar Logs for $Identity" + $TitleExtra + " for MeetingID [$($script:GCDO[0].CleanGlobalObjectId)]."
+ TitleSize = 14
+ ConditionalText = $ConditionalFormatting
+ }
+}
+
+# Need better way of tagging cells than the Range. Every time one is updated, you need to update all the ones after it.
+$ConditionalFormatting = $(
+ # Client, ShortClientInfoString and LogClientInfoString
+ New-ConditionalText "Outlook" -ConditionalTextColor Green -BackgroundColor $null
+ New-ConditionalText "OWA" -ConditionalTextColor DarkGreen -BackgroundColor $null
+ New-ConditionalText "Transport" -ConditionalTextColor Blue -BackgroundColor $null
+ New-ConditionalText "Repair" -ConditionalTextColor DarkRed -BackgroundColor LightPink
+ New-ConditionalText "Other ?BA" -ConditionalTextColor Orange -BackgroundColor $null
+ New-ConditionalText "Other REST" -ConditionalTextColor DarkRed -BackgroundColor $null
+ New-ConditionalText "ResourceBookingAssistant" -ConditionalTextColor Blue -BackgroundColor $null
+
+ #LogType
+ New-ConditionalText -Range "C3:C9999" -ConditionalType ContainsText -Text "Ignorable" -ConditionalTextColor DarkRed -BackgroundColor $null
+ New-ConditionalText -Range "C:C" -ConditionalType ContainsText -Text "Cleanup" -ConditionalTextColor DarkRed -BackgroundColor $null
+ New-ConditionalText -Range "C:C" -ConditionalType ContainsText -Text "Sharing" -ConditionalTextColor Blue -BackgroundColor $null
+
+ # TriggerAction
+ New-ConditionalText -Range "G:G" -ConditionalType ContainsText -Text "Create" -ConditionalTextColor Green -BackgroundColor $null
+ New-ConditionalText -Range "G:G" -ConditionalType ContainsText -Text "Delete" -ConditionalTextColor Red -BackgroundColor $null
+ # ItemClass
+ New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "IPM.Appointment" -ConditionalTextColor Blue -BackgroundColor $null
+ New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "Cancellation" -ConditionalTextColor Black -BackgroundColor Orange
+ New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text ".Request" -ConditionalTextColor DarkGreen -BackgroundColor $null
+ New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text ".Resp." -ConditionalTextColor Orange -BackgroundColor $null
+ New-ConditionalText -Range "H:H" -ConditionalType ContainsText -Text "IPM.OLE.CLASS" -ConditionalTextColor Plum -BackgroundColor $null
+
+ #FreeBusyStatus
+ New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Free" -ConditionalTextColor Red -BackgroundColor $null
+ New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Tentative" -ConditionalTextColor Orange -BackgroundColor $null
+ New-ConditionalText -Range "L3:L9999" -ConditionalType ContainsText -Text "Busy" -ConditionalTextColor Green -BackgroundColor $null
+
+ #Shared Calendar information
+ New-ConditionalText -Range "Q3:Q9999" -ConditionalType NotEqual -Text "Not Shared" -ConditionalTextColor Blue -BackgroundColor $null
+ New-ConditionalText -Range "R:R" -ConditionalType ContainsText -Text "TRUE" -ConditionalTextColor Blue -BackgroundColor $null
+ New-ConditionalText -Range "S:S" -ConditionalType NotEqual -Text "NotFound" -ConditionalTextColor Blue -BackgroundColor $null
+
+ #MeetingRequestType
+ New-ConditionalText -Range "V:V" -ConditionalType ContainsText -Text "Outdated" -ConditionalTextColor DarkRed -BackgroundColor LightPink
+
+ #AppointmentAuxiliaryFlags
+ New-ConditionalText -Range "AE3:AE9999" -ConditionalType ContainsText -Text "Copied" -ConditionalTextColor DarkRed -BackgroundColor LightPink
+
+ #ResponseType
+ New-ConditionalText -Range "AI3:AI9999" -ConditionalType ContainsText -Text "Organizer" -ConditionalTextColor Orange -BackgroundColor $null
+)
+
+function FormatHeader {
+ param(
+ [object] $excel
+ )
+ $sheet = $excel.Workbook.Worksheets[$ShortId]
+ $HeaderRow = 2
+ $n = 0
+
+ # Static List of Columns for now...
+ $sheet.Column(++$n) | Set-ExcelRange -Width 6 -HorizontalAlignment center # LogRow
+ Set-CellComment -Text "This is the Enhanced Calendar Logs for [$Identity] for MeetingID `n [$($script:GCDO[0].CleanGlobalObjectId)]." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center #LogTimestamp
+ Set-CellComment -Text "LogTimestamp: Time when the change was recorded in the CalLogs. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 11 -HorizontalAlignment center # LogType
+ Set-CellComment -Text "LogType: Grouping of logs so ignorable ones can be quickly filtered?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # SubjectProperty
+ Set-CellComment -Text "SubjectProperty: The Subject of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Client
+ Set-CellComment -Text "Client (ShortClientInfoString): The 'friendly' Client name of the client that made the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 5 -HorizontalAlignment Left # LogClientInfoString
+ Set-CellComment -Text "LogClientInfoString: Full Client Info String of client that made the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 12 -HorizontalAlignment Center # TriggerAction
+ Set-CellComment -Text "TriggerAction (CalendarLogTriggerAction): The type of action that caused the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 18 -HorizontalAlignment Left # ItemClass
+ Set-CellComment -Text "ItemClass: The Class of the Calendar Item" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # Seq:Exp:ItemVersion
+ Set-CellComment -Text "Seq:Exp:ItemVersion (AppointmentLastSequenceNumber:AppointmentSequenceNumber:ItemVersion): The Sequence Version, the Exception Version, and the Item Version. Each type of item has its own count." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Organizer
+ Set-CellComment -Text "Organizer (From.FriendlyDisplayName): The Organizer of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # From
+ Set-CellComment -Text "From: The SMTP address of the Organizer of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 12 -HorizontalAlignment center # FreeBusyStatus
+ Set-CellComment -Text "FreeBusy (FreeBusyStatus): The FreeBusy Status of the Calendar Item." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # ResponsibleUser
+ Set-CellComment -Text "ResponsibleUser(ResponsibleUserName): The Responsible User of the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # Sender
+ Set-CellComment -Text "Sender (SenderEmailAddress): The Sender of the change." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 16 -HorizontalAlignment Left # LogFolder
+ Set-CellComment -Text "LogFolder (ParentDisplayName): The Log Folder that the CalLog was in." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 16 -HorizontalAlignment Left # OriginalLogFolder
+ Set-CellComment -Text "OriginalLogFolder (OriginalParentDisplayName): The Original Log Folder that the item was in / delivered to." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 15 -HorizontalAlignment Right # SharedFolderName
+ Set-CellComment -Text "SharedFolderName: Was this from a Modern Sharing, and if so what Folder." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsFromSharedCalendar
+ Set-CellComment -Text "IsFromSharedCalendar: Is this CalLog from a Modern Sharing relationship?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # ExternalSharingMasterId
+ Set-CellComment -Text "ExternalSharingMasterId: If this is not [NotFound], then it is from a Modern Sharing relationship." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # ReceivedBy
+ Set-CellComment -Text "ReceivedBy: The Receiver of the Calendar Item. Should always be the owner of the Mailbox." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # ReceivedRepresenting
+ Set-CellComment -Text "ReceivedRepresenting: Who the item was Received for, of then the Delegate." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # MeetingRequestType
+ Set-CellComment -Text "MeetingRequestType: The Meeting Request Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center # StartTime
+ Set-CellComment -Text "StartTime: The Start Time of the Meeting. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment center # EndTime
+ Set-CellComment -Text "EndTime: The End Time of the Meeting. This and all Times are in UTC." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 17 -NumberFormat "m/d/yyyy h:mm:ss" -HorizontalAlignment Left # OriginalStartDate
+ Set-CellComment -Text "OriginalStartDate: The Original Start Date of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # TimeZone
+ Set-CellComment -Text "TimeZone: The Time Zone of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment Left # Location
+ Set-CellComment -Text "Location: The Location of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # CalendarItemType
+ Set-CellComment -Text "CalendarItemType: The Calendar Item Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsException
+ Set-CellComment -Text "IsException: Is this an Exception?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # RecurrencePattern
+ Set-CellComment -Text "RecurrencePattern: The Recurrence Pattern of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Center # AppointmentAuxiliaryFlags
+ Set-CellComment -Text "AppointmentAuxiliaryFlags: The Appointment Auxiliary Flags of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Left # DisplayAttendeesAll
+ Set-CellComment -Text "DisplayAttendeesAll: List of the Attendees of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # AttendeeCount
+ Set-CellComment -Text "AttendeeCount: The Attendee Count." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment Left # AppointmentState
+ Set-CellComment -Text "AppointmentState: The Appointment State of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # ResponseType
+ Set-CellComment -Text "ResponseType: The Response Type of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 20 -HorizontalAlignment center # ClientIntent
+ Set-CellComment -Text "ClientIntent: The Client Intent of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # AppointmentRecurring
+ Set-CellComment -Text "AppointmentRecurring: Is this a Recurring Meeting?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # HasAttachment
+ Set-CellComment -Text "HasAttachment: Does this Meeting have an Attachment?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsCancelled
+ Set-CellComment -Text "IsCancelled: Is this Meeting Cancelled?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsAllDayEvent
+ Set-CellComment -Text "IsAllDayEvent: Is this an All Day Event?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 10 -HorizontalAlignment center # IsSeriesCancelled
+ Set-CellComment -Text "IsSeriesCancelled: Is this a Series Cancelled Meeting?" -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 30 -HorizontalAlignment Left # SendMeetingMessagesDiagnostics
+ Set-CellComment -Text "SendMeetingMessagesDiagnostics: Compound Property to describe why meeting was or was not sent to everyone." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 50 -HorizontalAlignment Left # AttendeeCollection
+ Set-CellComment -Text "AttendeeCollection: The Attendee Collection of the Meeting, use -TrackingLogs to get values." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 40 -HorizontalAlignment Center # CalendarLogRequestId
+ Set-CellComment -Text "CalendarLogRequestId: The Calendar Log Request ID of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+ $sheet.Column(++$n) | Set-ExcelRange -Width 120 -HorizontalAlignment Left # CleanGlobalObjectId
+ Set-CellComment -Text "CleanGlobalObjectId: The Clean Global Object ID of the Meeting." -Row $HeaderRow -ColumnNumber $n -Worksheet $sheet
+
+ # Update header rows after all the others have been set.
+ # Title Row
+ $sheet.Row(1) | Set-ExcelRange -HorizontalAlignment Left
+
+ # Set the Header row to be bold and left aligned
+ $sheet.Row($HeaderRow) | Set-ExcelRange -Bold -HorizontalAlignment Left
+}
diff --git a/Calendar/CalLogHelpers/Invoke-GetCalLogs.ps1 b/Calendar/CalLogHelpers/Invoke-GetCalLogs.ps1
index b3df2d1ab4..fdc4843d12 100644
--- a/Calendar/CalLogHelpers/Invoke-GetCalLogs.ps1
+++ b/Calendar/CalLogHelpers/Invoke-GetCalLogs.ps1
@@ -30,6 +30,7 @@ $script:CustomPropertyNameList =
"SendMeetingMessagesDiagnostics",
"SentRepresentingDisplayName",
"SentRepresentingEmailAddress",
+"Sensitivity",
"LogTimestamp",
"LogClientInfoString",
"OriginalStartDate",
diff --git a/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1 b/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
index 801e435f11..616429cb06 100644
--- a/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
+++ b/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
@@ -5,7 +5,6 @@ $WellKnownCN_CA = "MICROSOFT SYSTEM ATTENDANT"
$CalAttendant = "Calendar Assistant"
$WellKnownCN_Trans = "MicrosoftExchange"
$Transport = "Transport Service"
-
<#
.SYNOPSIS
Get the Mailbox for the Passed in Identity.
@@ -18,46 +17,59 @@ Might want to extend to do 'Get-MailUser' as well.
function GetMailbox {
param(
[string]$Identity,
- [string]$Organization
+ [string]$Organization,
+ [bool]$UseGetMailbox
)
- try {
- Write-Verbose "Searching Get-Mailbox $(if (-not ([string]::IsNullOrEmpty($Organization))) {"with Org: $Organization"}) for $Identity."
+ if ($UseGetMailbox) {
+ $Cmdlet = "Get-Mailbox"
+ } else {
+ $Cmdlet = "Get-Recipient"
+ }
+ $params = @{Identity = $Identity
+ ErrorAction = "SilentlyContinue"
+ }
- if ($Identity -and $Organization) {
- if ($script:MSSupport) {
- Write-Verbose "Using Organization parameter"
- $GetMailboxOutput = Get-Mailbox -Identity $Identity -Organization $Organization -ErrorAction SilentlyContinue
- } else {
- Write-Verbose "Using -OrganizationalUnit parameter"
- $GetMailboxOutput = Get-Mailbox -Identity $Identity -OrganizationalUnit $Organization -ErrorAction SilentlyContinue
- }
- } else {
- $GetMailboxOutput = Get-Mailbox -Identity $Identity -ErrorAction SilentlyContinue
+ try {
+ Write-Verbose "Searching $Cmdlet $(if (-not ([string]::IsNullOrEmpty($Organization))) {"with Org: $Organization"}) for $Identity."
+
+ if (-not ([string]::IsNullOrEmpty($Organization)) -and $script:MSSupport) {
+ Write-Verbose "Using Organization parameter"
+ $params.Add("Organization", $Organization)
+ } elseif (-not ([string]::IsNullOrEmpty($Organization))) {
+ Write-Verbose "Using -OrganizationalUnit parameter with $Organization."
+ $params.Add("Organization", $Organization)
}
- if (!$GetMailboxOutput) {
+ Write-Verbose "Running $Cmdlet with params: $($params.Values)"
+ $RecipientOutput = & $Cmdlet @params
+ Write-Verbose "RecipientOutput: $RecipientOutput"
+
+ if (!$RecipientOutput) {
Write-Host "Unable to find [$Identity]$(if ($Organization -ne `"`" ) {" in Organization:[$Organization]"})."
Write-Host "Trying to find a Group Mailbox for [$Identity]..."
- $GetMailboxOutput = Get-Mailbox -Identity $Identity -ErrorAction SilentlyContinue -GroupMailbox
- if (!$GetMailboxOutput) {
+ $RecipientOutput = Get-Mailbox -Identity $Identity -ErrorAction SilentlyContinue -GroupMailbox
+ if (!$RecipientOutput) {
Write-Host "Unable to find a Group Mailbox for [$Identity] either."
return $null
} else {
- Write-Verbose "Found GroupMailbox [$($GetMailboxOutput.DisplayName)]"
+ Write-Verbose "Found GroupMailbox [$($RecipientOutput.DisplayName)]"
}
- } else {
- Write-Verbose "Found [$($GetMailboxOutput.DisplayName)]"
}
- if (CheckForNoPIIAccess($script:GetMailboxOutput.DisplayName)) {
- Write-Host -ForegroundColor Magenta "No PII Access for [$Identity]"
+ if ($null -eq $script:PIIAccess) {
+ [bool]$script:PIIAccess = CheckForPIIAccess($RecipientOutput.DisplayName)
+ }
+
+ if ($script:PIIAccess) {
+ Write-Verbose "Found [$($RecipientOutput.DisplayName)]"
} else {
- Write-Verbose "Found [$($GetMailboxOutput.DisplayName)]"
+ Write-Host -ForegroundColor Magenta "No PII Access for [$Identity]"
}
- return $GetMailboxOutput
+
+ return $RecipientOutput
} catch {
- Write-Error "An error occurred while running Get-Mailbox: [$_]"
+ Write-Error "An error occurred while running ${Cmdlet}: [$_]"
}
}
@@ -84,13 +96,13 @@ function CheckIdentities {
Write-Host "Preparing to check $($Identity.count) Mailbox(es)..."
foreach ($Id in $Identity) {
- $Account = GetMailbox -Identity $Id
+ $Account = GetMailbox -Identity $Id -UseGetMailbox $true
if ($null -eq $Account) {
# -or $script:MB.GetType().FullName -ne "Microsoft.Exchange.Data.Directory.Management.Mailbox") {
Write-DashLineBoxColor "`n Error: Mailbox [$Id] not found on Exchange Online. Please validate the mailbox name and try again.`n" -Color Red
continue
}
- if (CheckForNoPIIAccess $Account.DisplayName) {
+ if (-not (CheckForPIIAccess($Account.DisplayName))) {
Write-Host -ForegroundColor DarkRed "No PII access for Mailbox [$Id]. Falling back to SMTP Address."
$IdentityList += $ID
if ($null -eq $script:MB) {
@@ -104,11 +116,16 @@ function CheckIdentities {
}
}
if ($Account.CalendarVersionStoreDisabled -eq $true) {
+ [bool]$script:CalLogsDisabled = $true
Write-Host -ForegroundColor DarkRed "Mailbox [$Id] has CalendarVersionStoreDisabled set to True. This mailbox will not have Calendar Logs."
Write-Host -ForegroundColor DarkRed "Some logs will be available for Mailbox [$Id] but they will not be complete."
}
if ($Account.RecipientTypeDetails -eq "RoomMailbox" -or $Account.RecipientTypeDetails -eq "EquipmentMailbox") {
- $script:Rooms += $Account.PrimarySmtpAddress.ToString()
+ if ($script:PIIAccess -eq $true) {
+ $script:Rooms += $Account.PrimarySmtpAddress.ToString()
+ } else {
+ $script:Rooms += $Id
+ }
Write-Host -ForegroundColor Green "[$Id] is a Room / Equipment Mailbox."
}
}
@@ -262,14 +279,14 @@ function BetterThanNothingCNConversion {
.SYNOPSIS
Checks if an entries is Redacted to protect PII.
#>
-function CheckForNoPIIAccess {
+function CheckForPIIAccess {
param(
$PassedString
)
if ($PassedString -match "REDACTED-") {
- return $true
- } else {
return $false
+ } else {
+ return $true
}
}
@@ -314,7 +331,7 @@ function GetMailboxProp {
}
Write-Verbose "`t GetMailboxProp:[$Prop] :Found::[$ReturnValue]"
- if (CheckForNoPIIAccess($ReturnValue)) {
+ if (-not (CheckForPIIAccess($ReturnValue))) {
Write-Verbose "No PII Access for [$ReturnValue]"
return BetterThanNothingCNConversion($PassedCN)
}
diff --git a/Calendar/CalLogHelpers/ShortClientNameFunctions.ps1 b/Calendar/CalLogHelpers/ShortClientNameFunctions.ps1
index 914ef5eb4b..2953c3d590 100644
--- a/Calendar/CalLogHelpers/ShortClientNameFunctions.ps1
+++ b/Calendar/CalLogHelpers/ShortClientNameFunctions.ps1
@@ -58,6 +58,11 @@ function CreateShortClientName {
return $ShortClientName
}
+ if ($LogClientInfoString -like "*EDiscoverySearch*") {
+ $ShortClientName = "EDiscoverySearch"
+ return $ShortClientName
+ }
+
if ($LogClientInfoString -like "Client=EBA*" -or $LogClientInfoString -like "Client=TBA*") {
if ($LogClientInfoString -like "*ResourceBookingAssistant*") {
$ShortClientName = "ResourceBookingAssistant"
diff --git a/Calendar/CalLogHelpers/TimelineFunctions.ps1 b/Calendar/CalLogHelpers/TimelineFunctions.ps1
index eb22ce5cbb..aa40f1f67a 100644
--- a/Calendar/CalLogHelpers/TimelineFunctions.ps1
+++ b/Calendar/CalLogHelpers/TimelineFunctions.ps1
@@ -75,6 +75,11 @@ function BuildTimeline {
Write-Host "Found $($script:EnhancedCalLogs.count) Log entries, only the $($InterestingCalLogs.count) Non-Ignorable entries will be analyzed in the TimeLine. `n"
}
+ if ($script:CalLogsDisabled) {
+ Write-Host -ForegroundColor Red "Warning: CalLogs are disabled for this user, Timeline / CalLogs will be incomplete."
+ return
+ }
+
Write-DashLineBoxColor " TimeLine for: [$Identity]",
" Subject: $($script:GCDO[0].NormalizedSubject)",
" Organizer: $Script:Organizer",
diff --git a/Calendar/Check-SharingStatus.ps1 b/Calendar/Check-SharingStatus.ps1
index c11b57880b..f16aedd58c 100644
--- a/Calendar/Check-SharingStatus.ps1
+++ b/Calendar/Check-SharingStatus.ps1
@@ -429,9 +429,9 @@ function GetReceiverInformation {
# need to check if Get-CalendarValidationResult in the PS Workspace
if ((Get-Command -Name Get-CalendarValidationResult -ErrorAction SilentlyContinue) -and
$null -ne $ReceiverCalEntries) {
- Write-Host "Running cmdlet: Get-CalendarValidationResult -Version V2 -Identity $Receiver -SourceCalendarId $($ReceiverCalEntries[0].LocalFolderId) -TargetUserId $Owner -IncludeAnalysis 1 -OnlyReportErrors 1"
$ewsId_del= $ReceiverCalEntries[0].LocalFolderId
- Get-CalendarValidationResult -Version V2 -Identity $Receiver -SourceCalendarId $ewsId_del -TargetUserId $Owner -IncludeAnalysis 1 -OnlyReportErrors 1
+ Write-Host "Running cmdlet: Get-CalendarValidationResult -Version V2 -Identity $Receiver -SourceCalendarId $ewsId_del -TargetUserId $Owner -IncludeAnalysis 1 -OnlyReportErrors 1 | FT -a GlobalObjectId, EventValidationResult "
+ Get-CalendarValidationResult -Version V2 -Identity $Receiver -SourceCalendarId $ewsId_del -TargetUserId $Owner -IncludeAnalysis 1 -OnlyReportErrors 1 | Format-List UserPrimarySMTPAddress, Subject, GlobalObjectId, EventValidationResult, EventComparisonResult
}
}
diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1
index 8c4acac385..8684ad28f6 100644
--- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1
+++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1
@@ -26,6 +26,9 @@
# .PARAMETER CaseNumber
# Case Number to include in the Filename of the output.
#
+# .PARAMETER ShortLogs
+# Limit Logs to 500 instead of the default 2000, in case the server has trouble responding with the full logs.
+#
# .EXAMPLE
# Get-CalendarDiagnosticObjectsSummary.ps1 -Identity someuser@microsoft.com -MeetingID 040000008200E00074C5B7101A82E008000000008063B5677577D9010000000000000000100000002FCDF04279AF6940A5BFB94F9B9F73CD
#
@@ -45,6 +48,7 @@ param (
[string[]]$Identity,
[switch]$ExportToExcel,
[string]$CaseNumber,
+ [switch]$ShortLogs,
[Parameter(Mandatory, ParameterSetName = 'MeetingID', Position = 1)]
[string]$MeetingID,
@@ -79,11 +83,15 @@ Write-Verbose "Script Versions: $BuildVersion"
. $PSScriptRoot\CalLogHelpers\ShortClientNameFunctions.ps1
. $PSScriptRoot\CalLogHelpers\CalLogInfoFunctions.ps1
. $PSScriptRoot\CalLogHelpers\CalLogExportFunctions.ps1
-. $PSScriptRoot\CalLogHelpers\ExcelModuleInstaller.ps1
. $PSScriptRoot\CalLogHelpers\CreateTimelineRow.ps1
. $PSScriptRoot\CalLogHelpers\FindChangedPropFunctions.ps1
. $PSScriptRoot\CalLogHelpers\Write-DashLineBoxColor.ps1
+if ($ExportToExcel.IsPresent) {
+ . $PSScriptRoot\CalLogHelpers\ExcelModuleInstaller.ps1
+ . $PSScriptRoot\CalLogHelpers\ExportToExcelFunctions.ps1
+}
+
# ===================================================================================================
# Main
# ===================================================================================================
From 1d9e7e6bbd1e5994d82fe9e397b67b7d4fae36e1 Mon Sep 17 00:00:00 2001
From: Shane Ferrell
Date: Tue, 16 Jul 2024 08:20:34 -0700
Subject: [PATCH 7/7] Minor change to rerun validation
---
Calendar/CalLogHelpers/Invoke-GetMailbox.ps1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1 b/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
index 616429cb06..ea894111bd 100644
--- a/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
+++ b/Calendar/CalLogHelpers/Invoke-GetMailbox.ps1
@@ -64,7 +64,7 @@ function GetMailbox {
if ($script:PIIAccess) {
Write-Verbose "Found [$($RecipientOutput.DisplayName)]"
} else {
- Write-Host -ForegroundColor Magenta "No PII Access for [$Identity]"
+ Write-Verbose "No PII Access for [$Identity]"
}
return $RecipientOutput