ORCA.psm1

#Requires -Version 5.1

<#
    .SYNOPSIS
        The Office 365 Recommended Configuration Analyzer (ORCA)
 
    .DESCRIPTION
        
 
    .NOTES
        Cam Murray
        Field Engineer - Microsoft
        camurray@microsoft.com
 
        Output report uses open source components for HTML formatting
        - bootstrap - MIT License - https://getbootstrap.com/docs/4.0/about/license/
        - fontawesome - CC BY 4.0 License - https://fontawesome.com/license/free
         
        ############################################################################
 
        This sample script is not supported under any Microsoft standard support program or service.
        This sample script is provided AS IS without warranty of any kind.
        Microsoft further disclaims all implied warranties including, without limitation, any implied
        warranties of merchantability or of fitness for a particular purpose. The entire risk arising
        out of the use or performance of the sample script and documentation remains with you. In no
        event shall Microsoft, its authors, or anyone else involved in the creation, production, or
        delivery of the scripts be liable for any damages whatsoever (including, without limitation,
        damages for loss of business profits, business interruption, loss of business information,
        or other pecuniary loss) arising out of the use of or inability to use the sample script or
        documentation, even if Microsoft has been advised of the possibility of such damages.
 
        ############################################################################
 
    .LINK
        about_functions_advanced
 
#>


function Get-ORCADirectory
{
    <#
        Gets or creates the ORCA directory in AppData
    #>


    $Directory = "$($env:LOCALAPPDATA)\Microsoft\ORCA"

    If(Test-Path $Directory) 
    {
        Return $Directory
    }
    else 
    {
        mkdir $Directory | out-null
        Return $Directory
    }

}

<#
 
    Check modules
 
#>


function Invoke-CheckAllowedSenderDomains {
    Param(
        $ContentFilterPolicies
    )

    $Check = "Content Filter AllowedSenderDomains"
    $CID = "118-1"

    $return = @()

    ForEach($Policy in $ContentFilterPolicies) {

        # Fail if AllowedSenderDomains is not null

        If(($Policy.AllowedSenderDomains).Count -gt 0) {
            ForEach($Domain in $Policy.AllowedSenderDomains) {
                $return += New-Object -TypeName psobject -Property @{
                    Result="Fail"
                    Check=$Check
                    ConfigItem=$($Policy.Name)
                    ConfigData="$($Domain.Domain)"
                    Rule="AllowedSenderDomains is not empty"
                    Control=$CID
                } 
            }
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="0 Allowed Sender Domains"
                Rule="AllowedSenderDomains is empty"
                Control=$CID
            } 
        }
    }

    return $return        
}

function Invoke-CheckZAP {
    Param(
        $MalwareFilterPolicies,
        $ContentFilterPolicies
    )

    $Check = "ZAP"

    $return = @()

    ForEach($Policy in $MalwareFilterPolicies) {

        if($Policy.ZapEnabled -eq $true) {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Rule="ZAP Malware Enabled"
                Control="120-malware"
            } 
        } else {
            $return +=  New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Control="120-malware"
                SupplementText="Zero Hour Autopurge for Malware is disabled in the Malware Policy $($Policy.Name)"
            }
        }
    }

    ForEach($Policy in $ContentFilterPolicies) {
        if($Policy.ZapEnabled -eq $true) {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Rule="ZAP Phishing/Spam Enabled"
                Control="120-spam"
            } 
        } else {
            $return +=  New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Rule="ZAP Phishing/Spam Disabled"
                Control="120-spam"
            }
        }
        # Check requirement of Spam ZAP - MoveToJmf, redirect, delete, quarantine
        If($Policy.SpamAction -eq "MoveToJmf" -or $Policy.SpamAction -eq "Redirect" -or $Policy.SpamAction -eq "Delete" -or $Policy.SpamAction -eq "Quarantine") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.SpamAction)
                Rule="SpamAction set to an action necessary to move to JMF- ZAP Requirement"
                Control="121"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.SpamAction)
                SupplementText="Spam Action on policy $($Policy.Name) is set to $($Policy.SpamAction)"
                Control="121"
            }             
        }
        # Check requirement of Phish ZAP - MoveToJmf, redirect, delete, quarantine
        If($Policy.PhishSpamAction -eq "MoveToJmf" -or $Policy.PhishSpamAction -eq "Redirect" -or $Policy.PhishSpamAction -eq "Delete" -or $Policy.PhishSpamAction -eq "Quarantine") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishSpamAction)
                Rule="PhishSpamAction set to an action necessary to move to JMF - ZAP Requirement"
                Control="121"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishSpamAction)
                Rule="PhishSpamAction not set to an action necessary to move to JMF- ZAP Requirement"
                Control="121"
            }           
        }

    }

    return $return
}

function Invoke-CheckIPAllowList {
    Param(
        $HostedConnectionFilterPolicies
    )

    $Check = "IP Allow List Size"
    $Return = @()

    ForEach($HostedConnectionFilterPolicy in $HostedConnectionFilterPolicies) {
        # Check if IPAllowList < 0 and return inconclusive for manual checking of size
        If($HostedConnectionFilterPolicy.IPAllowList.Count -gt 0) {
            # IP Allow list present
            ForEach($IPAddr in @($HostedConnectionFilterPolicy.IPAllowList)) {
                $Return += New-Object -TypeName psobject -Property @{
                    Result="Fail"
                    Check=$Check
                    ConfigItem=$($HostedConnectionFilterPolicy.Name)
                    ConfigData=$IPAddr
                    Rule="IP Allow List contains too many IPs"
                    Control="114"
                }    
            }

        } else {
            # IPAllowList is blank, so pass.
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($HostedConnectionFilterPolicy.Name)
                ConfigData="IP Entries $($HostedConnectionFilterPolicy.IPAllowList.Count)"
                Rule="IP Allow List empty"
                Control="114"
            }
        }
    }

    return $Return
}

function Invoke-CheckATPForSPOTeamsODB {
    <#
     
    158
 
    Checks to determine if ATP is enabled for SharePoint, Teams, and OD4B as per 'tickbox' in
    the ATP configuration.
     
    #>

    Param(
        $ATPPolicy,
        $Questions
    )

    $Check = "ATP"
    $Cid = "158"
    
    $Return = @()

        ForEach($Policy in $ATPPolicy) {
        # Determine if ATP is enabled or not
        If($Policy.EnableATPForSPOTeamsODB -eq $true) {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy["EnableATPForSPOTeamsODB"])
                Rule="ATP enabled for SPO - OD4B - Teams"
                Control=$Cid
            }
        } else {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy["EnableATPForSPOTeamsODB"])
                Rule="ATP enabled for SPO - OD4B - Teams"
                Control=$Cid
            }
        }
    }

    Return $Return
}

function Invoke-CheckContentFilterActions {
    Param(
        $ContentFilterPolicies
    )

    $Check = "Content Filter Actions"

    $return = @()

    ForEach($Policy in $ContentFilterPolicies) {

        # Fail if HighConfidenceSpamAction is not set to Quarantine

        If($Policy.HighConfidenceSpamAction -ne "Quarantine") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.HighConfidenceSpamAction)
                Rule="HighConfidenceSpamAction set to $($Policy.HighConfidenceSpamAction)"
                Control="140"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.HighConfidenceSpamAction)
                Rule="HighConfidenceSpamAction set to $($Policy.HighConfidenceSpamAction)"
                Control="140"
            } 
        }

        # Fail if SpamAction is not set to MoveToJmf

        If($Policy.SpamAction -ne "MoveToJmf") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.SpamAction)
                Rule="SpamAction set to $($Policy.SpamAction)"
                Control="139"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.SpamAction)
                Rule="SpamAction set to $($Policy.SpamAction)"
                Control="139"
            } 
        }

        # Fail if BulkSpamAction is not set to MoveToJmf

        If($Policy.BulkSpamAction -ne "MoveToJmf") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.BulkSpamAction)
                Rule="BulkSpamAction set to $($Policy.BulkSpamAction)"
                Control="141"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.BulkSpamAction)
                Rule="BulkSpamAction set to $($Policy.BulkSpamAction)"
                Control="141"
            } 
        }

        # Fail if PhishSpamAction is not set to Quarantine

        If($Policy.PhishSpamAction -ne "Quarantine") {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishSpamAction)
                Rule="PhishSpamAction set to $($Policy.PhishSpamAction)"
                Control="142"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishSpamAction)
                Rule="PhishSpamAction set to $($Policy.PhishSpamAction)"
                Control="142"
            } 
        }
    }

    return $return        
}

