DefenderEval-Report.psm1

#Requires -Version 5.1

<#
.SYNOPSIS
    Verify configuration are aligning with recommended settings when performing an
    evaluation of Microsoft Defender Antivirus and Microsoft Defender for Endpoint
 
.DESCRIPTION
 
 
.NOTES
    Jonathan Devere-Ellery
    Cloud Solution Architect - Microsoft
 
 
##############################################################################################
#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.
##############################################################################################
 
#>
 

Function Get-RunningElevated {
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    Return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}


Function Invoke-ModuleVersionCheck {
    # Determines if the module is up to date
    
    $GalleryVersion = Find-Module DefenderEval
    $InstalledVersion = Get-Module DefenderEval | Select-Object -First 1

    If($GalleryVersion.Version -gt $InstalledVersion.Version) {
        Write-Host "$(Get-Date) The loaded version of the DefenderEval module ($($InstalledVersion.Version)) is older than the latest version in the PSGallery ($($GalleryVersion.Version)). Attempting to upgrade to the latest version."
        
        Try {
            Update-Module DefenderEval -Force
            Import-Module DefenderEval -RequiredVersion $GalleryVersion.Version -Force
        } Catch {
            Write-Error "Error while trying to upgrade the module. Try running Update-Module DefenderEval"
        }
        

        # Uninstall old versions
        $Modules = (Get-Module DefenderEval -ListAvailable | Sort-Object Version -Descending)
        $Latest = $Modules[0]

        If($Modules.Count -gt 1) {
            ForEach($Module in $Modules) {
                If($Module.Version -ne $Latest.Version) {
                    # Remove any out of date versions of the module
                    Write-Host "$(Get-Date) Uninstalling $($Module.Name) (Version $($Module.Version))"
                    Try {
                        Uninstall-Module $Module.Name -RequiredVersion $($Module.Version) -ErrorAction:Stop
                    } Catch {}
                }
            }
        }
    }
}

Function Invoke-CheckDefenderRecommendations {
    Write-Warning "The command to run the report has been changed to 'Get-DefenderEvaluationReport'. Please run that command instead next time."

    Get-DefenderEvaluationReport
}

