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
 
 
.PARAMETER NoPopup
    Skip opening the generated HTML file in the browser after completion.
 
 
.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 Get-DefenderEvaluationReport {
    param (
        [switch]$NoPopup
    )

    # 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";
        "c1db55ab-c21a-4637-bb3f-a12568109d35" = "Use advanced protection against ransomware";
    }

    # Add ASRs to hashtable only when report is run on an appropriate OS
    # Server OS
    if ($($ComputerInfo.WindowsInstallationType) -eq "Server") {
        $ASRDefinitions += @{
            "a8f5898e-1dc8-49a9-9878-85004b8a61e6" = "Block Webshell creation for Servers";
        }
    }
    
    # Client OS
    if ($($ComputerInfo.WindowsInstallationType) -eq "Client") {
        $ASRDefinitions += @{
            "92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b" = "Block Win32 API calls from Office macros";
        }
    }

    $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='toast-container position-fixed top-0 end-0 p-3'>
                <div class='toast show align-items-center' role='alert' aria-live='polite' aria-atomic='true' data-bs-autohide='false'>
                    <div class='toast-header'>
                        <strong class='me-auto'>Rate this report!</strong>
                        <button type='button' class='btn-close me-2 m-auto' data-bs-dismiss='toast' aria-label='Close'></button>
                    </div>
                    <div class='toast-body'>
                        <div class='rating-card p-0 m-0'>
                            <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>
                    </div>
                </div>
            </div>
            <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>
                <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;
        }
 
        .toast {
            max-width: 240px;
        }
     
        .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-fill 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

    if (!$NoPopup) {
        Invoke-Expression "&'$FilePath'"
    }
}