function Invoke-CheckATPSafeLinksTrackingInternal 
{
    <#
     
    179
 
    Checks to determine if SafeLinks is re-wring internal to internal emails. Does not however,
    check to determine if there is a rule enforcing this.
     
    #>

    Param(
        $ATPPolicy
    )

    $Check = "ATP"
    $Cid = "179"
    
    $Return = @()

    # Determine if ATP license

    ForEach($Policy in $ATPPolicy) 
    {
        # Determine if ATP link tracking is on for this safelinks policy
        If($Policy.EnableForInternalSenders -eq $true) 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.EnableForInternalSenders
                Rule="SafeLinks Enabled for Internal Senders"
                Control=$Cid
            }
        } 
        Else 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.EnableForInternalSenders
                Rule="SafeLinks Disabled for Internal Senders"
                Control=$Cid
            }
        }
    }

    Return $Return
}
function Invoke-CheckATPSafeLinksTrackingOfficeApps {
    <#
     
    169
 
    Determines if ATP SafeLinks protection extends to Office Apps in each policy,
    Does not however determine if SafeLinks policy extends to all users.
     
    #>


    Param(
        $ATPPolicy
    )

    $Check = "ATP"
    $Cid = "169"
    
    $Return = @()

    If($ATPPolicy.EnableSafeLinksForClients -eq $true)
    {
        $Return += New-Object -TypeName psobject -Property @{
            Result="Pass"
            Check=$Check
            ConfigItem="365 ATP Policy EnableSafeLinksForClients"
            ConfigData=$ATPPolicy.EnableSafeLinksForClients
            Rule="SafeLinks URL Tracking Enabled for Office Clients"
            Control=$Cid
        }
    } 
    Else 
    {
        $Return += New-Object -TypeName psobject -Property @{
            Result="Fail"
            Check=$Check
            ConfigItem="365 ATP Policy EnableSafeLinksForClients"
            ConfigData=$ATPPolicy.EnableSafeLinksForClients
            Rule="SafeLinks URL Tracking Enabled for Office Clients"
            Control=$Cid
        }
    }

    Return $Return
}

function Invoke-CheckATPSafeAttachmentsBypass {
<#
 
    189
     
    Checks to determine if SafeAttachments is being bypassed by injecting X-MS-Exchange-Organization-SkipSafeAttachmentProcessing
    header in to emails using a mail flow rule.
 
#>

    Param(
        $TransportRules
    )

    $Check = "ATP"
    $Cid = "189"
    
    $Return = @()

    $BypassRules = @($TransportRules | Where-Object {$_.SetHeaderName -eq "X-MS-Exchange-Organization-SkipSafeAttachmentProcessing"})
    
    If($BypassRules.Count -gt 0) 
    {
        # Rules exist to bypass
        ForEach($Rule in $BypassRules) 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem="Transport Rule - $($Rule.Name)"
                ConfigData="Setting X-MS-Exchange-Organization-SkipSafeAttachmentProcessing to $($Rule.SetHeaderValue)"
                Rule="SafeAttachments not bypassed"
                Control=$Cid
            }
        }
    } 
    Else 
    {
        # Rules do not exist to bypass
        $Return += New-Object -TypeName psobject -Property @{
            Result="Pass"
            Check=$Check
            ConfigItem="Transport Rules"
            Rule="SafeAttachments not bypassed"
            Control=$Cid
        }
    }

    Return $Return
}

function Invoke-CheckATPSafeLinksTracking {
    <#
     
    158
 
    Determines if SafeLinks URL tracing is enabled on a Policy, does not however check
    that there is a rule enforcing this policy.
     
    #>

    Param(
        $ATPPolicy,
        $Questions
    )

    $Check = "ATP"
    $Cid = "156"
    
    $Return = @()

    # Determine if ATP license

    ForEach($Policy in $ATPPolicy) 
    {
        # Determine if ATP link tracking is on for this safelinks policy
        If($Policy.DoNotTrackUserClicks -eq $false) {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.DoNotTrackUserClicks
                Rule="SafeLinks URL Tracking Enabled"
                Control=$Cid
            }
        } 
        else 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.DoNotTrackUserClicks
                Rule="SafeLinks URL Tracking Enabled"
                Control=$Cid
            }
        }
    }

    Return $Return
}

function Invoke-CheckContentFilterSafetyTips {
    Param(
        $ContentFilterPolicies
    )

    $Check = "Content Filter Safety Tips"
    $CID = "143"

    $return = @()

    ForEach($Policy in $ContentFilterPolicies) {

        # Fail if InlineSafetyTipsEnabled is not set to true

        If($Policy.InlineSafetyTipsEnabled -eq $false) {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.InlineSafetyTipsEnabled)
                Rule="InlineSafetyTipsEnabled is false - Safety Tips Disabled"
                Control=$CID
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.InlineSafetyTipsEnabled)
                Rule="InlineSafetyTipsEnabled is true - Safety Tips Enabled"
                Control=$CID
            } 
        }
    }

    return $return        
}

function Invoke-CheckMalwareNotifications {
    Param(
        $MalwareFilterPolicies
    )

    $Check = "Malware Notifications"

    $return = @()

    ForEach($Policy in $MalwareFilterPolicies) {

        # Fail if EnableExternalSenderNotifications is set to true in the policy

        If($Policy.EnableExternalSenderNotifications -eq $true) {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Rule="EnableExternalSenderNotifications set to True"
                Control="125"
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                Rule="EnableExternalSenderNotifications set to False"
                Control="125"
            } 
        }
    }

    return $return
}

function Invoke-CheckCommonAttachmentTypeFilter {
    Param(
        $MalwareFilterPolicies
    )

    $Check = "Common Attachment Type Filter"
    $Control = "205"

    $return = @()

    ForEach($Policy in $MalwareFilterPolicies) {

        # Fail if NotifyOutboundSpam is not set to true in the policy

        If($Policy.EnableFileFilter -eq $false -or @($Policy.FileTypes).Count -eq 0) {
            $return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=""
                Rule="EnableFilterFilter eq false or FileType count is zero"
                Control=$Control
            } 
        } else {
            $return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="EnableFileFilter $($Policy.EnableFileFilter) FileTypes Count $(($Policy.FileTypes).Count)"
                Rule="EnableFileFilter eq true and FileType count is greater than zero"
                Control=$Control
            } 
        }
    }

    return $return
}

function Invoke-CheckUnifiedLogging {
    Param(
        $AdminAuditLogConfig
    )

    $Check = "Unified Logging"
    $ConfigItem = "Admin Audit Log Settings"
    $CID="122"

    If($AdminAuditLogConfig.UnifiedAuditLogIngestionEnabled -eq $true) {

        # Unified audit logging turned on

        $return = New-Object -TypeName psobject -Property @{
            Result="Pass"
            Check=$Check
            ConfigItem=$ConfigItem
            Rule="UnifiedAuditLogIngestionEnabled is true"
            Control=$CID
        } 

    } else {

        # Unified audit logging turned off

        $return = New-Object -TypeName psobject -Property @{
            Result="Fail"
            Check=$Check
            ConfigItem=$ConfigItem
            Rule="UnifiedAuditLogIngestionEnabled is false"
            Control=$CID
        } 

    }

    return $return
}