Function Get-DefenderEvaluationReport {
    param (

    )

    # Prechecks
    Invoke-ModuleVersionCheck
    
    if ((Get-RunningElevated) -eq $false) {
        throw "PowerShell must be run elevated as an administrator to be able to collect data from the machine."
    }

    $Results = @()
    $MpPref = Get-MpPreference
    $MpComputerStatus = Get-MpComputerStatus
    $ComputerInfo = Get-ComputerInfo


    # Evaluate Settings

    # Collect details of configured Exclusions
    $Exclusions = [ordered]@{
        'Excluded Paths' = @($MpPref.ExclusionPath)
        'Excluded Processes' = @($MpPref.ExclusionExtension)
        'Excluded Extensions' = @($MpPref.ExclusionExtension)
        'Excluded IPs' = @($MpPref.ExclusionIpAddress)
        'Controlled Folder Access Excluded Applications' = @($MpPref.ControlledFolderAccessAllowedApplications)
    }


    # Cloud Protection - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#cloud-protection-features

    switch ($MpPref.MAPSReporting) {
        {1 -or 2} {$MAPSReporting = "Advanced"}
        default {$MAPSReporting = "Disabled"}
    }

    if ($MAPSReporting -eq "Advanced") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "MAPSReporting"
        Result = $Result
        Config = $MAPSReporting
        Description = "Enable the Microsoft Defender Cloud for near-instant protection and increased protection"
        Fix = "Set-MpPreference -MAPSReporting Advanced"
    }


    switch ($MpPref.SubmitSamplesConsent) {
        0 {$SubmitType = "AlwaysPrompt"}
        1 {$SubmitType = "SafeSamples"}
        2 {$SubmitType = "NeverSend"}
        3 {$SubmitType = "AllSamples"}
    }

    if ($SubmitType -eq "AllSamples") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "SubmitSamplesConsent"
        Result = $Result
        Config = $SubmitType
        Description = "Automatically submit samples to increase group protection"
        Fix = "Set-MpPreference -SubmitSamplesConsent SendAllSamples"
    }


    switch ($MpPref.DisableBlockAtFirstSeen) {
        $true {$BAFS = "Disabled"}
        default {$BAFS = "Enabled"}
    }
    if ($BAFS -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "BlockAtFirstSeen"
        Result = $Result
        Config = $BAFS
        Description = "Always use the cloud to block new malware within seconds"
        Fix = "Set-MpPreference -DisableBlockAtFirstSeen `$false"
    }


    switch ($MpPref.DisableIOAVProtection) {
        $true {$IOAV = "Disabled"}
        default {$IOAV = "Enabled"}
    }
    if ($IOAV -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "IOAVProtection"
        Result = $Result
        Config = $IOAV
        Description = "Scan all downloaded files and attachments"
        Fix = "Set-MpPreference -DisableIOAVProtection `$false"
    }


    switch ($MpPref.CloudBlockLevel) {
        0 {$CloudBlockLevel = "Default"}
        1 {$CloudBlockLevel = "Moderate"}
        2 {$CloudBlockLevel = "High"}
        4 {$CloudBlockLevel = "HighPlus"}
        6 {$CloudBlockLevel = "ZeroTolerance"}
        default {$CloudBlockLevel = "Default"}
    }
    if ($CloudBlockLevel -eq "High" -or $CloudBlockLevel -eq "HighPlus" -or $CloudBlockLevel -eq "ZeroTolerance") {
        $Result="Yes"
    } else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "CloudBlockLevel"
        Result = $Result
        Config = $CloudBlockLevel
        Description = "Set cloud block level to at least 'High'"
        Fix = "Set-MpPreference -CloudBlockLevel High"
    }


    if ($MpPref.CloudExtendedTimeout -ge 50) {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Cloud Protection"
        Check = "CloudExtendedTimeout"
        Result = $Result
        Config = $MpPref.CloudExtendedTimeout
        Description = "Extend cloud block time-out to 1 minute"
        Fix = "Set-MpPreference -CloudExtendedTimeout 50"
    }


    # Real-time Scanning - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#always-on-protection-real-time-scanning
    switch ($MpPref.DisableRealtimeMonitoring) {
        $true {$RTPMonitoring = "Disabled"}
        default {$RTPMonitoring = "Enabled"}
    }
    if ($RTPMonitoring -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "RealtimeMonitoring"
        Result = $Result
        Config = $RTPMonitoring
        Description = "Constantly monitor files and processes for known malware modifications"
        Fix = "Set-MpPreference -DisableRealtimeMonitoring `$false"
    }

    switch ($MpPref.RealTimeScanDirection) {
        1 {$RTPDirection = "Incoming Files"}
        2 {$RTPDirection = "Outgoing Files"}
        default {$RTPDirection = "Incoming and Outgoing Files"}
    }
    if ($RTPDirection -eq "Incoming and Outgoing Files") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "RealTimeScanDirection"
        Result = $Result
        Config = $RTPDirection
        Description = "Specifies scanning configuration for incoming and outgoing files on NTFS volumes"
        Fix = "Set-MpPreference -RealTimeScanDirection 0"
    }


    switch ($MpPref.DisableBehaviorMonitoring) {
        $true {$BehaviorMonitoring = "Disabled"}
        default {$BehaviorMonitoring = "Enabled"}
    }
    if ($BehaviorMonitoring -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "BehaviorMonitoring"
        Result = $Result
        Config = $BehaviorMonitoring
        Description = "Constantly monitor for known malware behaviors - even in 'clean' files and running programs"
        Fix = "Set-MpPreference -DisableBehaviorMonitoring `$false"
    }


    switch ($MpPref.DisableScriptScanning) {
        $true {$ScriptScanning = "Disabled"}
        default {$ScriptScanning = "Enabled"}
    }
    if ($ScriptScanning -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "ScriptScanning"
        Result = $Result
        Config = $ScriptScanning
        Description = "Scan scripts as soon as they're seen or run"
        Fix = "Set-MpPreference -DisableScriptScanning `$false"
    }


    switch ($MpPref.DisableRemovableDriveScanning) {
        $true {$RemovableDriveScanning = "Disabled"}
        default {$RemovableDriveScanning = "Enabled"}
    }
    if ($RemovableDriveScanning -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "RemovableDriveScanning"
        Result = $Result
        Config = $RemovableDriveScanning
        Description = "Scan removable drives as soon as they're inserted or mounted"
        Fix = "Set-MpPreference -DisableRemovableDriveScanning `$false"
    }


    switch ($MpPref.EnableFileHashComputation) {
        $true {$FileHash = "Enabled"}
        default {$FileHash = "Disabled"}
    }
    if ($FileHash -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Real-time Scanning"
        Check = "EnableFileHashComputation"
        Result = $Result
        Config = $FileHash
        Description = "Specifies whether to enable file hash computation for files that are scanned."
        DescriptionNote = "This improves blocking accuracy of file IoCs, however it may impact device performance"
        Fix = "Set-MpPreference -EnableFileHashComputation `$true"
    }


    # Potentially Unwanted Application protection - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#potentially-unwanted-application-protection

    switch ($MpPref.PUAProtection) {
        0 {$PUA = "Disabled"}
        1 {$PUA = "Enabled"}
        2 {$PUA = "Audit"}
    }
    if ($PUA -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Potentially Unwanted Application protection"
        Check = "PUAProtection"
        Result = $Result
        Config = $PUA
        Description = "Prevent grayware, adware, and other potentially unwanted apps from installing"
        Fix = "Set-MpPreference -PUAProtection Enabled"
    }


    # Email and archive scanning - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#email-and-archive-scanning

    switch ($MpPref.DisableArchiveScanning) {
        $true {$ArchiveScan = "Disabled"}
        default {$ArchiveScan = "Enabled"}
    }
    if ($ArchiveScan -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Email and archive scanning"
        Check = "ArchiveScanning"
        Result = $Result
        Config = $ArchiveScan
        Description = "Scan files contained within archives"
        Fix = "Set-MpPreference -DisableArchiveScanning `$false"
    }


    switch ($MpPref.DisableEmailScanning) {
        $false {$EmailScan = "Enabled"}
        default {$EmailScan = "Disabled"}
    }
    if ($EmailScan -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Email and archive scanning"
        Check = "EmailScanning"
        Result = $Result
        Config = $EmailScan
        Description = "Scan email stored within files (e.g. .PST)"
        Fix = "Set-MpPreference -DisableEmailScanning `$false"
    }

    # Protection updates - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#manage-product-and-protection-updates

    switch ($MpPref.CheckForSignaturesBeforeRunningScan) {
        $true {$SignatureUpdate = "Enabled"}
        default {$SignatureUpdate = "Disabled"}
    }
    if ($SignatureUpdate -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Scan settings"
        Check = "CheckForSignaturesBeforeRunningScan"
        Result = $Result
        Config = $SignatureUpdate
        Description = "Check to update signatures before running a scheduled scan"
        Fix = "Set-MpPreference -CheckForSignaturesBeforeRunningScan `$true"
    }

    switch ($MpPref.UILockdown) {
        $true {$UILockdown = "Disabled"}
        default {$UILockdown = "Enabled"}
    }
    if ($UILockdown -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Scan settings"
        Check = " UILockdown"
        Result = $Result
        Config = $UILockdown
        Description = "Ensure notifications allow you to boot the PC into a specialized malware removal environment"
        Fix = "Set-MpPreference -UILockdown `$false"
    }


    # Windows Server specific settings

    If ($ComputerInfo.WindowsInstallationType -eq "Server") {
        switch ($MpPref.AllowNetworkProtectionOnWinServer) {
            $true {$NPServer = "Enabled"}
            default {$NPServer = "Disabled"}
        }
        if ($NPServer -eq "Enabled") {$Result="Yes"} else {$Result="No"}

        $Results += New-Object -TypeName psobject -Property @{
            Topic = "Windows Server settings"
            Check = "AllowNetworkProtectionOnWinServer"
            Result = $Result
            Config = $NPServer
            Description = "Enable Network Protection on Windows Server"
            Fix = "Set-MpPreference -AllowNetworkProtectionOnWinServer `$true"
        }

        switch ($MpPref.AllowNetworkProtectionDownLevel) {
            $true {$NPDownlevel = "Enabled"}
            default {$NPDownlevel = "Disabled"}
        }
        if ($NPDownlevel -eq "Enabled") {$Result="Yes"} else {$Result="No"}

        $Results += New-Object -TypeName psobject -Property @{
            Topic = "Windows Server settings"
            Check = "AllowNetworkProtectionDownLevel"
            Result = $Result
            Config = $NPDownlevel
            Description = "Enable Network Protection on downlevel Windows Server"
            Fix = "Set-MpPreference -AllowNetworkProtectionDownLevel `$true"
        }

        switch ($MpPref.AllowDatagramProcessingOnWinServer) {
            $true {$NPDatagram = "Enabled"}
            default {$NPDatagram = "Disabled"}
        }
        if ($NPDatagram -eq "Enabled") {$Result="Yes"} else {$Result="No"}

        $Results += New-Object -TypeName psobject -Property @{
            Topic = "Windows Server settings"
            Check = "AllowDatagramProcessingOnWinServer"
            Result = $Result
            Config = $NPDatagram
            Description = "Enable Datagram procesing on Windows Server"
            Fix = "Set-MpPreference -AllowDatagramProcessingOnWinServer `$true"
        }

        switch ($MpPref.DisableAutoExclusions) {
            $true {$AutoExclude = "Disabled"}
            default {$AutoExclude = "Enabled"}
        }
        if ($AutoExclude -eq "Enabled") {$Result="Yes"} else {$Result="No"}

        $Results += New-Object -TypeName psobject -Property @{
            Topic = "Windows Server settings"
            Check = "AutoExclusions"
            Result = $Result
            Config = $AutoExclude
            Description = "Disable automatic exclusions on Windows Server"
            Fix = "Set-MpPreference -DisableAutoExclusions `$false"
        }
    }

    # Network protection

    switch ($MpPref.EnableNetworkProtection) {
        0 {$NetworkProtection = "Disabled"}
        1 {$NetworkProtection = "Enabled"}
        2 {$NetworkProtection = "Audit"}
    }
    if ($NetworkProtection -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "EnableNetworkProtection"
        Result = $Result
        Config = $NetworkProtection
        Description = "Block connections to known bad IP addresses and other network connections with Network protection"
        Fix = "Set-MpPreference -EnableNetworkProtection Enabled"
    }


    switch ($MpPref.DisableInboundConnectionFiltering) {
        $true{$InboundFilter = "Disabled"}
        default {$InboundFilter = "Enabled"}
    }
    if ($InboundFilter -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "InboundConnectionFiltering"
        Result = $Result
        Config = $InboundFilter
        Description = "Specifies whether to inspect only outbound connections. By default, Network Protection inspects both inbound and outbound connections"
        Fix = "Set-MpPreference -DisableInboundConnectionFiltering `$false"
    }


    switch ($MpPref.DisableDatagramProcessing) {
        $true {$DatagramParse = "Disabled"}
        default {$DatagramParse = "Enabled"}
    }
    if ($DatagramParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "DatagramProcessing"
        Result = $Result
        Config = $DatagramParse
        Description = "Inspection of UDP connections"
        Fix = "Set-MpPreference -DisableDatagramProcessing `$false"
    }


    switch ($MpPref.DisableDnsParsing) {
        $true {$DNSParse = "Disabled"}
        default {$DNSParse = "Enabled"}
    }
    if ($DNSParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "DnsParsing"
        Result = $Result
        Config = $DNSParse
        Description = "Inspection of DNS traffic that occurs over a UDP channel"
        Fix = "Set-MpPreference -DisableDnsParsing `$false"
    }


    switch ($MpPref.DisableDnsOverTcpParsing) {
        $true {$TCPDNS = "Disabled"}
        default {$TCPDNS = "Enabled"}
    }
    if ($TCPDNS -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "DnsOverTcpParsing"
        Result = $Result
        Config = $TCPDNS
        Description = "Inspection of DNS traffic that occurs over a TCP channel"
        Fix = "Set-MpPreference -DisableDnsOverTcpParsing `$false"
    }


    switch ($MpPref.EnableDnsSinkhole) {
        $true {$DnsSinkhole = "Enabled"}
        default {$DnsSinkhole = "Disabled"}
    }
    if ($DnsSinkhole -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "DnsSinkhole"
        Result = $Result
        Config = $DnsSinkhole
        Description = "Inspect DNS traffic to detect and sinkhole DNS exfiltration attempts and other DNS based malicious attacks"
        Fix = "Set-MpPreference -EnableDnsSinkhole `$true"
    }


    switch ($MpPref.DisableFtpParsing) {
        $true {$FTPParse = "Disabled"}
        default {$FTPParse = "Enabled"}
    }
    if ($FTPParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "FtpParsing"
        Result = $Result
        Config = $FTPParse
        Description = "Inspection of FTP traffic"
        Fix = "Set-MpPreference -DisableFtpParsing `$false"
    }


    switch ($MpPref.DisableHttpParsing) {
        $true {$HTTPParse = "Disabled"}
        default {$HTTPParse = "Enabled"}
    }
    if ($HTTPParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "HttpParsing"
        Result = $Result
        Config = $HTTPParse
        Description = "Inspection of HTTP traffic"
        Fix = "Set-MpPreference -DisableHttpParsing `$false"
    }


    switch ($MpPref.DisableRdpParsing) {
        $true {$RDPParse = "Disabled"}
        default {$RDPParse = "Enabled"}
    }
    if ($RDPParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "RdpParsing"
        Result = $Result
        Config = $RDPParse
        Description = "Inspect RDP traffic to look for malicious attacks using the RDP protocol"
        Fix = "Set-MpPreference -DisableRdpParsing `$false"
    }


    switch ($MpPref.DisableSmtpParsing) {
        $true {$SMTPParse = "Disabled"}
        default {$SMTPParse = "Enabled"}
    }
    if ($SMTPParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "SmtpParsing"
        Result = $Result
        Config = $SMTPParse
        Description = "Inspection of SMTP traffic"
        Fix = "Set-MpPreference -DisableSmtpParsing `$false"
    }


    switch ($MpPref.DisableSshParsing) {
        $true {$SSHParse = "Disabled"}
        default {$SSHParse = "Enabled"}
    }
    if ($SSHParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "SshParsing"
        Result = $Result
        Config = $SSHParse
        Description = "Inspection of SSH traffic"
        Fix = "Set-MpPreference -DisableSshParsing `$false"
    }


    switch ($MpPref.DisableTlsParsing) {
        $true {$TLSParse = "Disabled"}
        default {$TLSParse = "Enabled"}
    }
    if ($TLSParse -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Network protection"
        Check = "TlsParsing"
        Result = $Result
        Config = $TLSParse
        Description = "Inspect of TLS traffic to see if a connection is being made to a malicious website, and provide metadata to behavior monitoring"
        Fix = "Set-MpPreference -DisableTlsParsing `$false"
    }


    # Exploit protection - https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-antivirus-using-powershell#advanced-threat-and-exploit-mitigation-and-prevention-controlled-folder-access

    switch ($MpPref.EnableControlledFolderAccess) {
        0 {$CFA = "Disabled"}
        1 {$CFA = "Enabled"}
        2 {$CFA = "Audit"}
        3 {$CFA = "BlockDiskOnly"}
        4 {$CFA = "AuditDiskOnly"}
    }
    if ($CFA -eq "Enabled") {$Result="Yes"} else {$Result="No"}

    $Results += New-Object -TypeName psobject -Property @{
        Topic = "Exploit protection"
        Check = "EnableControlledFolderAccess"
        Result = $Result
        Config = $CFA
        Description = "Prevent malicious and suspicious apps (such as ransomware) from making changes to protected folders with Controlled folder access"
        Fix = "Set-MpPreference -EnableControlledFolderAccess Enabled"
    }


    # Define the GUIDs and the names for the attack surface reduction rules for use in the report
    # https://learn.microsoft.com/en-us/defender-endpoint/attack-surface-reduction-rules-reference
    $ASRDefinitions = @{
        "56a863a9-875e-4185-98a7-b882c64b5ce5" = "Block abuse of exploited vulnerable signed drivers";
        "7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c" = "Block Adobe Reader from creating child processes";
        "d4f940ab-401b-4efc-aadc-ad5f3c50688a" = "Block all Office applications from creating child processes";
        "9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2" = "Block credential stealing from the Windows local security authority subsystem (lsass.exe)";
        "be9ba2d9-53ea-4cdc-84e5-9b1eeee46550" = "Block executable content from email client and webmail";
        "01443614-cd74-433a-b99e-2ecdc07bfc25" = "Block executable files from running unless they meet a prevalence, age, or trusted list criterion";
        "5beb7efe-fd9a-4556-801d-275e5ffc04cc" = "Block execution of potentially obfuscated scripts";
        "d3e037e1-3eb8-44c8-a917-57927947596d" = "Block JavaScript or VBScript from launching downloaded executable content";
        "3b576869-a4ec-4529-8536-b80a7769e899" = "Block Office applications from creating executable content";
        "75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84" = "Block Office applications from injecting code into other processes";
        "26190899-1602-49e8-8b27-eb1d0a1ce869" = "Block Office communication application from creating child processes";
        "e6db77e5-3df2-4cf1-b95a-636979351e5b" = "Block persistence through WMI event subscription";
        "d1e49aac-8f56-4280-b9ba-993a6d77406c" = "Block process creations originating from PSExec and WMI commands";
        "33ddedf1-c6e0-47cb-833e-de6133960387" = "Block rebooting machine in Safe Mode";
        "b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4" = "Block untrusted and unsigned processes that run from USB";
        "c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb" = "Block use of copied or impersonated system tools";
        "a8f5898e-1dc8-49a9-9878-85004b8a61e6" = "Block Webshell creation for Servers";
        "92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b" = "Block Win32 API calls from Office macros";
        "c1db55ab-c21a-4637-bb3f-a12568109d35" = "Use advanced protection against ransomware";
    }


    $ASRIds = $MpPref.AttackSurfaceReductionRules_Ids
    $ASRActions = $MpPref.AttackSurfaceReductionRules_Actions

    $MappedASR = @()
    $i = 0

    # Map both the ASR ID and Action together within the same object to make looping through them easier
    foreach ($ASRId in $ASRIds) {
        $MappedASR += New-Object -TypeName psobject -Property @{
            ID=$ASRId
            Action=$ASRActions[$i]
        }
        $i++
    }


    ForEach ($ASR in $MappedASR) {
        # ASR Rule modes
        switch ($ASR.Action) {
            0 {$ASRState = "Disabled"}
            1 {$ASRState = "Block"}
            2 {$ASRState = "Audit"}
            6 {$ASRState = "Warn"}
        }

        if ($ASRState -eq "Block") {$Result="Yes"} else {$Result="No"}

        $ASRName = $ASRDefinitions[$ASR.ID]

        $Results += New-Object -TypeName psobject -Property @{
            Topic = "Exploit protection"
            Check = "ASR Rule ($($ASR.ID))"
            ASR = $ASR.ID
            Result = $Result
            Config = $ASRState
            Description = $ASRName
            Fix = "Add-MpPreference -AttackSurfaceReductionRules_Ids $($ASR.ID) -AttackSurfaceReductionRules_Actions Enabled"
        }
    }

    # Ensure that rows are added to the results even if any defined ASR rules are missing
    foreach ($ASRDefinition in $($ASRDefinitions.GetEnumerator())) {
        if ($Results.ASR -notcontains $($ASRDefinition.Name)) {
            $Results += New-Object -TypeName psobject -Property @{
                Topic = "Exploit protection"
                Check = "ASR Rule ($($ASRDefinition.Name))"
                Result = "No"
                Config = "Missing"
                Description = $($ASRDefinition.Value)
                Fix = "Add-MpPreference -AttackSurfaceReductionRules_Ids $($ASRDefinition.Name) -AttackSurfaceReductionRules_Actions Enabled"
            }
        }
    }

    # Return the results
    Invoke-GenerateReport -Results $Results
}


