Core/Confirm-SystemCompliance.psm1
function Confirm-SystemCompliance { [CmdletBinding()] [OutputType([System.String], [System.Collections.Concurrent.ConcurrentDictionary[System.String, HardeningModule.IndividualResult[]]])] param ( [ArgumentCompleter({ # Get the current command and the already bound parameters param($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters) # Find all string constants in the AST $Existing = $CommandAst.FindAll( # The predicate scriptblock to define the criteria for filtering the AST nodes { $Args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] }, # The recurse flag, whether to search nested scriptblocks or not. $false ).Value foreach ($Item in [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { # Check if the item is already selected if ($Item -notin $Existing) { # Return the item $Item } } })] [ValidateScript({ if ($_ -notin [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { throw "Invalid Category Name: $_" } # Return true if everything is okay $true })] [System.String[]]$Categories, [parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$ExportToCSV, [parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$ShowAsObjectsOnly, [parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$DetailedDisplay, [parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$Offline ) begin { [HardeningModule.Initializer]::Initialize() [HardeningModule.GlobalVars]::MDAVConfigCurrent = Get-MpComputerStatus # Importing the required sub-modules Write-Verbose -Message 'Importing the required sub-modules' Import-Module -FullyQualifiedName "$([HardeningModule.GlobalVars]::Path)\Shared\Update-self.psm1" -Force -Verbose:$false # Makes sure this cmdlet is invoked with Admin privileges if (-NOT ([HardeningModule.UserPrivCheck]::IsAdmin())) { Throw [System.Security.AccessControl.PrivilegeNotHeldException] 'Administrator' } if (-NOT $Offline) { Write-Verbose -Message 'Checking for updates...' Update-Self -InvocationStatement $MyInvocation.Statement } if ((Get-CimInstance -ClassName Win32_OperatingSystem -Verbose:$false).OperatingSystemSKU -in '101', '100') { Write-Warning -Message 'The Windows Home edition has been detected, many features are unavailable in this edition.' } #Region Defining-Variables # a Synchronized HashTable to safely increment/decrement values from multiple threads and also access parent scope variables inside thread jobs $SyncHash = [System.Collections.Hashtable]::Synchronized(@{}) # Total number of Compliant values not equal to N/A $SyncHash['TotalNumberOfTrueCompliantValues'] = 239 $SyncHash['VerbosePreference'] = $VerbosePreference # An object to store the FINAL results $FinalMegaObject = [System.Collections.Concurrent.ConcurrentDictionary[System.String, HardeningModule.IndividualResult[]]]::new() # The total number of the steps for the parent/main progress bar to render [System.UInt16]$TotalMainSteps = 2 [System.UInt16]$CurrentMainStep = 0 #EndRegion Defining-Variables #Region Colors [System.Collections.Hashtable]$global:ColorsMap = @{ Plum = @{ Code = '221', '160', '221' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(221,160,221))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } Orchid = @{ Code = '218', '112', '214' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(218,112,214))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } Fuchsia = @{ Code = '255', '0', '255' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,0,255))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } MediumOrchid = @{ Code = '186', '85', '211' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(186,85,211))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } MediumPurple = @{ Code = '147', '112', '219' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(147,112,219))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } BlueViolet = @{ Code = '138', '43', '226' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(138,43,226))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } AndroidGreen = @{ Code = '176', '191', '26' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(176,191,26))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } Pink = @{ Code = '255', '192', '203' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,192,203))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } HotPink = @{ Code = '255', '105', '180' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,105,180))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } DeepPink = @{ Code = '255', '20', '147' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,20,147))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } MintGreen = @{ Code = '152', '255', '152' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(152,255,152))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } Orange = @{ Code = '255', '165', '0' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,165,0))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } SkyBlue = @{ Code = '135', '206', '235' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(135,206,235))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } Daffodil = @{ Code = '255', '255', '49' ScriptBlock = { Write-Output -InputObject "$($PSStyle.Foreground.FromRGB(255,255,49))$($PSStyle.Reverse)$($Args[0])$($PSStyle.Reset)" } } } # Defining a validate set class for the colors Class Colorsx : System.Management.Automation.IValidateSetValuesGenerator { [System.String[]] GetValidValues() { $Colorsx = @($global:ColorsMap.Keys) return [System.String[]]$Colorsx } } # An array of colors used in multiple places [System.Drawing.Color[]]$Global:Colors = @( [System.Drawing.Color]::SkyBlue, [System.Drawing.Color]::Pink, [System.Drawing.Color]::HotPink, [System.Drawing.Color]::Lavender, [System.Drawing.Color]::LightGreen, [System.Drawing.Color]::Coral, [System.Drawing.Color]::Plum, [System.Drawing.Color]::Gold ) [System.Management.Automation.ScriptBlock]$WriteRainbow = { Param([System.String]$Text) $StringBuilder = New-Object -TypeName System.Text.StringBuilder for ($i = 0; $i -lt $Text.Length; $i++) { $Color = $Global:Colors[$i % $Global:Colors.Length] [System.Void]$StringBuilder.Append("$($PSStyle.Foreground.FromRGB($Color.R, $Color.G, $Color.B))$($Text[$i])$($PSStyle.Reset)") } Write-Output -InputObject $StringBuilder.ToString() } #Endregion Colors } process { try { #Region Rainbow Progress Bar # Define a variable to store the current color index [System.UInt16]$Global:ColorIndex = 0 # Create a timer object that fires every 2 seconds [System.Timers.Timer]$RainbowTimer = New-Object System.Timers.Timer $RainbowTimer.Interval = 2000 # milliseconds $RainbowTimer.AutoReset = $true # repeat until stopped # Register an event handler that changes Write-Progress' style every time the timer elapses [System.Management.Automation.PSEventJob]$EventHandler = Register-ObjectEvent -InputObject $RainbowTimer -EventName Elapsed -Action { $Global:ColorIndex++ if ($Global:ColorIndex -ge $Global:Colors.Length) { $Global:ColorIndex = 0 } # Get the current color from the array [System.Drawing.Color]$CurrentColor = $Global:Colors[$Global:ColorIndex] # Set the progress bar style to use the current color and the blink effect $PSStyle.Progress.Style = "$($PSStyle.Foreground.FromRGB($CurrentColor.R, $CurrentColor.G, $CurrentColor.B))$($PSStyle.Blink)" } # Start the timer $RainbowTimer.Start() #Endregion Rainbow Progress Bar $CurrentMainStep++ Write-Progress -Id 0 -Activity 'Gathering Security Policy Information' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) # Get the security group policies $null = &"$env:SystemDrive\Windows\System32\Secedit.exe" /export /cfg .\security_policy.inf # Storing the output of the ini file parsing function $SyncHash['SecurityPoliciesIni'] = [HardeningModule.IniFileConverter]::ConvertFromIniFile('.\security_policy.inf') $CurrentMainStep++ Write-Progress -Id 0 -Activity 'Verifying the security settings' -Status "Step $CurrentMainStep/$TotalMainSteps" -PercentComplete ($CurrentMainStep / $TotalMainSteps * 100) #Region Main-Functions Function Invoke-MicrosoftDefender { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:MicrosoftDefenderJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) Try { $ErrorActionPreference = 'Stop' $VerbosePreference = $SyncHash['VerbosePreference'] # A try-Catch-finally block to revert the changes being made to the Controlled Folder Access exclusions list # Which is currently required for BCD NX value verification in the MicrosoftDefender category # backup the currently allowed apps list in Controlled folder access in order to restore them at the end # doing this so that when we Add and then Remove PowerShell executables in Controlled folder access exclusions # no user customization will be affected [System.String[]]$CFAAllowedAppsBackup = ([HardeningModule.GlobalVars]::MDAVPreferencesCurrent).ControlledFolderAccessAllowedApplications # Temporarily allow the currently running PowerShell executables to the Controlled Folder Access allowed apps # so that the script can run without interruption. This change is reverted at the end. foreach ($FilePath in (Get-ChildItem -Path "$PSHOME\*.exe" -File).FullName) { Add-MpPreference -ControlledFolderAccessAllowedApplications $FilePath } # Give the Defender internals time to process the updated exclusions list Start-Sleep -Seconds '5' # An array to store the nested custom objects, inside the main output object $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'MicrosoftDefender' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add([HardeningModule.IndividualResult]$Result) } $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.AllowSwitchToAsyncInspection $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'AllowSwitchToAsyncInspection' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'AllowSwitchToAsyncInspection' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.oobeEnableRtpAndSigUpdate $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'oobeEnableRtpAndSigUpdate' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'oobeEnableRtpAndSigUpdate' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.IntelTDTEnabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'IntelTDTEnabled' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'IntelTDTEnabled' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = $((Get-ProcessMitigation -System).aslr.ForceRelocateImages) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Mandatory ASLR' Compliant = $IndividualItemResult -eq 'on' ? $True : $false Value = $IndividualItemResult Name = 'Mandatory ASLR' Category = $CatName Method = 'Cmdlet' }) # Verify the NX bit as shown in bcdedit /enum or Get-BcdEntry, info about numbers and values correlation: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/bcd/bcdosloader-nxpolicy $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Boot Configuration Data (BCD) No-eXecute (NX) Value' Compliant = (((Get-BcdEntry).elements | Where-Object -FilterScript { $_.Name -eq 'nx' }).value -eq '3') Value = (((Get-BcdEntry).elements | Where-Object -FilterScript { $_.Name -eq 'nx' }).value -eq '3') Name = 'Boot Configuration Data (BCD) No-eXecute (NX) Value' Category = $CatName Method = 'Cmdlet' }) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Smart App Control State' Compliant = ([HardeningModule.GlobalVars]::MDAVConfigCurrent.SmartAppControlState -eq 'On') ? $True : $False Value = [HardeningModule.GlobalVars]::MDAVConfigCurrent.SmartAppControlState Name = 'Smart App Control State' Category = $CatName Method = 'Cmdlet' }) try { $IndividualItemResult = $((Get-ScheduledTask -TaskPath '\MSFT Driver Block list update\' -TaskName 'MSFT Driver Block list update' -ErrorAction SilentlyContinue) ? $True : $false) } catch { # suppress any possible terminating errors } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Fast weekly Microsoft recommended driver block list update' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Fast weekly Microsoft recommended driver block list update' Category = $CatName Method = 'Cmdlet' }) [System.Collections.Hashtable]$DefenderPlatformUpdatesChannels = @{ 0 = 'NotConfigured' 2 = 'Beta' 3 = 'Preview' 4 = 'Staged' 5 = 'Broad' 6 = 'Delayed' } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Microsoft Defender Platform Updates Channel' Compliant = 'N/A' Value = ($DefenderPlatformUpdatesChannels[[System.Int32]([HardeningModule.GlobalVars]::MDAVPreferencesCurrent).PlatformUpdatesChannel]) Name = 'Microsoft Defender Platform Updates Channel' Category = $CatName Method = 'Cmdlet' }) [System.Collections.Hashtable]$DefenderEngineUpdatesChannels = @{ 0 = 'NotConfigured' 2 = 'Beta' 3 = 'Preview' 4 = 'Staged' 5 = 'Broad' 6 = 'Delayed' } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Microsoft Defender Engine Updates Channel' Compliant = 'N/A' Value = ($DefenderEngineUpdatesChannels[[System.Int32]([HardeningModule.GlobalVars]::MDAVPreferencesCurrent).EngineUpdatesChannel]) Name = 'Microsoft Defender Engine Updates Channel' Category = $CatName Method = 'Cmdlet' }) # This covers instances where CFA is applied through Intune policy $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Controlled Folder Access' Compliant = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.EnableControlledFolderAccess -eq 1 ? $true : $false Value = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.EnableControlledFolderAccess Name = 'Controlled Folder Access' Category = $CatName Method = 'Cmdlet' }) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Controlled Folder Access Exclusions' Compliant = 'N/A' Value = ([HardeningModule.GlobalVars]::MDAVPreferencesCurrent.ControlledFolderAccessAllowedApplications -join ',') # Join the array elements into a string to display them properly in the output CSV file Name = 'Controlled Folder Access Exclusions' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.DisableRestorePoint $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Enable Restore Point scanning' Compliant = ($IndividualItemResult -eq $False) Value = ($IndividualItemResult -eq $False) Name = 'Enable Restore Point scanning' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.PerformanceModeStatus $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'PerformanceModeStatus' Compliant = [System.Boolean]($IndividualItemResult -eq '0') Value = $IndividualItemResult Name = 'PerformanceModeStatus' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.EnableConvertWarnToBlock $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'EnableConvertWarnToBlock' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'EnableConvertWarnToBlock' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.BruteForceProtectionAggressiveness $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'BruteForceProtectionAggressiveness' Compliant = [System.Boolean]($IndividualItemResult -in ('1', '2')) Value = $IndividualItemResult Name = 'BruteForceProtectionAggressiveness' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.BruteForceProtectionConfiguredState $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'BruteForceProtectionConfiguredState' Compliant = [System.Boolean]($IndividualItemResult -eq '1') Value = $IndividualItemResult Name = 'BruteForceProtectionConfiguredState' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.BruteForceProtectionMaxBlockTime $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'BruteForceProtectionMaxBlockTime' Compliant = [System.Boolean]($IndividualItemResult -in ('0', '4294967295')) Value = $IndividualItemResult Name = 'BruteForceProtectionMaxBlockTime' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.RemoteEncryptionProtectionAggressiveness $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'RemoteEncryptionProtectionAggressiveness' Compliant = [System.Boolean]($IndividualItemResult -eq '2') Value = $IndividualItemResult Name = 'RemoteEncryptionProtectionAggressiveness' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.RemoteEncryptionProtectionConfiguredState $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'RemoteEncryptionProtectionConfiguredState' Compliant = [System.Boolean]($IndividualItemResult -eq '1') Value = $IndividualItemResult Name = 'RemoteEncryptionProtectionConfiguredState' Category = $CatName Method = 'Cmdlet' }) $IndividualItemResult = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.RemoteEncryptionProtectionMaxBlockTime $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'RemoteEncryptionProtectionMaxBlockTime' Compliant = [System.Boolean]($IndividualItemResult -in ('0', '4294967295')) Value = $IndividualItemResult Name = 'RemoteEncryptionProtectionMaxBlockTime' Category = $CatName Method = 'Cmdlet' }) #Region Microsoft-Defender-Exploit-Guard-Category # Get the current system's exploit mitigation policy XML file using the Get-ProcessMitigation cmdlet [System.String]$RandomGUID = (New-Guid).Guid.ToString() Get-ProcessMitigation -RegistryConfigFilePath ".\CurrentlyAppliedMitigations-$RandomGUID.xml" # Load the XML file as an XML object [System.Xml.XmlDocument]$SystemMitigationsXML = Get-Content -Path ".\CurrentlyAppliedMitigations-$RandomGUID.xml" -Force # Delete the XML file after loading it Remove-Item -Path ".\CurrentlyAppliedMitigations-$RandomGUID.xml" -Force #Region System-Mitigations-Processing # A hashtable to store the output of the current system's exploit mitigation policy XML file exported by the Get-ProcessMitigation cmdlet [System.Collections.Hashtable]$ProcessMitigationsOnTheSystem = @{} # Loop through each AppConfig element in the XML object foreach ($App in $SystemMitigationsXML.MitigationPolicy.AppConfig) { # Get the executable name of the app [System.String]$Name = $App.Executable # Create an empty array to store the mitigations $Mitigations = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]' # Loop through each child element of the app element foreach ($Child in $App.ChildNodes ) { # Get the name of the mitigation [System.String]$Mitigation = $Child.Name # Loop through each attribute of the child element foreach ($Attribute in $Child.Attributes) { # Get the name and value of the attribute [System.String]$AttributeName = $Attribute.Name [System.String]$AttributeValue = $Attribute.Value # If the attribute value is true, add it to the array # We don't include the mitigations that are disabled/set to false # For example, some poorly designed git apps are incompatible with mandatory ASLR # And they pollute the output of the Get-ProcessMitigation cmdlet with items such as "<ASLR ForceRelocateImages="false" RequireInfo="false" />" if ($AttributeValue -eq 'true') { # If the attribute name is Enable, use the mitigation name instead, because we only need the names of the mitigations that are enabled for comparison with the CSV file. # Some attributes such as "<StrictHandle Enable="true" />" don't have a name so we add the mitigation's name to the array instead, which is "StrictHandle" in this case. if ($AttributeName -eq 'Enable') { [System.Void]$Mitigations.Add($Mitigation) } else { [System.Void]$Mitigations.Add($AttributeName) } } } } # Make sure the array isn't empty which filters out apps with no mitigations or mitigations that are all disabled/set to false if ($Mitigations.Count -ne 0) { # Create a hashtable entry with the name and mitigations properties $ProcessMitigationsOnTheSystem[$Name] = $Mitigations } } # Create a new empty hashtable which replaces "ControlFlowGuard" with "CFG" since the shortened name is used in the CSV file and required by the the Set-ProcessMitigation cmdlet [System.Collections.Hashtable]$RevisedProcessMitigationsOnTheSystem = @{} # Loop over the keys and values of the original hashtable foreach ($Key in $ProcessMitigationsOnTheSystem.Keys) { # Get the value array for the current key [System.String[]]$Value = $ProcessMitigationsOnTheSystem[$Key] # Replace "ControlFlowGuard" with "CFG" in the value array [System.String[]]$Value = $Value -replace 'ControlFlowGuard', 'CFG' # Add the modified key-value pair to the new hashtable [System.Void]$RevisedProcessMitigationsOnTheSystem.add($Key, $Value) } #Endregion System-Mitigations-Processing #Region Harden-Windows-Security-Module-CSV-Processing # Import the CSV file as an object [HardeningModule.ProcessMitigationsParser+ProcessMitigationsRecords[]]$ProcessMitigations = [HardeningModule.GlobalVars]::ProcessMitigations # Only keep the enabled mitigations in the CSV, then Group the data by ProgramName [Microsoft.PowerShell.Commands.GroupInfo[]]$GroupedMitigations = $ProcessMitigations.Where({ $_.Action -eq 'Enable' }) | Group-Object -Property ProgramName # A hashtable to store the output of the CSV file [System.Collections.Hashtable]$TargetMitigations = @{} # Loop through each group in the grouped mitigations array and add the ProgramName and Mitigations to the hashtable foreach ($Item in $GroupedMitigations) { $TargetMitigations[$Item.Name] = $Item.Group.Mitigation } #Endregion Harden-Windows-Security-Module-CSV-Processing #Region Comparison # Compare the values of the two hashtables if the keys match $TargetMitigations.GetEnumerator() | ForEach-Object -Process { # Get the current key and value from hashtable containing the CSV data [System.String]$ProcessName_Target = $_.Key [System.String[]]$ProcessMitigations_Target = $_.Value # Check if the hashtable containing the currently applied mitigations contains the same key # Meaning the same executable is present in both hashtables if ($RevisedProcessMitigationsOnTheSystem.ContainsKey($ProcessName_Target)) { # Get the value from the applied mitigations hashtable [System.String[]]$ProcessMitigations_Applied = $RevisedProcessMitigationsOnTheSystem[$ProcessName_Target] # Compare the values of the two hashtables to see if they are the same without the order of the elements (process mitigations) in the arrays being considered # Compare-Object produces output only if the objects are different if (Compare-Object -ReferenceObject $ProcessMitigations_Target -DifferenceObject $ProcessMitigations_Applied) { # If the values are different, it means the process has different mitigations applied to it than the ones in the CSV file Write-Verbose -Message "Mitigations for $ProcessName_Target were found but are not compliant" # Increment the total number of the verifiable compliant values for each process that has a mitigation applied to it in the CSV file $SyncHash['TotalNumberOfTrueCompliantValues']++ $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Process Mitigations for: $ProcessName_Target" Compliant = $False Value = ($ProcessMitigations_Applied -join ',') # Join the array elements into a string to display them properly in the output CSV file Name = "Process Mitigations for: $ProcessName_Target" Category = $CatName Method = 'Cmdlet' }) } else { # If the values are the same, it means the process has the same mitigations applied to it as the ones in the CSV file Write-Verbose -Message "Mitigations for $ProcessName_Target are compliant" # Increment the total number of the verifiable compliant values for each process that has a mitigation applied to it in the CSV file $SyncHash['TotalNumberOfTrueCompliantValues']++ $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Process Mitigations for: $ProcessName_Target" Compliant = $true Value = ($ProcessMitigations_Target -join ',') # Join the array elements into a string to display them properly in the output CSV file Name = "Process Mitigations for: $ProcessName_Target" Category = $CatName Method = 'Cmdlet' }) } } else { # If the process name is not found in the hashtable containing the currently applied mitigations, it means the process doesn't have any mitigations applied to it Write-Verbose -Message "Mitigations for $ProcessName_Target were not found" # Increment the total number of the verifiable compliant values for each process that has a mitigation applied to it in the CSV file $SyncHash['TotalNumberOfTrueCompliantValues']++ $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Process Mitigations for: $ProcessName_Target" Compliant = $False Value = 'N/A' Name = "Process Mitigations for: $ProcessName_Target" Category = $CatName Method = 'Cmdlet' }) } } #Endregion Comparison #Endregion Microsoft-Defender-Exploit-Guard-Category # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } catch { Write-Verbose -Message $_ -Verbose Throw $_ } finally { # Reverting the PowerShell executables allow listings in Controlled folder access foreach ($FilePath in (Get-ChildItem -Path "$PSHOME\*.exe" -File).FullName) { Remove-MpPreference -ControlledFolderAccessAllowedApplications $FilePath } # restoring the original Controlled folder access allow list - if user already had added PowerShell executables to the list # they will be restored as well, so user customization will remain intact if ($null -ne $CFAAllowedAppsBackup) { Set-MpPreference -ControlledFolderAccessAllowedApplications $CFAAllowedAppsBackup } } } -Name 'Invoke-MicrosoftDefender' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-AttackSurfaceReductionRules { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:AttackSurfaceReductionRulesJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'AttackSurfaceReductionRules' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Individual ASR rules verification [System.String[]]$Ids = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.AttackSurfaceReductionRules_Ids [System.String[]]$Actions = [HardeningModule.GlobalVars]::MDAVPreferencesCurrent.AttackSurfaceReductionRules_Actions # If $Ids variable is not empty, convert them to lower case because some IDs can be in upper case and result in inaccurate comparison if ($Ids) { $Ids = $Ids.tolower() } # Hashtable to store the descriptions for each ID [System.Collections.Hashtable]$ASRsTable = @{ '26190899-1602-49e8-8b27-eb1d0a1ce869' = 'Block Office communication application from creating child processes' 'd1e49aac-8f56-4280-b9ba-993a6d77406c' = 'Block process creations originating from PSExec and WMI commands' 'b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4' = 'Block untrusted and unsigned processes that run from USB' '92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b' = 'Block Win32 API calls from Office macros' '7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c' = 'Block Adobe Reader from creating child processes' '3b576869-a4ec-4529-8536-b80a7769e899' = 'Block Office applications from creating executable content' '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' 'e6db77e5-3df2-4cf1-b95a-636979351e5b' = 'Block persistence through WMI event subscription' '75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84' = 'Block Office applications from injecting code into other processes' '56a863a9-875e-4185-98a7-b882c64b5ce5' = 'Block abuse of exploited vulnerable signed drivers' 'c1db55ab-c21a-4637-bb3f-a12568109d35' = 'Use advanced protection against ransomware' 'd3e037e1-3eb8-44c8-a917-57927947596d' = 'Block JavaScript or VBScript from launching downloaded executable content' '33ddedf1-c6e0-47cb-833e-de6133960387' = 'Block rebooting machine in Safe Mode' 'c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb' = 'Block use of copied or impersonated system tools' 'a8f5898e-1dc8-49a9-9878-85004b8a61e6' = 'Block Webshell creation for Servers' } # Loop over each ID in the hashtable foreach ($Name in $ASRsTable.Keys) { # Check if the $Ids array is not empty and current ID is present in the $Ids array if ($Ids -and $Ids -icontains $Name) { # If yes, check if the $Actions array is not empty if ($Actions) { # If yes, use the index of the ID in the array to access the action value $Action = $Actions[$Ids.IndexOf($Name)] } else { # If no, assign a default action value of 0 $Action = 0 } } else { # If no, assign a default action value of 0 $Action = 0 } # An exception for the ASR rule with ID 'c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb' # 'Block use of copied or impersonated system tools' # Because it's in preview and is set to 6 for Warn instead of 1 for block if ($Name -eq 'c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb') { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = $ASRsTable[$name] Compliant = [System.Boolean]($Action -in '6', '1') # Either 6 or 1 is compliant and acceptable Value = $Action Name = $Name Category = $CatName Method = 'Cmdlet' }) } # For ease of use this is valid if it's set to block or warn elseif ($Name -eq '01443614-cd74-433a-b99e-2ecdc07bfc25') { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = $ASRsTable[$name] Compliant = [System.Boolean]($Action -in '6', '1') # Either 6 or 1 is compliant and acceptable Value = $Action Name = $Name Category = $CatName Method = 'Cmdlet' }) } else { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = $ASRsTable[$name] Compliant = [System.Boolean]($Action -eq 1) # Compare action value with 1 and cast to boolean Value = $Action Name = $Name Category = $CatName Method = 'Cmdlet' }) } } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-AttackSurfaceReductionRules' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-BitLockerSettings { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:BitLockerSettingsJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $PSDefaultParameterValues = @{ 'Get-BitLockerVolume:Verbose' = $false 'Get-Volume:Verbose' = $false } $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'BitLockerSettings' # Returns true or false depending on whether Kernel DMA Protection is on or off [System.Boolean]$BootDMAProtection = ([SystemInfo.NativeMethods]::BootDmaCheck()) -ne 0 # Get the status of Bitlocker DMA protection try { [System.Int32]$BitlockerDMAProtectionStatus = Get-ItemPropertyValue -Path 'Registry::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE' -Name 'DisableExternalDMAUnderLock' -ErrorAction SilentlyContinue } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch } # Bitlocker DMA counter measure status # Returns true if only either Kernel DMA protection is on and Bitlocker DMA protection if off # or Kernel DMA protection is off and Bitlocker DMA protection is on [System.Boolean]$ItemState = ($BootDMAProtection -xor ($BitlockerDMAProtectionStatus -eq '1')) ? $True : $False # Create a custom object with 5 properties to store them as nested objects inside the main output object $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'DMA protection' Compliant = $ItemState Value = $ItemState Name = 'DMA protection' Category = $CatName Method = 'Group Policy' }) # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # To detect if Hibernate is enabled and set to full if (-NOT ([HardeningModule.GlobalVars]::MDAVConfigCurrent.IsVirtualMachine)) { try { $IndividualItemResult = $($((Get-ItemProperty 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power' -Name 'HiberFileType' -ErrorAction SilentlyContinue).HiberFileType) -eq 2 ? $True : $False) } catch { # suppress the errors if any } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Hibernate is set to full' Compliant = [System.Boolean]($IndividualItemResult) Value = [System.Boolean]($IndividualItemResult) Name = 'Hibernate is set to full' Category = $CatName Method = 'Cmdlet' }) } else { $SyncHash['TotalNumberOfTrueCompliantValues']-- } # OS Drive encryption verifications # Check if BitLocker is on for the OS Drive # The ProtectionStatus remains off while the drive is encrypting or decrypting if ((Get-BitLockerVolume -MountPoint $env:SystemDrive).ProtectionStatus -eq 'on') { # Get the key protectors of the OS Drive [System.String[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector.keyprotectortype # Check if TPM+PIN and recovery password are being used - Normal Security level if (($KeyProtectors -contains 'Tpmpin') -and ($KeyProtectors -contains 'RecoveryPassword')) { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $True Value = 'Normal Security Level' Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' }) } # Check if TPM+PIN+StartupKey and recovery password are being used - Enhanced security level elseif (($KeyProtectors -contains 'TpmPinStartupKey') -and ($KeyProtectors -contains 'RecoveryPassword')) { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $True Value = 'Enhanced Security Level' Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' }) } else { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $false Value = $false Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' }) } } else { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Secure OS Drive encryption' Compliant = $false Value = $false Name = 'Secure OS Drive encryption' Category = $CatName Method = 'Cmdlet' }) } #region Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Get the list of non OS volumes [System.Object[]]$NonOSBitLockerVolumes = Get-BitLockerVolume | Where-Object -FilterScript { $_.volumeType -ne 'OperatingSystem' } # Get all the volumes and filter out removable ones [System.Object[]]$RemovableVolumes = Get-Volume | Where-Object -FilterScript { ($_.DriveType -eq 'Removable') -and $_.DriveLetter } # Check if there is any removable volumes if ($RemovableVolumes) { # Get the letters of all the removable volumes [System.String[]]$RemovableVolumesLetters = foreach ($RemovableVolume in $RemovableVolumes) { $(($RemovableVolume).DriveLetter + ':' ) } # Filter out removable drives from BitLocker volumes to process $NonOSBitLockerVolumes = $NonOSBitLockerVolumes | Where-Object -FilterScript { $_.MountPoint -notin $RemovableVolumesLetters } } # Check if there is any non-OS volumes if ($NonOSBitLockerVolumes) { # Loop through each non-OS volume and verify their encryption foreach ($MountPoint in $($NonOSBitLockerVolumes | Sort-Object).MountPoint) { # Increase the number of available compliant values for each non-OS drive that was found $SyncHash['TotalNumberOfTrueCompliantValues']++ # If status is unknown, that means the non-OS volume is encrypted and locked, if it's on then it's on if ((Get-BitLockerVolume -MountPoint $MountPoint).ProtectionStatus -in 'on', 'Unknown') { # Check 1: if Recovery Password and Auto Unlock key protectors are available on the drive [System.Object[]]$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector.keyprotectortype if (($KeyProtectors -contains 'RecoveryPassword') -or ($KeyProtectors -contains 'Password')) { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Secure Drive $MountPoint encryption" Compliant = $True Value = 'Encrypted' Name = "Secure Drive $MountPoint encryption" Category = $CatName Method = 'Cmdlet' }) } else { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Secure Drive $MountPoint encryption" Compliant = $false Value = 'Not properly encrypted' Name = "Secure Drive $MountPoint encryption" Category = $CatName Method = 'Cmdlet' }) } } else { $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Secure Drive $MountPoint encryption" Compliant = $false Value = 'Not encrypted' Name = "Secure Drive $MountPoint encryption" Category = $CatName Method = 'Cmdlet' }) } } } #endregion Non-OS-Drive-BitLocker-Drives-Encryption-Verification # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-BitLockerSettings' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-TLSSecurity { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:TLSSecurityJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'TLSSecurity' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # ECC Curves [System.String[]]$ECCCurves = Get-TlsEccCurve [System.String[]]$List = ('nistP521', 'curve25519', 'NistP384', 'NistP256') # Make sure both arrays are completely identical in terms of members and their exact position # If this variable is empty that means both arrays are completely identical $IndividualItemResult = Compare-Object -ReferenceObject $ECCCurves -DifferenceObject $List -SyncWindow 0 $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'ECC Curves and their positions' Compliant = [System.Boolean]($IndividualItemResult ? $false : $True) Value = ($List -join ',') # Join the array elements into a string to display them properly in the output CSV file Name = 'ECC Curves and their positions' Category = $CatName Method = 'Cmdlet' }) # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Registry Keys'))) { $NestedObjectArray.Add($Result) } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-TLSSecurity' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-LockScreen { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:LockScreenJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'LockScreen' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\InactivityTimeoutSecs'] -eq '4,120') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Machine inactivity limit' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Machine inactivity limit' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD'] -eq '4,0') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Interactive logon: Do not require CTRL+ALT+DEL' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Interactive logon: Do not require CTRL+ALT+DEL' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\MaxDevicePasswordFailedAttempts'] -eq '4,5') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Interactive logon: Machine account lockout threshold' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Interactive logon: Machine account lockout threshold' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLockedUserId'] -eq '4,4') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Interactive logon: Display user information when the session is locked' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Interactive logon: Display user information when the session is locked' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayUserName'] -eq '4,1') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Interactive logon: Don't display username at sign-in" Compliant = $IndividualItemResult Value = $IndividualItemResult Name = "Interactive logon: Don't display username at sign-in" Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'System Access'['LockoutBadCount'] -eq '5') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Account lockout threshold' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Account lockout threshold' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'System Access'['LockoutDuration'] -eq '1440') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Account lockout duration' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Account lockout duration' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'System Access'['ResetLockoutCount'] -eq '1440') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Reset account lockout counter after' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Reset account lockout counter after' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName'] -eq '4,1') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = "Interactive logon: Don't display last signed-in" Compliant = $IndividualItemResult Value = $IndividualItemResult Name = "Interactive logon: Don't display last signed-in" Category = $CatName Method = 'Security Group Policy' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-LockScreen' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-UserAccountControl { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:UserAccountControlJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'UserAccountControl' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorAdmin'] -eq '4,2') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'UAC: Behavior of the elevation prompt for administrators in Admin Approval Mode' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'UAC: Behavior of the elevation prompt for administrators in Admin Approval Mode' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ConsentPromptBehaviorUser'] -eq '4,0') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'UAC: Automatically deny elevation requests on Standard accounts' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'UAC: Automatically deny elevation requests on Standard accounts' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]($($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ValidateAdminCodeSignatures'] -eq '4,1') ? $True : $False) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'UAC: Only elevate executables that are signed and validated' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'UAC: Only elevate executables that are signed and validated' Category = $CatName Method = 'Security Group Policy' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-UserAccountControl' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-DeviceGuard { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:DeviceGuardJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'DeviceGuard' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-DeviceGuard' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-WindowsFirewall { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:WindowsFirewallJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'WindowsFirewall' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Verify the 3 built-in Firewall rules (for all 3 profiles) for Multicast DNS (mDNS) UDP-in are disabled $IndividualItemResult = [System.Boolean]( (Get-NetFirewallRule | Where-Object -FilterScript { ($_.RuleGroup -eq '@%SystemRoot%\system32\firewallapi.dll,-37302') -and ($_.Direction -eq 'inbound') }).Enabled -inotcontains 'True' ) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'mDNS UDP-In Firewall Rules are disabled' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'mDNS UDP-In Firewall Rules are disabled' Category = $CatName Method = 'Cmdlet' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-WindowsFirewall' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-OptionalWindowsFeatures { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:OptionalWindowsFeaturesJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'OptionalWindowsFeatures' # Windows PowerShell handling Windows optional features verifications [System.String[]]$Results = @() $Results = powershell.exe { [System.Boolean]$PowerShell1 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2).State -eq 'Disabled' [System.Boolean]$PowerShell2 = (Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root).State -eq 'Disabled' [System.String]$WorkFoldersClient = (Get-WindowsOptionalFeature -Online -FeatureName WorkFolders-Client).state [System.String]$InternetPrintingClient = (Get-WindowsOptionalFeature -Online -FeatureName Printing-Foundation-Features).state [System.String]$WindowsMediaPlayer = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*Media.WindowsMediaPlayer*' }).state [System.String]$MDAG = (Get-WindowsOptionalFeature -Online -FeatureName Windows-Defender-ApplicationGuard).state [System.String]$WindowsSandbox = (Get-WindowsOptionalFeature -Online -FeatureName Containers-DisposableClientVM).state [System.String]$HyperV = (Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).state [System.String]$WMIC = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*wmic*' }).state [System.String]$IEMode = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*Browser.InternetExplorer*' }).state [System.String]$LegacyNotepad = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*Microsoft.Windows.Notepad.System*' }).state [System.String]$LegacyWordPad = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*Microsoft.Windows.WordPad*' }).state [System.String]$PowerShellISE = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*Microsoft.Windows.PowerShell.ISE*' }).state [System.String]$StepsRecorder = (Get-WindowsCapability -Online | Where-Object -FilterScript { $_.Name -like '*App.StepsRecorder*' }).state # returning the output of the script block as an array Return $PowerShell1, $PowerShell2, $WorkFoldersClient, $InternetPrintingClient, $WindowsMediaPlayer, $MDAG, $WindowsSandbox, $HyperV, $WMIC, $IEMode, $LegacyNotepad, $LegacyWordPad, $PowerShellISE, $StepsRecorder } # Verify PowerShell v2 is disabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'PowerShell v2 is disabled' Compliant = ($Results[0] -and $Results[1]) ? $True : $False Value = ($Results[0] -and $Results[1]) ? $True : $False Name = 'PowerShell v2 is disabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify Work folders is disabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Work Folders client is disabled' Compliant = [System.Boolean]($Results[2] -eq 'Disabled') Value = [System.String]$Results[2] Name = 'Work Folders client is disabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify Internet Printing Client is disabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Internet Printing Client is disabled' Compliant = [System.Boolean]($Results[3] -eq 'Disabled') Value = [System.String]$Results[3] Name = 'Internet Printing Client is disabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify the old Windows Media Player is disabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Windows Media Player (legacy) is disabled' Compliant = [System.Boolean]($Results[4] -eq 'NotPresent') Value = [System.String]$Results[4] Name = 'Windows Media Player (legacy) is disabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify MDAG is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Microsoft Defender Application Guard is not present' Compliant = [System.Boolean]($Results[5] -eq 'Disabled' -or [System.String]::IsNullOrWhitespace($Results[5])) Value = [System.String]$Results[5] Name = 'Microsoft Defender Application Guard is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify Windows Sandbox is enabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Windows Sandbox is enabled' Compliant = [System.Boolean]($Results[6] -eq 'Enabled') Value = [System.String]$Results[6] Name = 'Windows Sandbox is enabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify Hyper-V is enabled $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Hyper-V is enabled' Compliant = [System.Boolean]($Results[7] -eq 'Enabled') Value = [System.String]$Results[7] Name = 'Hyper-V is enabled' Category = $CatName Method = 'Optional Windows Features' }) # Verify WMIC is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'WMIC is not present' Compliant = [System.Boolean]($Results[8] -eq 'NotPresent') Value = [System.String]$Results[8] Name = 'WMIC is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify Internet Explorer mode functionality for Edge is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Internet Explorer mode functionality for Edge is not present' Compliant = [System.Boolean]($Results[9] -eq 'NotPresent') Value = [System.String]$Results[9] Name = 'Internet Explorer mode functionality for Edge is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify Legacy Notepad is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Legacy Notepad is not present' Compliant = [System.Boolean]($Results[10] -eq 'NotPresent') Value = [System.String]$Results[10] Name = 'Legacy Notepad is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify Legacy WordPad is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'WordPad is not present' Compliant = [System.Boolean]($Results[11] -eq 'NotPresent' -or [System.String]::IsNullOrWhitespace($Results[11])) Value = [System.String]$Results[11] Name = 'WordPad is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify PowerShell ISE is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'PowerShell ISE is not present' Compliant = [System.Boolean]($Results[12] -eq 'NotPresent') Value = [System.String]$Results[12] Name = 'PowerShell ISE is not present' Category = $CatName Method = 'Optional Windows Features' }) # Verify Steps Recorder is not present $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Steps Recorder is not present' Compliant = [System.Boolean]($Results[13] -eq 'NotPresent') Value = [System.String]$Results[13] Name = 'Steps Recorder is not present' Category = $CatName Method = 'Optional Windows Features' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-OptionalWindowsFeatures' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-WindowsNetworking { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:WindowsNetworkingJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'WindowsNetworking' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Check network location of all connections to see if they are public $Condition = Get-NetConnectionProfile | ForEach-Object -Process { $_.NetworkCategory -eq 'public' } [System.Boolean]$IndividualItemResult = -NOT ($Condition -contains $false) ? $True : $false # Verify a Security setting using Cmdlet $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Network Location of all connections set to Public' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Network Location of all connections set to Public' Category = $CatName Method = 'Cmdlet' }) # Verify a Security setting using registry try { $IndividualItemResult = [System.Boolean]((Get-ItemPropertyValue -Path 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -Name 'EnableLMHOSTS' -ErrorAction SilentlyContinue) -eq '0') } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Disable LMHOSTS lookup protocol on all network adapters' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Disable LMHOSTS lookup protocol on all network adapters' Category = $CatName Method = 'Registry Key' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine'] -eq '7,') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Network access: Remotely accessible registry paths' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Network access: Remotely accessible registry paths' Category = $CatName Method = 'Security Group Policy' }) # Verify a Security Group Policy setting $IndividualItemResult = [System.Boolean]$($SyncHash['SecurityPoliciesIni'].'Registry Values'['MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine'] -eq '7,') ? $True : $False $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Network access: Remotely accessible registry paths and subpaths' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Network access: Remotely accessible registry paths and subpaths' Category = $CatName Method = 'Security Group Policy' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-WindowsNetworking' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-MiscellaneousConfigurations { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:MiscellaneousConfigurationsJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'MiscellaneousConfigurations' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Verify an Audit policy is enabled - only supports systems with English-US language if ((Get-Culture).Name -eq 'en-US') { $IndividualItemResult = [System.Boolean](((auditpol /get /subcategory:"Other Logon/Logoff Events" /r | ConvertFrom-Csv).'Inclusion Setting' -eq 'Success and Failure') ? $True : $False) $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Audit policy for Other Logon/Logoff Events' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Audit policy for Other Logon/Logoff Events' Category = $CatName Method = 'Cmdlet' }) } else { $SyncHash['TotalNumberOfTrueCompliantValues']-- } # Checking if all user accounts are part of the Hyper-V security Group # Get all the enabled user account SIDs [System.Security.Principal.SecurityIdentifier[]]$EnabledUsers = (Get-LocalUser | Where-Object -FilterScript { $_.Enabled -eq 'True' }).SID # Get the members of the Hyper-V Administrators security group using their SID [System.Security.Principal.SecurityIdentifier[]]$GroupMembers = (Get-LocalGroupMember -SID 'S-1-5-32-578').SID # Make sure the arrays are not empty if (($null -ne $EnabledUsers) -and ($null -ne $GroupMembers)) { # only outputs data if there is a difference, so when it returns $false it means both arrays are equal $IndividualItemResult = [System.Boolean](-NOT (Compare-Object -ReferenceObject $EnabledUsers -DifferenceObject $GroupMembers) ) } else { # if either of the arrays are null or empty then return false [System.Boolean]$IndividualItemResult = $false } # Saving the results of the Hyper-V administrators members group to the array as an object $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'All users are part of the Hyper-V Administrators group' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'All users are part of the Hyper-V Administrators group' Category = $CatName Method = 'Cmdlet' }) # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Registry Keys'))) { $NestedObjectArray.Add($Result) } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-MiscellaneousConfigurations' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-WindowsUpdateConfigurations { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:WindowsUpdateConfigurationsJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'WindowsUpdateConfigurations' # Process items in Registry resources.csv file with "Group Policy" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Group Policy'))) { $NestedObjectArray.Add($Result) } # Verify a Security setting using registry try { $IndividualItemResult = [System.Boolean]((Get-ItemPropertyValue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -Name 'RestartNotificationsAllowed2' -ErrorAction SilentlyContinue) -eq '1') } catch { # -ErrorAction SilentlyContinue wouldn't suppress the error if the path exists but property doesn't, so using try-catch } $NestedObjectArray.Add([HardeningModule.IndividualResult]@{ FriendlyName = 'Enable restart notification for Windows update' Compliant = $IndividualItemResult Value = $IndividualItemResult Name = 'Enable restart notification for Windows update' Category = $CatName Method = 'Registry Key' }) # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-WindowsUpdateConfigurations' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-EdgeBrowserConfigurations { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:EdgeBrowserConfigurationsJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'EdgeBrowserConfigurations' # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Registry Keys'))) { $NestedObjectArray.Add($Result) } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-EdgeBrowserConfigurations' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } Function Invoke-NonAdminCommands { Param ($SyncHash, $FinalMegaObject) [System.Management.Automation.Job2]$script:NonAdminCommandsJob = Start-ThreadJob -ScriptBlock { Param ($SyncHash, $FinalMegaObject) $ErrorActionPreference = 'Stop' $NestedObjectArray = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] [System.String]$CatName = 'NonAdminCommands' # Process items in Registry resources.csv file with "Registry Keys" origin and add them to the $NestedObjectArray array as custom objects foreach ($Result in ([HardeningModule.CategoryProcessing]::ProcessCategory($CatName, 'Registry Keys'))) { $NestedObjectArray.Add($Result) } # Add the array of the custom objects to the main output HashTable [System.Void]$FinalMegaObject.TryAdd($CatName, $NestedObjectArray) } -Name 'Invoke-NonAdminCommands' -StreamingHost $Host -ArgumentList ($SyncHash, $FinalMegaObject) } #Endregion Main-Functions Switch ($Categories) { 'MicrosoftDefender' { Invoke-MicrosoftDefender -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'AttackSurfaceReductionRules' { Invoke-AttackSurfaceReductionRules -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'BitLockerSettings' { Invoke-BitLockerSettings -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'TLSSecurity' { Invoke-TLSSecurity -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'LockScreen' { Invoke-LockScreen -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'UserAccountControl' { Invoke-UserAccountControl -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'DeviceGuard' { Invoke-DeviceGuard -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'WindowsFirewall' { Invoke-WindowsFirewall -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'OptionalWindowsFeatures' { Invoke-OptionalWindowsFeatures -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'WindowsNetworking' { Invoke-WindowsNetworking -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'MiscellaneousConfigurations' { Invoke-MiscellaneousConfigurations -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'WindowsUpdateConfigurations' { Invoke-WindowsUpdateConfigurations -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'EdgeBrowserConfigurations' { Invoke-EdgeBrowserConfigurations -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } 'NonAdminCommands' { Invoke-NonAdminCommands -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } Default { # Get the values of the ValidateSet attribute of the Categories parameter of the main function foreach ($Item in [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { # Run all of the categories' functions if the user didn't specify any . "Invoke-$Item" -SyncHash $SyncHash -FinalMegaObject $FinalMegaObject } } } #Region Threading management $JobsToWaitFor = New-Object -TypeName System.Collections.Generic.List[System.Management.Automation.Job2] # If user didn't specify any categories, add all of them to the list of jobs to wait for if ($null -eq $Categories) { $JobsToWaitFor = foreach ($Cat in [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { [System.String]$VariableName = $Cat + 'Job' (Get-Item -Path "variable:$VariableName").Value } } # If user specified categories, add only the specified ones to the list of the jobs to wait for else { $JobsToWaitFor = foreach ($Cat in $Categories) { [System.String]$VariableName = $Cat + 'Job' (Get-Item -Path "variable:$VariableName").Value } } $null = Wait-Job -Job $JobsToWaitFor Receive-Job -Job $JobsToWaitFor Remove-Job -Job $JobsToWaitFor -Force #Endregion Threading management if ($ExportToCSV) { # Create an empty list to store the results based on the category order by sorting the concurrent hashtable $AllOrderedResults = New-Object -TypeName System.Collections.Generic.List[HardeningModule.IndividualResult] $AllOrderedResults = foreach ($Key in [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { if ($FinalMegaObject.ContainsKey($Key)) { foreach ($Item in $FinalMegaObject[$Key].GetEnumerator()) { $Item } } } # Store the results in the current working directory in a CSV files $AllOrderedResults | ConvertTo-Csv | Out-File -FilePath ".\Compliance Check Output $(Get-Date -Format "MM-dd-yyyy 'at' HH-mm-ss").CSV" -Force } function Set-CategoryFormat { [CmdletBinding()] param ( [ValidateSet([Colorsx])] [Parameter(Mandatory)][System.String]$ColorInput, [Parameter(Mandatory)][System.String]$CategoryName, [Parameter(Mandatory)][System.String]$DisplayName, [Parameter(Mandatory)][System.Collections.Hashtable]$ColorMap, [Parameter(Mandatory)][PSCustomObject[]]$FinalMegaObject, [AllowNull()] [Parameter(Mandatory)][System.String[]]$Categories, [ValidateSet('List', 'Table')] [Parameter(Mandatory)][System.String]$Type ) # If user selected specific categories and the current function call's category name is not included in them, return from this function if (($null -ne $Categories) -and ($CategoryName -notin $Categories)) { Return } # Assign the array of color codes to a variable for easier/shorter assignments [System.Int32[]]$RGBs = $ColorMap[$ColorInput]['Code'] &$ColorMap[$ColorInput]['ScriptBlock'] "`n-------------$DisplayName Category-------------" Switch ($Type) { 'List' { # Setting the List Format Accent the same color as the category's title $PSStyle.Formatting.FormatAccent = $($PSStyle.Foreground.FromRGB($RGBs[0], $RGBs[1], $RGBs[2])) $FinalMegaObject.$CategoryName | Format-List -Property FriendlyName, @{ Label = 'Compliant' Expression = { switch ($_.Compliant) { { $_ -eq $true } { $SwitchColor = $($PSStyle.Foreground.FromRGB($RGBs[0], $RGBs[1], $RGBs[2])); break } { $_ -eq $false } { $SwitchColor = "$($PSStyle.Foreground.FromRGB(229,43,80))$($PSStyle.Blink)"; break } { $_ -eq 'N/A' } { $SwitchColor = "$($PSStyle.Foreground.FromRGB(238,255,204))"; break } } "$SwitchColor$($_.Compliant)$($PSStyle.Reset)" } }, Value, Name, Category, Method } 'Table' { # Setting the Table header the same color as the category's title $PSStyle.Formatting.TableHeader = $($PSStyle.Foreground.FromRGB($RGBs[0], $RGBs[1], $RGBs[2])) $FinalMegaObject.$CategoryName | Format-Table -Property FriendlyName, @{ Label = 'Compliant' Expression = { switch ($_.Compliant) { { $_ -eq $true } { $SwitchColor = $($PSStyle.Foreground.FromRGB($RGBs[0], $RGBs[1], $RGBs[2])); break } { $_ -eq $false } { $SwitchColor = "$($PSStyle.Foreground.FromRGB(229,43,80))$($PSStyle.Blink)"; break } { $_ -eq 'N/A' } { $SwitchColor = "$($PSStyle.Foreground.FromRGB(238,255,204))"; break } } "$SwitchColor$($_.Compliant)$($PSStyle.Reset)" } } , Value -AutoSize } } } if ($ShowAsObjectsOnly) { # return the main object that contains multiple nested objects return $FinalMegaObject } else { Set-CategoryFormat -ColorInput Plum -CategoryName 'MicrosoftDefender' -DisplayName 'Microsoft Defender' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput Orchid -CategoryName 'AttackSurfaceReductionRules' -DisplayName 'Attack Surface Reduction Rules' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput Fuchsia -CategoryName 'BitLockerSettings' -DisplayName 'Bitlocker Category' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput MediumOrchid -CategoryName 'TLSSecurity' -DisplayName 'TLS' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput MediumPurple -CategoryName 'LockScreen' -DisplayName 'Lock Screen' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput BlueViolet -CategoryName 'UserAccountControl' -DisplayName 'User Account Control' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput AndroidGreen -CategoryName 'DeviceGuard' -DisplayName 'Device Guard' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput Pink -CategoryName 'WindowsFirewall' -DisplayName 'Windows Firewall' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput SkyBlue -CategoryName 'OptionalWindowsFeatures' -DisplayName 'Optional Windows Features' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput HotPink -CategoryName 'WindowsNetworking' -DisplayName 'Windows Networking' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput DeepPink -CategoryName 'MiscellaneousConfigurations' -DisplayName 'Miscellaneous' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput MintGreen -CategoryName 'WindowsUpdateConfigurations' -DisplayName 'Windows Update' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput Orange -CategoryName 'EdgeBrowserConfigurations' -DisplayName 'Microsoft Edge' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') Set-CategoryFormat -ColorInput Daffodil -CategoryName 'NonAdminCommands' -DisplayName 'Non-Admin' -ColorMap $global:ColorsMap -FinalMegaObject $FinalMegaObject -Categories:$Categories -Type ($DetailedDisplay ? 'List' : 'Table') # Counting the number of $True Compliant values in the Final Output Object [System.UInt32]$TotalTrueCompliantValuesInOutPut = 0 foreach ($Category in [HardeningModule.ComplianceCategoriex]::new().GetValidValues()) { $TotalTrueCompliantValuesInOutPut += ($FinalMegaObject.$Category).Where({ $_.Compliant -eq $True }).Count } # Only display the overall score if the user has not specified any categories if (!$Categories) { switch ($True) { ($TotalTrueCompliantValuesInOutPut -in 1..40) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\1To40.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } ($TotalTrueCompliantValuesInOutPut -in 41..80) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\41To80.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } ($TotalTrueCompliantValuesInOutPut -in 81..120) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\81To120.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } ($TotalTrueCompliantValuesInOutPut -in 121..160) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\121To160.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } ($TotalTrueCompliantValuesInOutPut -in 161..200) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\161To200.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } ($TotalTrueCompliantValuesInOutPut -gt 200) { & $WriteRainbow "$(Get-Content -Raw -Path "$([HardeningModule.GlobalVars]::Path)\Resources\Media\Text Arts\Above200.txt")`nYour compliance score is $TotalTrueCompliantValuesInOutPut out of $($SyncHash['TotalNumberOfTrueCompliantValues'])!" } } } } } Catch { # Throw any unhandled errors in a terminating fashion Throw $_ } finally { # End the progress bar and mark it as completed Write-Progress -Id 0 -Activity 'Completed' -Completed #Region stopping rainbow progress bar # Stop the timer $RainbowTimer.Stop() # Unregister the event handler Unregister-Event -SourceIdentifier $EventHandler.Name -Force # Remove the event handler's job Remove-Job -Job $EventHandler -Force #Endregion stopping rainbow progress bar # Clean up Remove-Item -Path '.\security_policy.inf' -Force } } <# .SYNOPSIS Checks the compliance of a system with the Harden Windows Security script guidelines .LINK https://github.com/HotCakeX/Harden-Windows-Security/wiki/Harden%E2%80%90Windows%E2%80%90Security%E2%80%90Module .DESCRIPTION Checks the compliance of a system with the Harden Windows Security script. Checks the applied Group policies, registry keys and PowerShell cmdlets used by the hardening script. .COMPONENT Gpresult, Secedit, PowerShell, Registry .FUNCTIONALITY Uses Gpresult and Secedit to first export the effective Group policies and Security policies, then goes through them and checks them against the Harden Windows Security's guidelines. .EXAMPLE $Result = Confirm-SystemCompliance -ShowAsObjectsOnly ($Result['MicrosoftDefender'] | Where-Object -FilterScript { $_.Name -eq 'Controlled Folder Access Exclusions'}).Value Do this to get the Controlled Folder Access Programs list when using ShowAsObjectsOnly optional parameter to output an object .EXAMPLE $Result = Confirm-SystemCompliance -ShowAsObjectsOnly $Result['MicrosoftDefender'] Do this to only see the result for the Microsoft Defender category when using ShowAsObjectsOnly optional parameter to output an object .EXAMPLE Confirm-SystemCompliance -Categories MicrosoftDefender, MiscellaneousConfigurations Do this to only check the compliance for the Microsoft Defender and Miscellaneous Configurations categories .PARAMETER ExportToCSV Export the output to a CSV file in the current working directory .PARAMETER ShowAsObjectsOnly Returns a nested object instead of writing strings on the PowerShell console, it can be assigned to a variable .PARAMETER DetailedDisplay Shows the output on the PowerShell console with more details and in the list format instead of table format .PARAMETER Categories Specify the categories to check compliance for. If not specified, all categories will be checked .PARAMETER Offline Skips the online update check .INPUTS System.Management.Automation.SwitchParameter System.String[] .OUTPUTS System.String System.Collections.Concurrent.ConcurrentDictionary[System.String, HardeningModule.IndividualResult[]] #> } |