Function Invoke-CheckTransportRuleSCL {
    Param(
        $TransportRules
    )

    $Check = "Transport Rule SCL"
    $CID = "118-2"

    $return = @()

    # Look through Transport Rule for an action SetSCL -1

    ForEach($TransportRule in $TransportRules) {
        If($TransportRule.SetSCL -eq "-1") {
            #Rules that apply to the sender domain
            #From Address notmatch is to include if just domain name is value
            If($TransportRule.SenderDomainIs -ne $null -or ($TransportRule.FromAddressContainsWords -ne $null -and $TransportRule.FromAddressContainsWords -notmatch ".+@") -or ($TransportRule.FromAddressMatchesPatterns -ne $null -and $TransportRule.FromAddressMatchesPatterns -notmatch ".+@")){
                #Look for condition that checks auth results header and its value
                If(($TransportRule.HeaderContainsMessageHeader -eq 'Authentication-Results' -and $TransportRule.HeaderContainsWords -ne $null) -or ($TransportRule.HeaderMatchesMessageHeader -like '*Authentication-Results*' -and $TransportRule.HeaderMatchesPatterns -ne $null)) {
                    # OK
                }
                #Look for exception that checks auth results header and its value
                elseif(($TransportRule.ExceptIfHeaderContainsMessageHeader -eq 'Authentication-Results' -and $TransportRule.ExceptIfHeaderContainsWords -ne $null) -or ($TransportRule.ExceptIfHeaderMatchesMessageHeader -like '*Authentication-Results*' -and $TransportRule.ExceptIfHeaderMatchesPatterns -ne $null)) {
                    # OK
                }
                elseif($TransportRule.SenderIpRanges -ne $null) {
                    # OK
                }
                #Look for condition that checks for any other header and its value
                else {
                    ForEach($RuleDomain in $($TransportRule.SenderDomainIs)) {
                        $return +=  New-Object -TypeName psobject -Property @{
                            Result="Fail"
                            Check=$Check
                            ConfigItem=$($TransportRule.Name)
                            ConfigData=$($RuleDomain)
                            Rule="SetSCL -1 action for sender domain but no check for auth results header, sender IP, or other header"
                            Control=$CID
                        }
                    }
                    ForEach($FromAddressContains in $($TransportRule.FromAddressContainsWords)) {
                        $return +=  New-Object -TypeName psobject -Property @{
                            Result="Fail"
                            Check=$Check
                            ConfigItem=$($TransportRule.Name)
                            ConfigData="Contains $($FromAddressContains)"
                            Rule="SetSCL -1 action for sender domain but no check for auth results header, sender IP, or other header"
                            Control=$CID
                        }
                    }
                    ForEach($FromAddressMatch in $($TransportRule.FromAddressMatchesPatterns)) {
                        $return +=  New-Object -TypeName psobject -Property @{
                            Result="Fail"
                            Check=$Check
                            ConfigItem=$($TransportRule.Name)
                            ConfigData="Matches $($FromAddressMatch)"
                            Rule="SetSCL -1 action for sender domain but no check for auth results header, sender IP, or other header"
                            Control=$CID
                        }
                    }

                }
            }
            #No sender domain restriction, so check for IP restriction
            elseif($null -ne $TransportRule.SenderIpRanges) {
                ForEach($SenderIpRange in $TransportRule.SenderIpRanges) {
                    $return +=  New-Object -TypeName psobject -Property @{
                        Result="Fail"
                        Check=$Check
                        ConfigItem=$($TransportRule.Name)
                        ConfigData=$SenderIpRange
                        Rule="SetSCL -1 action with IP condition but not limiting sender domain"
                        Control="114"
                    }
                }
            }
            #No sender restriction, so check for condition that checks auth results header and its value
            elseif(($TransportRule.HeaderContainsMessageHeader -eq 'Authentication-Results' -and $TransportRule.HeaderContainsWords -ne $null) -or ($TransportRule.HeaderMatchesMessageHeader -like '*Authentication-Results*' -and $TransportRule.HeaderMatchesPatterns -ne $null)) {
                # OK
            }
            #No sender restriction, so check for exception that checks auth results header and its value
            elseif(($TransportRule.ExceptIfHeaderContainsMessageHeader -eq 'Authentication-Results' -and $TransportRule.ExceptIfHeaderContainsWords -ne $null) -or ($TransportRule.ExceptIfHeaderMatchesMessageHeader -like '*Authentication-Results*' -and $TransportRule.ExceptIfHeaderMatchesPatterns -ne $null)) {
                # OK
            }
        }
    }
    # If no rules found with SetSCL -1, then pass.

    if(!$return) {
        $return +=  New-Object -TypeName psobject -Property @{
            Result="Pass"
            Check=$Check
            ConfigItem="Transport Rules"
            Rule="No SetSCL -1 actions found"
            Control=$CID
        }  
    }

    return $return
}

function Invoke-CheckATPPhishThreshold
{
    <#
 
    Checks to determine if ATP is enabled for SharePoint, Teams, and OD4B as per 'tickbox' in
    the ATP configuration.
     
    #>

    Param(
        $AntiPhishPolicy
    )

    $Check = "ATP Phish Threshold"
    $Cid = "220"
    
    $Return = @()

    ForEach($Policy in $AntiPhishPolicy) {
        If($Policy.PhishThresholdLevel -eq 1)
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishThresholdLevel)
                Rule="PhishThreshold Level is 1"
                Control=$Cid
            }
        } 
        else
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.PhishThresholdLevel)
                Rule="PhishThreshold Level 2 or higher"
                Control=$Cid
            }
        }
    }

    Return $Return
}

function Invoke-CheckATPPhishDomainImpAction
{
    Param(
        $AntiPhishPolicy
    )

    $Check = "ATP Phish Domain Impersonation"
    $Cid = "222"
    
    $Return = @()

    ForEach($Policy in ($AntiPhishPolicy | Where-Object {$_.Enabled -eq $True}))
    {
        If($Policy.EnableTargetedDomainsProtection -eq $False)
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="TargetedDomainsProtection Disabled"
                Rule="Targeted Domains Protection is Off"
                Control=$Cid
            }
        } 
        ElseIf($Policy.EnableTargetedDomainsProtection -eq $True -and $Policy.TargetedDomainProtectionAction -ne "MoveToJMF")
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.TargetedDomainProtectionAction)
                Rule="TargetedDomainProtectionAction not MoveToJMF"
                Control=$Cid
            }
        } 
        ElseIf($Policy.EnableTargetedDomainsProtection -eq $True -and $Policy.TargetedDomainProtectionAction -eq "MoveToJMF")
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData=$($Policy.TargetedDomainProtectionAction)
                Rule="EnableTargetedDomainsProtection is True and TargetedDomainProtectionAction is MoveToJMF"
                Control=$Cid
            }
        }
    }

    If($Return.Count -eq 0)
    {
        $Return += New-Object -TypeName psobject -Property @{
            Result="Fail"
            Check=$Check
            Rule="No Enabled AntiPhish Policy"
            Control=$Cid
        }            
    }

    Return $Return
}