function Invoke-GenerateReport {
    param (
        $Results
    )

    $ReportTitle = "Defender Evaluation report"
    $ReportHeading = "Defender Evaluation report"
    $IntroText = "Verify configuration are aligning with recommended settings when performing an evaluation of Microsoft Defender Antivirus and Microsoft Defender for Endpoint."
    [version]$ModuleInfo = (Get-Module -Name DefenderEval | Select-Object -First 1).Version

     # 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 href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css' rel='stylesheet' integrity='sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7' crossorigin='anonymous'>
        <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css'>
 
        <title>$ReportTitle</title>
    </head>
      <body>
        <div class='container my-5'>
            <div class='position-relative p-5 text-center text-muted bg-dark-subtle border border-dashed rounded-5'>
                <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' width='60px' height='60px'><path fill='#0370c8' d='M24,44c-0.552,0-1-0.448-1-1s0.448-1,1-1V44z'/><path fill='#0f5094' d='M25,43c0,0.552-0.448,1-1,1v-2C24.552,42,25,42.448,25,43z'/><circle cx='42' cy='11' r='1' fill='#0883d9'/><circle cx='6' cy='11' r='1' fill='#33bff0'/><path fill='#0f5094' d='M24,43l0.427,0.907c0,0,15.144-7.9,18.08-19.907H24V43z'/><path fill='#0883d9' d='M43,11l-1-1c-11.122,0-11.278-6-18-6v20h18.507C42.822,22.712,43,21.378,43,20C43,16.856,43,11,43,11 z'/><path fill='#0370c8' d='M24,43l-0.427,0.907c0,0-15.144-7.9-18.08-19.907H24V43z'/><path fill='#33bff0' d='M5,11l1-1c11.122,0,11.278-6,18-6v20H5.493C5.178,22.712,5,21.378,5,20C5,16.856,5,11,5,11z'/></svg><h1 class='text-body-emphasis'>$ReportHeading</h1>
                <p class='col-lg-10 mx-auto mb-4'>$IntroText</p>
                <div class='rating-card p-0 m-2'>
                    <div class='star-rating animated-stars'>
                        <input type='radio' id='star5' name='rating' value='5' onclick=`"window.open('https://aka.ms/DefenderEval-Feedback-5','_blank');`" />
                        <label for='star5' class='bi bi-star-fill'></label>
                        <input type='radio' id='star4' name='rating' value='4' onclick=`"window.open('https://aka.ms/DefenderEval-Feedback-4','_blank');`" />
                        <label for='star4' class='bi bi-star-fill'></label>
                        <input type='radio' id='star3' name='rating' value='3' onclick=`"window.open('https://aka.ms/DefenderEval-Feedback-3','_blank');`" />
                        <label for='star3' class='bi bi-star-fill'></label>
                        <input type='radio' id='star2' name='rating' value='2' onclick=`"window.open('https://aka.ms/DefenderEval-Feedback-2','_blank');`" />
                        <label for='star2' class='bi bi-star-fill'></label>
                        <input type='radio' id='star1' name='rating' value='1' onclick=`"window.open('https://aka.ms/DefenderEval-Feedback-1','_blank');`" />
                        <label for='star1' class='bi bi-star-fill'></label>
                    </div>
                </div>
                <a class='btn btn-primary px-4 mb-4' href='https://aka.ms/mdavevaluate' role='button' target='_blank'>Learn more</a>
                <div class='text-right'>Report generated: $((get-date).ToString("dd MMMM yyyy - HH:mm:ss"))</div>
                </div>
            </div>
        </div>
        <script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js' integrity='sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq' crossorigin='anonymous'></script>
 
        <style>
        .custom-popover {
            --bs-border-width: 2px;
        }
     
        .star-rating {
            direction: rtl;
            display: inline-block;
            cursor: pointer;
        }
 
        .star-rating input {
            display: none;
        }
 
        .star-rating label {
            color: #91a6ff;
            font-size: 24px;
            padding: 0 2px;
            cursor: pointer;
            transition: all 0.2s ease;
        }
 
        .star-rating label:hover,
        .star-rating label:hover ~ label,
        .star-rating input:checked ~ label {
            color: #f7b731;
        }
 
        </style>
    "


    # Add header cards to the beginning of the report before the main results

    $output += "<div class='row justify-content-around'>" # Start of header cards


    $output += "<div class='card text-bg-light text-center p-0 border-info' style='width: 18rem;'>
        <div class='card-header h5 mb-0 text-bg-info'>Computer ID</div>
        <div class='card-body mb-0 small'>
            <p class='card-text user-select-all'>$($MpPref.ComputerID)</p>
            <p class='card-text'><strong>Platform:</strong> $($MpComputerStatus.AMProductVersion)</p>
            <p class='card-text'><strong>Engine:</strong> $($MpComputerStatus.AMEngineVersion)</p>
        </div>
    </div>"



    $output += "<div class='card text-bg-light text-center p-0 border-info' style='width: 18rem;'>
        <div class='card-header h5 text-bg-info'>Operating System</div>
            <div class='card-body small'>
            <p class='card-text'><strong>Name:</strong> $(($ComputerInfo.OsName).TrimStart('Microsoft '))</p>"

            if ($($ComputerInfo.WindowsInstallationType) -eq "Client") {
                $output += "<p class='card-text'><strong>Version:</strong> $($ComputerInfo.OSDisplayVersion)</p>"
            }
            $output += "
            <p class='card-text'><strong>Type:</strong> $($ComputerInfo.WindowsInstallationType)</p>
        </div>
    </div>"



    $output += "<div class='card text-center p-0"
    if($($MpComputerStatus.IsTamperProtected -eq $true)) {
        $output += " text-bg-success"
    } else {
        $output += " text-bg-danger"
    }
    $output += "' style='width: 18rem;'>
        <div class='card-header'><h5>Tamper Protection</h5></div>
            <div class='card-body'>
            <p class='card-text mb-2 align-middle'><strong>Enabled:</strong> $($MpComputerStatus.IsTamperProtected)</p>
            <p class='card-text align-middle'><strong>Source:</strong> $($MpComputerStatus.TamperProtectionSource)</p>
        </div>
    </div>"



    $output += "</div>" # End of header cards

    
    # Create a new table for each category within the results
    foreach ($Topic in ($Results | Group-Object Topic)){
        $output += "<div class='card m-3'>
            <h5 class='card-header bg-dark-subtle'>$($Topic.Name)</h5>
        <div class='card-body'>
        <table class='table table-hover table-striped mb-1'>
            <thead class='table-light'><tr>
                <th scope='col'></th>
                <th scope='col'>Feature</th>
                <th scope='col'>Current Value</th>
                <th scope='col'>Follows Recommendation?</th>
                <th scope='col'>Description</th>
                <th scope='col'></th>
            </tr></thead>
            <tbody>
        "


        # Add a new row for each result
        foreach ($Result in ($Results | Where-Object {$_.Topic -eq $Topic.Name})) {
            $output += "<tr><th scope='row'></th>
                <td>$($Result.Check)</td>
                <td>$($Result.Config)</td>
                <td class='text-center "

                if ($($Result.Result -eq "Yes")) {
                    $output += "table-success'"
                } else {
                    $output += "table-danger'"
                }
                $output += ">$($Result.Result)"
                if ($Result.DescriptionNote) {
                    $output += "<br><i class='bi-exclamation-triangle opacity-75' data-bs-title='$($Result.DescriptionNote)' data-bs-toggle='tooltip' data-bs-placement='top' style='font-size: 1.3rem'></i>"
                }
                $output += "</td>
                <td>$($Result.Description)</td>
                <td>"

                if ($($Result.Result -eq "No") -and $Result.Fix) {
                    $output += "<button type='button' class='btn btn-secondary float-end' data-bs-html='true' data-bs-container='body' data-bs-toggle='popover' data-bs-placement='left' data-bs-custom-class='custom-popover' data-bs-content='<p class=`"user-select-all m-0 font-monospace`"><strong>$($Result.Fix)</strong></p>'>How to fix</button>"
                }
                $output += "</td>
            </tr>"

        }

        $output += "</tbody></table></div></div>"
    }

    # Add details of Exclusions which have been configured
    foreach ($Ex in $Exclusions.Keys){
        $CollapsingName = ($Ex -replace ' ','') # Friendly name to allow collapsing of table rows

        # Add one table for each exclusion type
        $output += "<div class='card m-3'>
            <div class='h5 card-header bg-dark-subtle'>$($Ex)"

            if ($($Exclusions.$Ex).Count -ge 10) {
                $output += "<button type='button' class='btn btn-secondary btn-sm float-end' data-bs-toggle='collapse' data-bs-target='#collapse$CollapsingName'>Collapse</button>"
            }
            $output += "</div>
                <table class='table table-hover table-striped mb-0'>"

        # Allow the exclusion table rows to be collapsed
        if ($($Exclusions.$Ex).Count -ge 10) {
            $output += "<tbody class='collapse' id='collapse$CollapsingName'>"
        } else {
            $output += "<tbody>"
        }

        # Define how to add a new row to the Exclusions tables
        $Row = "<tr>
        <td scope='row'><ReplaceMe></td></tr>
        "


        if ($($Exclusions.$Ex).Count -eq 0) {
            # Add a single row indicating there are no exclusions configured
            $newRow = ($Row -replace ("<ReplaceMe>","None"))
            $newRow = ($newRow -replace ("<td ","<td class='table-success'")) # Update the background formatting if there are no exclusions
            $output += $newRow
        } else {
            # Add a row for each configured exclusion
            foreach ($obj in ($Exclusions.$Ex)) {
                $newRow = ($Row -replace ("<ReplaceMe>","<small>$Obj</small>"))
                $output += $newRow
            }
        }

        $output += "
            </tbody></table>
            </div></div>"

    }


    # Add a Footer to the end of the report
    $output += "
        <div class='card m-3 card-body text-center border-light text-body-secondary'>
            <p>Version: $ModuleInfo | <a href='https://aka.ms/DefenderEval' class='link-secondary'>GitHub</a></p>
            <script>
            const popoverTriggerList = document.querySelectorAll('[data-bs-toggle=`"popover`"]')
            const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
            const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle=`"tooltip`"]')
            const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
            const collapseElementList = document.querySelectorAll('.collapse')
            const collapseList = [...collapseElementList].map(collapseEl => new bootstrap.Collapse(collapseEl))
            </script>
        </div>
    </body>
    </html>
    "



    # Export the generated HTML file

    $Folder = (Get-Item .).FullName
    $OutFile = "DefenderEval_$(Get-Date -Format ("yyyymmdd-HHmmss")).html"
    $FilePath = Join-Path -Path $Folder -ChildPath $OutFile

    $output | Out-File -FilePath $FilePath

    Invoke-Expression "&'$FilePath'"
}