function Invoke-CheckATPPhishUserImpAction
{
    Param(
        $AntiPhishPolicy
    )

    $Check = "ATP User Impersonation"
    $Cid = "223"
    
    $Return = @()

    ForEach($Policy in ($AntiPhishPolicy | Where-Object {$_.Enabled -eq $True}))
    {

        If($Policy.EnableTargetedUserProtection -eq $False)
        {
            # Policy Targeted UserProtection is off
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="Targetted User Impersonation Disabled"
                Rule="Targeted User Protection Off"
                Control=$Cid
            }
        } 
        Else
        {
            # Check for enabled safety tips
            If($Policy.EnableSimilarUsersSafetyTips -eq $False)
            {
                $Return += New-Object -TypeName psobject -Property @{
                    Result="Fail"
                    Check=$Check
                    ConfigItem=$($Policy.Name)
                    ConfigData="EnableSimilarUsersSafetyTips $($Policy.EnableSimilarUsersSafetyTips)"
                    Rule="Safety tips is off"
                    Control=$Cid
                }
            }
            Else
            {
                $Return += New-Object -TypeName psobject -Property @{
                    Result="Pass"
                    Check=$Check
                    ConfigItem=$($Policy.Name)
                    ConfigData="EnableSimilarUsersSafetyTips $($Policy.EnableSimilarUsersSafetyTips)"
                    Rule="Safety tips is on"
                    Control=$Cid
                }                    
            }

            # Check for action being MoveToJmf
            If($Policy.TargetedUserProtectionAction -eq "MoveToJmf")
            {
                $Return += New-Object -TypeName psobject -Property @{
                    Result="Pass"
                    Check=$Check
                    ConfigItem=$($Policy.Name)
                    ConfigData="TargetedUserProtectionAction $($Policy.TargetedUserProtectionAction)"
                    Rule="TargetedUserProtectionAction is MoveToJmf"
                    Control=$Cid
                }   
            }
            Else
            {
                $Return += New-Object -TypeName psobject -Property @{
                    Result="Fail"
                    Check=$Check
                    ConfigItem=$($Policy.Name)
                    ConfigData="TargetedUserProtectionAction $($Policy.TargetedUserProtectionAction)"
                    Rule="TargetedUserProtectionAction not MoveToJmf"
                    Control=$Cid
                }                 
            }
        }
    }

    If($Return.Count -eq 0)
    {
        $Return += New-Object -TypeName psobject -Property @{
            Result="Fail"
            Check=$Check
            Rule="No Enabled AntiPhish Policy"
            Control=$Cid
        }            
    }

    Return $Return
}

function Invoke-CheckATPPhishMIAction
{
    <#
     
    221 and 224 - Check ATP Phishing Mailbox Intelligence Action
     
    #>

    Param(
        $AntiPhishPolicy
    )

    $Check = "ATP Mailbox Intelligence"
    
    $Return = @()


    ForEach($Policy in ($AntiPhishPolicy | Where-Object {$_.Enabled -eq $True}))
    {

        # Determine Mailbox Intelligence is ON

        If($Policy.EnableMailboxIntelligence -eq $false)
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="EnableMailboxIntelligence $($Policy.EnableMailboxIntelligence)"
                Rule="Mailbox Intelligence Off"
                Control="221"
            }                
        }
        Else 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="EnableMailboxIntelligence $($Policy.EnableMailboxIntelligence)"
                Rule="Mailbox Intelligence On"
                Control="221"
            }                            
        }

        # Determine if tips for user impersonation is on

        If($Policy.EnableSimilarUsersSafetyTips -eq $false)
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="EnableSimilarUsersSafetyTips $($Policy.EnableSimilarUsersSafetyTips)"
                Rule="Similar User Safety Tips Off"
                Control="224"
            }  
        }
        else
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                Check=$Check
                ConfigItem=$($Policy.Name)
                ConfigData="EnableSimilarUsersSafetyTips $($Policy.EnableSimilarUsersSafetyTips)"
                Rule="Similar User Safety Tips On"
                Control="224"
            }                  
        }
    }

    If($Return.Count -eq 0)
    {
        $Return += New-Object -TypeName psobject -Property @{
            Result="Fail"
            Check=$Check
            Rule="No Enabled AntiPhish Policy"
            Control=$Cid
        }            
    }

    Return $Return
}

function Invoke-CheckMarkAsSpamBulkMail
{
    <#
     
    ORCA-101
 
    #>

    Param(
        $HostedContentFilterPolicy
    )
    
    $Return = @()
    $CID = "ORCA-101"

    ForEach($Policy in $HostedContentFilterPolicy)
    {

        If($Policy.MarkAsSpamBulkMail -eq "On")
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.MarkAsSpamBulkMail
                Control=$CID
            }                
        }
        Else 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.MarkAsSpamBulkMail
                Control=$CID
            }                            
        }

    }

    Return $Return
}
function Invoke-CheckBulkThreshold
{
    <#
     
    ORCA-100
 
    #>

    Param(
        $HostedContentFilterPolicy
    )
    
    $Return = @()
    $CID = "ORCA-100"

    ForEach($Policy in $HostedContentFilterPolicy)
    {

        If($Policy.BulkThreshold -le 6)
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Pass"
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.BulkThreshold
                Control=$CID
            }                
        }
        Else 
        {
            $Return += New-Object -TypeName psobject -Property @{
                Result="Fail"
                ConfigItem=$($Policy.Name)
                ConfigData=$Policy.BulkThreshold
                Control=$CID
            }                            
        }

    }

    Return $Return
}


Function Invoke-ORCAConnections
{
    If(!(Get-Command "Connect-EXOPSSession" -ErrorAction:SilentlyContinue)) {

        Throw "Please load the Exchange Online PowerShell module before running ORCA."

    } Else {

        <#
 
        The following code is commented out until SCC is required
 
        Write-Host "$(Get-Date) Connecting to Security and Compliance Center.."
        Connect-IPPSSession -PSSessionOption $ProxySetting -WarningAction:SilentlyContinue | Out-Null
        $sessionSCC = (Get-PSSession | Where-Object {$_.ComputerName -like "*protection.outlook.com"})
 
        #>


        Write-Host "$(Get-Date) Connecting to Exchange Online.."
        Connect-EXOPSSession -PSSessionOption $ProxySetting -WarningAction:SilentlyContinue | Out-Null

    }
}

Function Get-ORCACheckDefs
{
    <#
 
        Check definition
 
        The checks defined below allow contextual information to be added in to the report HTML document.
        - Control : A unique identifier that can be used to index the results back to the check
        - Area : The area that this check should appear within the report
        - PassText : The text that should appear in the report when this 'control' passes
        - FailRecommendation : The text that appears as a title when the 'control' fails. Short, descriptive. E.g "Do this"
        - Importance : Why this is important
        - ExpandResults : If we should create a table in the callout which points out which items fail and where
        - ItemName : When ExpandResults is set to, what does the check return as ConfigItem, for instance, is it a Transport Rule?
        - DataType : When ExpandResults is set to, what type of data is returned in ConfigData, for instance, is it a Domain?
 
    #>

    $Checks = @()

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="ORCA-100"
        Area="Content Filter Policies"
        Name="Bulk Complaint Level"
        PassText="Bulk Complaint Level threshold is set to 6 or lower"
        FailRecommendation="Set the Bulk Complaint Level threshold to be 6 or lower"
        Importance="The differentiation between bulk and spam can sometimes be subjective. The bulk complaint level is based on the number of complaints from the sender. Decreasing the threshold can decrease the amount of perceived spam received."
        ExpandResults=$True
        ItemName="Content Filter Policy"
        DataType="Bulk Complaint Level Threshold"
        Links= @{
            "Bulk Complaint Level values"="https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/bulk-complaint-level-values"
        }
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="ORCA-101"
        Area="Content Filter Policies"
        Name="Mark Bulk as Spam"
        PassText="Bulk is marked as spam"
        FailRecommendation="Set the content filter policy to mark bulk mail as spam"
        Importance="The differentiation between bulk and spam can sometimes be subjective. The bulk complaint level is based on the number of complaints from the sender. Marking bulk as spam can decrease the amount of perceived spam received."
        ExpandResults=$True
        ItemName="Content Filter Policy"
        DataType="Mark as Spam Bulk Mail Setting (MarkAsSpamBulkMail)"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=114
        Area="Content Filter Policies"
        Name="IP Allow Lists"
        PassText="No IP Allow Lists have been configured"
        FailRecommendation="Remove IP addresses from IP allow list"
        Importance="IP addresses containted in the IP allow list are able to bypass spam, phishing and spoofing checks, potentially resulting in more spam. Ensure that the IP list is kept to a minimum."
        ExpandResults=$True
        ItemName="Content Filter Policy"
        DataType="Allowed IP"
        Links= @{
            "Use Anti-Spam Policy IP Allow lists"="https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/create-safe-sender-lists-in-office-365#use-anti-spam-policy-ip-allow-lists"
        }
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="118-1"
        Area="Content Filter Policies"
        Name="Domain Whitelisting"
        PassText="Domains are not being whitelisted in an unsafe manner"
        FailRecommendation="Remove whitelisting on domains"
        Importance="Emails coming from whitelisted domains bypass several layers of protection within Exchange Online Protection. If domains are whitelisted, they are open to being spoofed from malicious actors."
        ExpandResults=$True
        ItemName="Content Filter Policy"
        DataType="Whitelisted Domain"
        Links= @{
            "Use Anti-Spam Policy Sender/Domain Allow lists"="https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/create-safe-sender-lists-in-office-365#use-anti-spam-policy-senderdomain-allow-lists"
        }
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="118-2"
        Area="Transport Rules"
        Name="Domain Whitelisting"
        PassText="Domains are not being whitelisted in an unsafe manner"
        FailRecommendation="Remove whitelisting on domains"
        Importance="Emails coming from whitelisted domains bypass several layers of protection within Exchange Online Protection. If domains are whitelisted, they are open to being spoofed from malicious actors."
        ExpandResults=$True
        ItemName="Transport Rule"
        DataType="Whitelisted Domain"
        Links= @{
            "Using Exchange Transport Rules (ETRs) to allow specific senders"="https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/create-safe-sender-lists-in-office-365#using-exchange-transport-rules-etrs-to-allow-specific-senders-recommended"
        }
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="120-spam"
        Area="Zero Hour Autopurge"
        Name="Zero Hour Autopurge Enabled for Spam"
        PassText="Zero Hour Autopurge is Enabled"
        FailRecommendation="Enable Zero Hour Autopurge"
        Importance="Zero Hour Autopurge can assist removing false-negatives post detection from mailboxes. By default, it is enabled."
        ExpandResults=$True
        ItemName="Policy"
        DataType="Setting"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control="120-malware"
        Area="Zero Hour Autopurge"
        Name="Zero Hour Autopurge Enabled for Malware"
        PassText="Zero Hour Autopurge is Enabled"
        FailRecommendation="Enable Zero Hour Autopurge"
        Importance="Zero Hour Autopurge can assist removing false-negatives post detection from mailboxes. By default, it is enabled."
        ExpandResults=$True
        ItemName="Policy"
        DataType="Setting"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=121
        Area="Zero Hour Autopurge"
        Name="Supported filter policy action"
        PassText="Supported filter policy action used"
        FailRecommendation="Change filter policy action to support Zero Hour Auto Purge"
        Importance="Zero Hour Autopurge can assist removing false-negatives post detection from mailboxes. It requires a supported action in the spam filter policy."
        ExpandResults=$True
        ItemName="Policy"
        DataType="Setting"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=122
        Area="Tenant Settings"
        Name="Unified Audit Log"
        PassText="Unified Audit Log is enabled"
        FailRecommendation="Enable the Unified Audit Log"
        Importance="The Unified Audit Log collects logs from most Office 365 services and provides one central place to correlate and pull logs from Office 365."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=125
        Area="Malware Filter Policy"
        Name="External Sender Notifications"
        PassText="External Sender notifications are disabled"
        FailRecommendation="Disable notifying external senders of malware detection"
        Importance="Notifying external senders about malware detected in email messages could have negative impact. An adversary may use this information to verify effectiveness of malware detection."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=139
        Area="Content Filter Policies"
        Name="Spam Action"
        PassText="Spam action set to Move message to Junk Email Folder"
        FailRecommendation="Change Spam action to Move message to Junk Email Folder"
        Importance="It is recommended to configure Spam detection action to Move messages to Junk Email folder."
        ExpandResults=$True
        ItemName="Spam Policy"
        DataType="Action"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=140
        Area="Content Filter Policies"
        Name="High Confidence Spam Action"
        PassText="High Confidence Spam action set to Quarantine message"
        FailRecommendation="Change High Confidence Spam action to Quarantine message"
        Importance="It is recommended to configure High Confidence Spam detection action to Quarantine message."
        ExpandResults=$True
        ItemName="Spam Policy"
        DataType="Action"  
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=141
        Area="Content Filter Policies"
        Name="Bulk Action"
        PassText="Bulk action set to Move message to Junk Email Folder"
        FailRecommendation="Change Bulk action to Move message to Junk Email Folder"
        Importance="It is recommended to configure Bulk detection action to Move messages to Junk Email folder."
        ExpandResults=$True
        ItemName="Spam Policy"
        DataType="Action"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=142
        Area="Content Filter Policies"
        Name="Phish Action"
        PassText="Phish action set to Quarantine message"
        FailRecommendation="Change Phish action to Quarantine message"
        Importance="It is recommended to configure the Phish detection action to Quarantine so that these emails are not visible to the end user from within Outlook. As Phishing emails are designed to look legitimate, users may mistakenly think that a phishing email in Junk is false-positive."
        ExpandResults=$True
        ItemName="Spam Policy"
        DataType="Action"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=143
        Area="Content Filter Policies"
        Name="Safety Tips"
        PassText="Safety Tips are enabled"
        FailRecommendation="Safety Tips should be enabled"
        Importance="By default, safety tips can provide useful security information when reading an email."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=156
        Area="Advanced Threat Protection Policies"
        Name="Safe Links Tracking"
        PassText="Safe Links Policies are tracking when user clicks on safe links"
        FailRecommendation="Enable tracking of user clicks in Safe Links Policies"
        Importance="When these options are configured, click data for URLs in Word, Excel, PowerPoint, Visio documents and in emails is stored by Safe Links. This information can help dealing with phishing, suspicious email messages and URLs."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=158
        Area="Advanced Threat Protection Policies"
        Name="Safe Attachments SharePoint and Teams"
        PassText="Safe Attachments is enabled for SharePoint and Teams"
        FailRecommendation="Enable Safe Attachments for SharePoint and Teams"
        Importance="Safe Attachments assists scanning for zero day malware by using behavioural analysis and sandboxing, supplimenting signature definitions."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=169
        Area="Advanced Threat Protection Policies"
        Name="Office Enablement"
        PassText="Safe Links is enabled for Office ProPlus, Office for iOS and Android"
        FailRecommendation="Enable Safe Links for Office ProPlus, Office for iOS and Android"
        Importance="Phishing attacks are not limited to email messages. Malicious URLs can be delivered using Office documents as well. Configuring Office 365 ATP Safe Links for Office ProPlus, Office for iOS and Android can help combat against these attacks via providing time-of-click verification of web addresses (URLs) in Office documents."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=179
        Area="Advanced Threat Protection Policies"
        Name="Intra-organization Safe Links"
        PassText="Safe Links is enabled intra-organization"
        FailRecommendation="Enable Safe Links between internal users"
        Importance="Phishing attacks are not limited from external users. Commonly, when one user is compromised, that user can be used in a process of lateral movement between different accounts in your organization. Configuring Safe Links so that internal messages are also re-written can assist with lateral movement using phishing."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=189
        Area="Advanced Threat Protection Policies"
        Name="Safe Attachment Whitelisting"
        PassText="Safe Attachments is not bypassed"
        FailRecommendation="Remove mail flow rules which bypass Safe Attachments"
        Importance="Office 365 ATP Safe Attachments assists scanning for zero day malware by using behavioural analysis and sandboxing, supplementing signature definitions. The protection can be bypassed using mail flow rules which set the X-MS-Exchange-Organization-SkipSafeAttachmentProcessing header for email messages."
        ExpandResults=$True
        ItemName="Transport Rule"
        DataType="Details"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=205
        Area="Malware Filter Policy"
        Name="Common Attachment Type Filter"
        PassText="Common attachment type filter is enabled"
        FailRecommendation="Enable common attachment type filter"
        Importance="The common attachment type filter can block file types that commonly contain malware, including in internal emails."
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=220
        Area="Advanced Threat Protection Policies"
        Name="Phishing Threshold Level"
        PassText="Phishing theshold level is adequate."
        FailRecommendation="Increase the antiphishing threshold level"
        Importance="The higher the antiphishing threshold, the stricter the mechanisms are that detect phishing attempts against your users."
        ExpandResults=$True
        ItemName="Antiphishing Policy"
        DataType="Phishing Threshold Level"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=221
        Area="Advanced Threat Protection Policies"
        Name="Mailbox Intelligence Enabled"
        PassText="Mailbox intelligence is enabled in anti-phishing policies"
        FailRecommendation="Enable mailbox intelligence in anti-phishing policies"
        Importance="Mailbox Intelligence checks can provide your users with intelligence on suspicious incomming emails that appear to be from users that they normally communicate with based on their graph."
        ExpandResults=$True
        ItemName="Antiphishing Policy"
        DataType="Setting"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=222
        Area="Advanced Threat Protection Policies"
        Name="Domain Impersonation Action"
        PassText="Domain Impersonation action is set to move to Junk Mail Folder (JMF)"
        FailRecommendation="Configure domain impersonation action to Junk Mail Filter (JMF)"
        Importance="Domain Impersonation can detect impersonation attempts against your domains or domains that look very similiar to your domains. Move messages that are caught using this impersonation protection to Junk Mail Folder (JMF)."
        ExpandResults=$True
        ItemName="Antiphishing Policy"
        DataType="Domain Impersonation Action"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=223
        Area="Advanced Threat Protection Policies"
        Name="Domain Impersonation Action"
        PassText="User impersonation action is set to Junk Mail Filter (JMF) and Tip"
        FailRecommendation="Configure user impersonation action to Junk Mail Filter (JMF) and Tip"
        Importance="User impersonation protection can detect spoofing of your sensitive users. Move messages that are caught using user impersonation detection to Junk and mark them with a safety tip."
        ExpandResults=$True
        ItemName="Antiphishing Policy"
        DataType="User Impersonation Action"
    }

    $Checks += New-Object -TypeName PSObject -Property @{
        Control=224
        Area="Advanced Threat Protection Policies"
        Name="Mailbox Intelligence Action"
        PassText="Your policy is configured to notify users with a tip."
        FailRecommendation="Enable tips so that users can receive visible indication on incomming messages."
        Importance="Mailbox Intelligence checks can provide your users with intelligence on suspicious incomming emails that appear to be from users that they normally communicate with based on their graph."
        ExpandResults=$True
        ItemName="Antiphishing Policy"
        DataType="Setting"
    }

    Return $Checks
}

Function Get-ORCACollection
{
    $Collection = @{}

    Write-Host "$(Get-Date) Getting Anti-Spam Settings"
    $Collection["HostedConnectionFilterPolicy"] = Get-HostedConnectionFilterPolicy
    $Collection["HostedContentFilterPolicy"] = Get-HostedContentFilterPolicy
    $Collection["HostedContentFilterRule"] = Get-HostedContentFilterRule
    $Collection["HostedOutboundSpamFilterPolicy"] = Get-HostedOutboundSpamFilterPolicy

    Write-Host "$(Get-Date) Getting Tenant Settings"
    $Collection["AdminAuditLogConfig"] = Get-AdminAuditLogConfig

    Write-Host "$(Get-Date) Getting Anti Phish Settings"
    $Collection["AntiPhishPolicy"] = Get-AntiphishPolicy

    Write-Host "$(Get-Date) Getting Anti-Malware Settings"
    $Collection["MalwareFilterPolicy"] = Get-MalwareFilterPolicy
    $Collection["MalwareFilterRule"] = Get-MalwareFilterRule

    Write-Host "$(Get-Date) Getting Transport Rules"
    $Collection["TransportRules"] = Get-TransportRule

    Write-Host "$(Get-Date) Getting ATP Policies"
    $Collection["SafeAttachmentsPolicy"] = Get-SafeAttachmentPolicy
    $Collection["SafeAttachmentsRules"] = Get-SafeAttachmentRule
    $Collection["SafeLinksPolicy"] = Get-SafeLinksPolicy
    $Collection["SafeLinksRules"] = Get-SafeLinksRule
    $Collection["AtpPolicy"] = Get-AtpPolicyForO365

    Write-Host "$(Get-Date) Getting Accepted Domains"
    $Collection["AcceptedDomains"] = Get-AcceptedDomain

    Return $Collection
}

Function Get-ORCAResults
{
    Param(
        $Collection
    )

    # Main Analysis
    Write-Host "$(Get-Date) Analysis starting. Version $($Version)" -ForegroundColor Green

    $Return = @()
    Write-Host "$(Get-Date) Analysis - Unified Auditing"
    $Return += Invoke-CheckUnifiedLogging $Collection.Get_Item("AdminAuditLogConfig")

    Write-Host "$(Get-Date) Analysis - Exchange - Allow lists"
    $Return += Invoke-CheckAllowedSenderDomains $Collection.Get_Item("HostedContentFilterPolicy")

    Write-Host "$(Get-Date) Analysis - Exchange - ZAP Checks"
    $Return += Invoke-CheckZAP -MalwareFilterPolicies $Collection.Get_Item("MalwareFilterPolicy") -ContentFilterPolicies $Collection.Get_Item("HostedContentFilterPolicy")

    Write-Host "$(Get-Date) Analysis - Exchange - Content Filter Policy"
    $Return += Invoke-CheckContentFilterActions -ContentFilterPolicies $Collection.Get_Item("HostedContentFilterPolicy")
    $Return += Invoke-CheckContentFilterSafetyTips -ContentFilterPolicies $Collection.Get_Item("HostedContentFilterPolicy")
    $Return += Invoke-CheckBulkThreshold -HostedContentFilterPolicy $Collection.Get_Item("HostedContentFilterPolicy")
    $Return += Invoke-CheckMarkAsSpamBulkMail  -HostedContentFilterPolicy $Collection.Get_Item("HostedContentFilterPolicy")

    Write-Host "$(Get-Date) Analysis - Exchange - Malware Filter Policy"
    $Return += Invoke-CheckMalwareNotifications $Collection.Get_Item("MalwareFilterPolicy")
    $Return += Invoke-CheckCommonAttachmentTypeFilter -MalwareFilterPolicies $Collection.Get_Item("MalwareFilterPolicy")

    $Return += Invoke-CheckIPAllowList $Collection.Get_Item("HostedConnectionFilterPolicy")

    Write-Host "$(Get-Date) Analysis - Exchange - Transport Rules"
    $Return += Invoke-CheckTransportRuleSCL $Collection.Get_Item("TransportRules")

    Write-Host "$(Get-Date) Analysis - ATP - General"
    $Return += Invoke-CheckATPForSPOTeamsODB -ATPPolicy $Collection.Get_Item("AtpPolicy")

    Write-Host "$(Get-Date) Analysis - ATP - SafeLinks"
    $Return += Invoke-CheckATPSafeLinksTracking -ATPPolicy $Collection.Get_Item("SafeLinksPolicy")
    $Return += Invoke-CheckATPSafeLinksTrackingInternal -ATPPolicy $Collection.Get_Item("SafeLinksPolicy")
    $Return += Invoke-CheckATPSafeLinksTrackingOfficeApps -ATPPolicy $Collection.Get_Item("AtpPolicy")

    Write-Host "$(Get-Date) Analysis - ATP - SafeAttachments"
    $Return += Invoke-CheckATPSafeAttachmentsBypass -TransportRules $Collection.Get_Item("TransportRules")

    Write-Host "$(Get-Date) Analysis - ATP - Antiphishing"
    $Return += Invoke-CheckATPPhishThreshold -AntiPhishPolicy $Collection.Get_Item("AntiPhishPolicy")
    $Return += Invoke-CheckATPPhishDomainImpAction -AntiPhishPolicy $Collection.Get_Item("AntiPhishPolicy") 
    $Return += Invoke-CheckATPPhishUserImpAction -AntiPhishPolicy $Collection.Get_Item("AntiPhishPolicy")
    $Return += Invoke-CheckATPPhishMIAction -AntiPhishPolicy $Collection.Get_Item("EXO-AntiPhishPolicy")

    Return $Return
}

Function Invoke-ORCASummary
{
    <#
 
        DETERMINE CHECK RESULTS
 
        To determine the final PASS/FAIL result of the check based on the results
        To add an affected object property which can be used in tables
 
    #>

    Param(
        $Results,
        $Checks
    )

    ForEach($Check in $Checks)
    {    
        $CheckResults = @($Results | Where-Object {$_.Control -eq $Check.Control})

        $FailResults = @($CheckResults | Where-Object {$_.Result -eq "Fail"})
        $PassResults = @($CheckResults | Where-Object {$_.Result -eq "Pass"})

        $Objects = @()
        ForEach($CheckResult in $CheckResults) {
            $Objects += New-Object -TypeName PSObject -Property @{
                ConfigItem=$($CheckResult.ConfigItem)
                ConfigData=$($CheckResult.ConfigData)
                Result=$($CheckResult.Result)
            }
        }

        If($($FailResults.Count) -eq 0)
        {
            $Result = "Pass"
        }
        else {
            $Result = "Fail"
        }

        $Check | Add-Member -NotePropertyName FailCount -NotePropertyValue $($FailResults.Count)
        $Check | Add-Member -NotePropertyName PassCount -NotePropertyValue $($PassResults.Count)
        $Check | Add-Member -NotePropertyName Result -NotePropertyValue $($Result)
        $Check | Add-Member -NotePropertyName Objects -NotePropertyValue $($Objects)

    }

    Return $Checks

}

Function Get-ORCAHtmlOutput
{
    <#
 
        OUTPUT GENERATION / Header
 
    #>

    Param(
        $Collection,
        $Checks,
        $VersionCheck
    )

    Write-Host "$(Get-Date) Generating Output" -ForegroundColor Green

    # Obtain the tenant domain and date for the report
    $TenantDomain = ($Collection["AcceptedDomains"] | Where-Object {$_.InitialDomain -eq $True}).DomainName
    $ReportDate = $(Get-Date -format 'dd-MMM-yyyy HH:mm')

    # Summary
    $RecommendationCount = $($Checks | Where-Object {$_.Result -eq "Fail"}).Count
    $OKCount = $($Checks | Where-Object {$_.Result -eq "Pass"}).Count

    # Output start
    $output = "<!doctype html>
    <html lang='en'>
    <head>
        <!-- Required meta tags -->
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
 
        <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css' crossorigin='anonymous'>
        <link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css' integrity='sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T' crossorigin='anonymous'>
 
 
        <script src='https://code.jquery.com/jquery-3.3.1.slim.min.js' integrity='sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo' crossorigin='anonymous'></script>
        <script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js' integrity='sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1' crossorigin='anonymous'></script>
        <script src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js' integrity='sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM' crossorigin='anonymous'></script>
        <script src='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/all.js'></script>
 
        <style>
        .bd-callout {
            padding: 1.25rem;
            margin-top: 1.25rem;
            margin-bottom: 1.25rem;
            border: 1px solid #eee;
            border-left-width: .25rem;
            border-radius: .25rem
        }
         
        .bd-callout h4 {
            margin-top: 0;
            margin-bottom: .25rem
        }
         
        .bd-callout p:last-child {
            margin-bottom: 0
        }
         
        .bd-callout code {
            border-radius: .25rem
        }
         
        .bd-callout+.bd-callout {
            margin-top: -.25rem
        }
         
        .bd-callout-info {
            border-left-color: #5bc0de
        }
         
        .bd-callout-info h4 {
            color: #5bc0de
        }
         
        .bd-callout-warning {
            border-left-color: #f0ad4e
        }
         
        .bd-callout-warning h4 {
            color: #f0ad4e
        }
         
        .bd-callout-danger {
            border-left-color: #d9534f
        }
         
        .bd-callout-danger h4 {
            color: #d9534f
        }
 
        .bd-callout-success {
            border-left-color: #00bd19
        }
 
        </style>
 
        <title>ORCA Report</title>
 
    </head>
    <body class='app header-fixed bg-light'>
 
        <nav class='navbar fixed-top navbar-light bg-white p-3 border-bottom'>
            <div class='container-fluid'>
                <div class='col-sm' style='text-align:left'>
                    <div class='row'><div><i class='fas fa-binoculars'></i></div><div class='ml-3'><strong>ORCA</strong></div></div>
                </div>
                <div class='col-sm' style='text-align:center'>
                    <strong>$($TenantDomain)</strong>
                </div>
                <div class='col-sm' style='text-align:right'>
                    $($ReportDate)
                </div>
            </div>
        </nav>
 
            <div class='app-body p-3'>
            <main class='main'>
                <!-- Main content here -->
                <div class='container' style='padding-top:50px;'></div>
                <div class='card'>
                         
                        <div class='card-body'>
                            <h2 class='card-title'>Office ATP Recommended Configuration Analyzer Report</h5>
                            <strong>Version $($VersionCheck.Version.ToString())</strong>
                            <p>This report details any tenant configuration changes recommended within your tenant.</p>"


        <#
 
                OUTPUT GENERATION / Version Warning
 
        #>

                                
        If($VersionCheck.Updated -eq $False) {

            $Output += "
            <div class='alert alert-danger pt-2' role='alert'>
                ORCA is out of date. You're running version $($VersionCheck.Version) but version $($VersionCheck.GalleryVersion) is available! Run Update-Module ORCA to get the latest definitions!
            </div>
             
            "


        }


                        $Output += "</div>
                </div>"




    <#
 
        OUTPUT GENERATION / Summary cards
 
    #>


    $Output += "
 
                <div class='row p-3'>
 
                <div class='col d-flex justify-content-center text-center'>
                    <div class='card text-white bg-warning mb-3' style='width: 18rem;'>
                        <div class='card-header'><h5>Recommendations</h4></div>
                        <div class='card-body'>
                        <h2>$($RecommendationCount)</h3>
                        </div>
                    </div>
                </div>
 
                <div class='col d-flex justify-content-center text-center'>
                    <div class='card text-white bg-success mb-3' style='width: 18rem;'>
                        <div class='card-header'><h5>OK</h4></div>
                        <div class='card-body'>
                        <h2>$($OKCount)</h5>
                        </div>
                    </div>
                </div>
 
            </div>
 
    "



    <#
 
        OUTPUT GENERATION / Zones
 
    #>


    ForEach ($Area in ($Checks | Group-Object Area)) {

        # Write the top of the card
        $Output += "
        <div class='card m-3'>
            <div class='card-header'>
                $($Area.Name)
            </div>
            <div class='card-body'>"


        # Each check
        ForEach ($Check in $Area.Group) {

            $Output += "
                <h5>$($Check.Name)</h5>"


                    If($Check.Result -eq "Pass") {
                        $CalloutType = "bd-callout-success"
                        $BadgeType = "badge-success"
                        $BadgeName = "OK"
                        $Icon = "fas fa-thumbs-up"
                        $Title = $Check.PassText
                    } Else {
                        $CalloutType = "bd-callout-warning"
                        $BadgeType = "badge-warning"
                        $BadgeName = "Improvement"
                        $Icon = "fas fa-thumbs-down"
                        $Title = $Check.FailRecommendation
                    }

                    $Output += "
                     
                        <div class='bd-callout $($CalloutType) b-t-1 b-r-1 b-b-1 p-3'>
                            <div class='container-fluid'>
                                <div class='row'>
                                    <div class='col-1'><i class='$($Icon)'></i></div>
                                    <div class='col-8'><h5>$($Title)</h5></div>
                                    <div class='col' style='text-align:right'><h5><span class='badge $($BadgeType)'>$($BadgeName)</span></h5></div>
                                </div>"


                        if($Check.Importance) {

                                $Output +="
                                <div class='row p-3'>
                                    <div><p>$($Check.Importance)</p></div>
                                </div>"


                        }
                        
                        If($Check.ExpandResults -eq $True) {

                            # We should expand the results by showing a table of Config Data and Items
                            $Output +="<h6>Effected objects</h6>
                            <div class='row pl-2 pt-3'>
                                <table class='table'>
                                    <thead class='border-bottom'>
                                        <tr>
                                            <th>$($Check.ItemName)</th>
                                            <th>$($Check.DataType)</th>
                                            <th style='width:50px'></th>
                                        </tr>
                                    </thead>
                                    <tbody>
                            "


                            ForEach($o in $Check.Objects)
                            {
                                if($o.Result -eq "Pass") {
                                    $oicon="fas fa-check-circle text-success"
                                } Else{
                                    $oicon="fas fa-times-circle text-danger"
                                }
                                $Output += "
                                    <tr>
                                        <td>$($o.ConfigItem)</td>
                                        <td>$($o.ConfigData)</td>
                                        <td><i class='$($oicon)'></i></td>
                                    </tr>
                                "

                            }

                            $Output +="
                                    </tbody>
                                </table>"

                                
                            # If any links exist
                            If($Check.Links)
                            {
                                $Output += "
                                <table>"


                                ForEach($Link in $Check.Links.Keys) {
                                    $Output += "
                                    <tr>
                                    <td style='width:40px'><i class='fas fa-external-link-alt'></i></td>
                                    <td><a href='$($Check.Links[$Link])'>$Link</a></td>
                                    <tr>
                                    "

                                }
                                $Output += "
                                </table>
                                "

                            }

                            $Output +="
                            </div>"


                        }

                        $Output += "
                            </div>
                        </div> "

        }            

        # End the card
        $Output+=   "
            </div>
        </div>"


    }
    <#
 
        OUTPUT GENERATION / Footer
 
    #>


    $Output += "
            </main>
            </div>
 
            <footer class='app-footer'>
            <!-- Footer content here -->
            </footer>
        </body>
    </html>"


    Return $Output
}

function Invoke-ORCAVersionCheck
{
    Param(
        $Terminate
    )

    Write-Host "$(Get-Date) Performing ORCA Version check..."

    $ORCAVersion = (Get-Module ORCA | Sort-Object Version -Desc)[0].Version
    $PSGalleryVersion = (Find-Module ORCA -Repository PSGallery -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue).Version

    If($PSGalleryVersion -gt $ORCAVersion)
    {
        $Updated = $False
        If($Terminate)
        {
            Throw "ORCA is out of date. Your version is $ORCAVersion and the published version is $PSGalleryVersion. Run Update-Module ORCA or run Get-ORCAReport with -NoUpdate."
        }
        else {
            Write-Host "$(Get-Date) ORCA is out of date. Your version: $($ORCAVersion) published version is $($PSGalleryVersion)"
        }
    }
    else
    {
        $Updated = $True
    }

    Return New-Object -TypeName PSObject -Property @{
        Updated=$Updated
        Version=$ORCAVersion
        GalleryVersion=$PSGalleryVersion
    }
}

Function Get-ORCAReport
{
    <#
        .SYNOPSIS
        The Office 365 Recommended Configuration Analyzer (ORCA) Report Generator
 
        .DESCRIPTION
        Office 365 Recommended Configuration Analyzer (ORCA)
 
        The Get-ORCAReport command generates a HTML report based on recommended practices based
        on field experiences working with Exchange Online Protection and Advanced Threat Protection.
 
        Output report uses open source components for HTML formatting
        - bootstrap - MIT License - https://getbootstrap.com/docs/4.0/about/license/
        - fontawesome - CC BY 4.0 License - https://fontawesome.com/license/free
 
        Engine and report generation
        Cam Murray
        Field Engineer - Microsoft
        camurray@microsoft.com
 
        .PARAMETER Report
 
        Optional.
 
        Full path to the report file that you want to generate. If this is not specified,
        a directory in your current users AppData is created called ORCA. Reports are generated in this
        directory in the following format:
 
        ORCA-tenantname-date.html
 
        .PARAMETER NoUpdate
 
        Optional.
 
        Switch that will tell the script not to exit in the event you are running an outdated rule
        definition. It's always recommended to be running the latest rule definition/module.
 
        .PARAMETER NoConnect
 
        Optional.
 
        Switch that will instruct ORCA not to connect and to use an already established connection
        to Exchange Online.
         
        .PARAMETER Collection
 
        Optional.
 
        For passing an already established collection object. Can be used for offline collection
        analysis.
 
        .EXAMPLE
 
        Get-ORCAReport
 
        .EXAMPLE
 
        Get-ORCAReport -Report myreport.html
 
        .EXAMPLE
 
        Get-ORCAReport -Report myreport.html -NoConnect
         
    #>

    Param(
        [CmdletBinding()]
        [Switch]$NoConnect,
        [Switch]$NoUpdate,
        $Collection,
        $Output
    )

    # Version check
    $VersionCheck = Invoke-ORCAVersionCheck

    # Unless -NoConnect specified (already connected), connect to Exchange Online
    If(!$NoConnect) {
        Invoke-ORCAConnections
    }

    # Get the object of ORCA checks
    $Checks = Get-ORCACheckDefs

    # Get the collection in to memory. For testing purposes, we support passing the collection as an object
    If($Null -eq $Collection)
    {
        $Collection = Get-ORCACollection
    }

    # Get the results
    $Return = Get-ORCAResults -Collection $Collection

    # Put data in to Checks from the results
    $Checks = Invoke-ORCASummary -Results $Return -Checks $Checks

    # Generate HTML Output
    $HTMLReport = Get-ORCAHtmlOutput -Collection $Collection -Checks $Checks -VersionCheck $VersionCheck

    # Write to file

    If(!$Output)
    {
        $OutputDirectory = Get-ORCADirectory
        $Tenant = $(($Collection["AcceptedDomains"] | Where-Object {$_.InitialDomain -eq $True}).DomainName -split '\.')[0]
        $ReportFileName = "ORCA-$($tenant)-$(Get-Date -Format 'MMddyy').html"
        $Output = "$OutputDirectory\$ReportFileName"
    }

    $HTMLReport | Out-File -FilePath $Output
    Write-Host "$(Get-Date) Complete! Output is in $Output"
    Invoke-Expression $Output

}