hardening code

#
# Start Config/Audit mode
# The processing is done per category of the finding list.
# The finding list defines which module is used and the arguments and recommended values for the test.
#
If ($Mode -eq “Audit” -or $Mode -eq “Config”) {

# A CSV finding list is imported. HardeningKitty has one machine and one user list.
If ($FileFindingList.Length -eq 0) {

$CurrentLocation = $PSScriptRoot
$DefaultList = “$CurrentLocation\lists\finding_list_0x6d69636b_machine.csv”

If (Test-Path -Path $DefaultList) {
$FileFindingList = $DefaultList
} Else {
$Message = “The finding list $DefaultList was not found.”
Write-ProtocolEntry -Text $Message -LogLevel “Error”
Continue
}
}

$FindingList = Import-Csv -Path $FileFindingList -Delimiter “,”
If ($Filter) {
$FindingList = $FindingList | Where-Object -FilterScript $Filter
If ($FindingList.Length -eq 0) {
$Message = “Your filter did not return any results, please adjust the filter so that HardeningKitty has something to work with.”
Write-ProtocolEntry -Text $Message -LogLevel “Error”
Break
}
}
$LastCategory = “”

ForEach ($Finding in $FindingList) {

#
# Reset
#
$Result = “”

#
# Category
#
If ($LastCategory -ne $Finding.Category) {

$Message = “Starting Category ” + $Finding.Category
Write-Output “`n”
Write-ProtocolEntry -Text $Message -LogLevel “Info”
$LastCategory = $Finding.Category
}

#
# Get Registry Item
# Registry entries can be read with a native PowerShell function. The retrieved value is evaluated later.
# If the registry entry is not available, a default value is used. This must be specified in the finding list.
#
If ($Finding.Method -eq ‘Registry’) {

If (Test-Path -Path $Finding.RegistryPath) {

try {
$Result = Get-ItemPropertyValue -Path $Finding.RegistryPath -Name $Finding.RegistryItem
} catch {
$Result = $Finding.DefaultValue
}
} Else {
$Result = $Finding.DefaultValue
}
}

#
# Get secedit policy
# Secedit configures and analyzes system security, results are written
# to a file, which means HardeningKitty must create a temporary file
# and afterwards delete it. HardeningKitty is very orderly.
#
ElseIf ($Finding.Method -eq ‘secedit’) {

# Check if Secedit binary is available, skip test if not
If (-Not (Test-Path $BinarySecedit)) {
Write-BinaryError -Binary $BinarySecedit -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

$TempFileName = [System.IO.Path]::GetTempFileName()

$Area = “”;

Switch ($Finding.Category) {
“Account Policies” { $Area = “SECURITYPOLICY”; Break }
“Security Options” { $Area = “SECURITYPOLICY”; Break }
}

&$BinarySecedit /export /cfg $TempFileName /areas $Area | Out-Null

$Data = Get-IniContent $TempFileName

$Value = Get-HashtableValueDeep $Data $Finding.MethodArgument

if ($null -eq $Value) {
$Result = $null
} else {
$Result = $Value -as [int]
}

Remove-Item $TempFileName
}

#
# Get Registry List and search for item
# Depending on the registry structure, the value cannot be accessed directly, but must be found within a data structure
# If the registry entry is not available, a default value is used. This must be specified in the finding list.
#
ElseIf ($Finding.Method -eq ‘RegistryList’) {

If (Test-Path -Path $Finding.RegistryPath) {

try {
$ResultList = Get-ItemProperty -Path $Finding.RegistryPath

If ($ResultList | Where-Object { $_ -like “*” + $Finding.RegistryItem + “*” }) {
$Result = $Finding.RegistryItem
} Else {
$Result = “Not found”
}

} catch {
$Result = $Finding.DefaultValue
}
} Else {
$Result = $Finding.DefaultValue
}
}

#
# Get Audit Policy
# The output of auditpol.exe is parsed and will be evaluated later.
# The desired value is not output directly, some output lines can be ignored
# and are therefore skipped. If the output changes, the parsing must be adjusted 🙁
#
ElseIf ($Finding.Method -eq ‘auditpol’) {

# Check if Auditpol binary is available, skip test if not
If (-Not (Test-Path $BinaryAuditpol)) {
Write-BinaryError -Binary $BinaryAuditpol -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$SubCategory = $Finding.MethodArgument

# auditpol.exe does not write a backup in an existing file, so we have to build a name instead of create one
$TempFileName = [System.IO.Path]::GetTempPath() + “HardeningKitty_auditpol-” + $(Get-Date -Format yyyyMMdd-HHmmss) + “.csv”
&$BinaryAuditpol /backup /file:$TempFileName > $null

$ResultOutputLoad = Get-Content $TempFileName
foreach ($line in $ResultOutputLoad) {
$table = $line.Split(“,”)
if ($table[3] -eq $SubCategory) {

# Translate setting value (works only for English list, so this is workaround)
Switch ($table[6]) {
“0” { $Result = “No Auditing”; Break }
“1” { $Result = “Success”; Break }
“2” { $Result = “Failure”; Break }
“3” { $Result = “Success and Failure”; Break }
}
}
}

# House cleaning
Remove-Item $TempFileName
Clear-Variable -Name (“ResultOutputLoad”, “table”)

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Get Account Policy
# The output of net.exe is parsed and will be evaluated later.
# It may be necessary to use the /domain parameter when calling net.exe.
# The values of the user executing the script are read out. These may not match the password policy.
#
ElseIf ($Finding.Method -eq ‘accountpolicy’) {

# Check if net binary is available, skip test if not
If (-Not (Test-Path $BinaryNet)) {
Write-BinaryError -Binary $BinaryNet -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$ResultOutput = &$BinaryNet accounts

# “Parse” account policy
Switch ($Finding.Name) {
“Force user logoff how long after time expires” { $ResultOutput[0] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Network security: Force logoff when logon hours expires” { $ResultOutput[0] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Minimum password age” { $ResultOutput[1] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Maximum password age” { $ResultOutput[2] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Minimum password length” { $ResultOutput[3] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Length of password history maintained” { $ResultOutput[4] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Account lockout threshold” { $ResultOutput[5] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Account lockout duration” { $ResultOutput[6] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
“Reset account lockout counter” { $ResultOutput[7] -match ‘([a-zA-Z:, /-]+) ([a-z0-9, ]+)’ | Out-Null; $Result = $Matches[2]; Break }
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Get Local Account Information
# The PowerShell function Get-LocalUser is used for this.
# In order to get the correct user, the query is made via the SID,
# the base value of the computer must first be retrieved.
#
ElseIf ($Finding.Method -eq ‘localaccount’) {

try {

# Get Computer SID
$ComputerSid = ((Get-LocalUser | Select-Object -First 1).SID).AccountDomainSID.ToString()

# Get User Status
$Sid = $ComputerSid + “-” + $Finding.MethodArgument
$ResultOutput = Get-LocalUser -SID $Sid

If ($Finding.Name.Contains(“account status”)) {
$Result = $ResultOutput.Enabled
} ElseIf ($Finding.Name.Contains(“Rename”)) {
$Result = $ResultOutput.Name
} Else {
$Result = $Finding.DefaultValue
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# User Rights Assignment
# This method was first developed with the tool accessck.exe, hence the name.
# Due to compatibility problems in languages other than English, secedit.exe is
# now used to read the User Rights Assignments.
#
# Secedit configures and analyzes system security, results are written
# to a file, which means HardeningKitty must create a temporary file
# and afterwards delete it. HardeningKitty is very orderly.
#
ElseIf ($Finding.Method -eq ‘accesschk’) {

# Check if Secedit binary is available, skip test if not
If (-Not (Test-Path $BinarySecedit)) {
Write-BinaryError -Binary $BinarySecedit -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

$TempFileName = [System.IO.Path]::GetTempFileName()

try {

&$BinarySecedit /export /cfg $TempFileName /areas USER_RIGHTS | Out-Null
$ResultOutputRaw = Get-Content -Encoding unicode $TempFileName | Select-String $Finding.MethodArgument

If ($null -eq $ResultOutputRaw) {
$Result = “”
} Else {
$ResultOutputList = $ResultOutputRaw.ToString().split(“=”).Trim()
$Result = $ResultOutputList[1] -Replace “\*”, “”
$Result = $Result -Replace “,”, “;”
}

} catch {
# If secedit did not work, throw an error instead of using the DefaultValue
$Script:StatsError++
$Message = “ID ” + $Finding.ID + “, ” + $Finding.Name + “, secedit.exe could not read the configuration. Test skipped.”
Write-ProtocolEntry -Text $Message -LogLevel “Error”
Continue
}

Remove-Item $TempFileName
}

#
# Windows Optional Feature
# Yay, a native PowerShell function! The status of the feature can easily be read out directly.
#
ElseIf ($Finding.Method -eq ‘WindowsOptionalFeature’) {

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$ResultOutput = Get-WindowsOptionalFeature -Online -FeatureName $Finding.MethodArgument
$Result = $ResultOutput.State

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Get CimInstance and search for item
# Via a CIM instance classes can be read from the CIM server.
# Afterwards, you have to search for the correct property within the class.
#
ElseIf ($Finding.Method -eq ‘CimInstance’) {

try {

$ResultList = Get-CimInstance -ClassName $Finding.ClassName -Namespace $Finding.Namespace
$Property = $Finding.Property

If ($ResultList.$Property | Where-Object { $_ -like “*” + $Finding.RecommendedValue + “*” }) {
$Result = $Finding.RecommendedValue
} Else {
$Result = “Not available”
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# BitLocker Drive Encryption
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
#
ElseIf ($Finding.Method -eq ‘BitLockerVolume’) {

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$ResultOutput = Get-BitLockerVolume -MountPoint $Env:SystemDrive
If ($ResultOutput.VolumeType -eq ‘OperatingSystem’) {
$ResultArgument = $Finding.MethodArgument
$Result = $ResultOutput.$ResultArgument
} Else {
$Result = “Manual check required”
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# PowerShell Language Mode
# This is a single purpose function, the desired configuration is output directly.
#
ElseIf ($Finding.Method -eq ‘LanguageMode’) {

try {

$ResultOutput = $ExecutionContext.SessionState.LanguageMode
$Result = $ResultOutput

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Microsoft Defender Preferences
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
#
ElseIf ($Finding.Method -eq ‘MpPreference’) {

try {

$ResultOutput = Get-MpPreference
$ResultArgument = $Finding.MethodArgument
$Result = $ResultOutput.$ResultArgument

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Microsoft Defender Preferences – Attack surface reduction rules (ASR rules)
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
#
ElseIf ($Finding.Method -eq ‘MpPreferenceAsr’) {

try {

$ResultOutput = Get-MpPreference
$ResultAsrIds = $ResultOutput.AttackSurfaceReductionRules_Ids
$ResultAsrActions = $ResultOutput.AttackSurfaceReductionRules_Actions
$Result = $Finding.DefaultValue
$Counter = 0

ForEach ($AsrRule in $ResultAsrIds) {

If ($AsrRule -eq $Finding.MethodArgument) {
$Result = $ResultAsrActions[$Counter]
Continue
}
$Counter++
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Microsoft Defender Preferences – Exclusion lists
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
#
ElseIf ($Finding.Method -eq ‘MpPreferenceExclusion’) {

# Check if the user has admin rights, skip test if not
# Normal users are not allowed to get exclusions
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$ResultOutput = Get-MpPreference
$ExclusionType = $Finding.MethodArgument
$ResultExclusions = $ResultOutput.$ExclusionType

ForEach ($Exclusion in $ResultExclusions) {
$Result += $Exclusion + “;”
}
# Remove last character
$Result = $Result -replace “.$”

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Exploit protection (System)
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
# Since the object has several dimensions and there is only one dimension
# in the finding list (lazy) a workaround with split must be done…
#
ElseIf ($Finding.Method -eq ‘Processmitigation’) {

try {

$ResultOutput = Get-ProcessMitigation -System
$ResultArgumentArray = $Finding.MethodArgument.Split(“.”)
$ResultArgument0 = $ResultArgumentArray[0]
$ResultArgument1 = $ResultArgumentArray[1]
$Result = $ResultOutput.$ResultArgument0.$ResultArgument1

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Exploit protection (Application)
# The values are saved from a PowerShell function into an object.
# The desired arguments can be accessed directly.
# Since the object has several dimensions and there is only one dimension
# in the finding list (lazy) a workaround with split must be done…
#
ElseIf ($Finding.Method -eq ‘ProcessmitigationApplication’) {

try {

$ResultArgumentArray = $Finding.MethodArgument.Split(“/”)
$ResultOutput = Get-ProcessMitigation -Name $ResultArgumentArray[0]
$ResultArgument0 = $ResultArgumentArray[1]
$ResultArgument1 = $ResultArgumentArray[2]
$Result = $ResultOutput.$ResultArgument0.$ResultArgument1

} catch {
$Result = $Finding.DefaultValue
}
}

#
# bcdedit
# Again, the output of a tool must be searched and parsed. Ugly…
#
ElseIf ($Finding.Method -eq ‘bcdedit’) {

# Check if the user has admin rights, skip test if not
If (-not($IsAdmin)) {
Write-NotAdminError -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

# Check if Bcdedit binary is available, skip test if not
If (-Not (Test-Path $BinaryBcdedit)) {
Write-BinaryError -Binary $BinaryBcdedit -FindingID $Finding.ID -FindingName $Finding.Name -FindingMethod $Finding.Method
Continue
}

try {

$ResultOutput = &$BinaryBcdedit
$ResultOutput = $ResultOutput | Where-Object { $_ -like “*” + $Finding.RecommendedValue + “*” }

If ($ResultOutput -match ‘ ([a-z,A-Z]+)’) {
$Result = $Matches[1]
} Else {
$Result = $Finding.DefaultValue
}

} catch {
$Result = $Finding.DefaultValue
}
}

#
# FirewallRule
# Search for a specific firewall rule with a given name
#
ElseIf ($Finding.Method -eq ‘FirewallRule’) {

try {

$ResultOutput = Get-NetFirewallRule -PolicyStore ActiveStore -DisplayName $Finding.Name 2> $null
$Result = $ResultOutput.Enabled

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Service
# Check the status of a service
#
ElseIf ($Finding.Method -eq ‘service’) {

try {

$ResultOutput = Get-Service -Name $Finding.MethodArgument 2> $null
$Result = $ResultOutput.StartType

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Scheduled Task
# Check the status of a scheduled task
#
ElseIf ($Finding.Method -eq ‘ScheduledTask’) {

try {

$ResultOutput = Get-ScheduledTask -TaskName $Finding.MethodArgument 2> $null
$Result = $ResultOutput.State

} catch {
$Result = $Finding.DefaultValue
}
}

#
# Compare result value and recommendation
# The finding list specifies the test, as well as the recommended values.
# There are two output formats, one for command line output and one for the CSV file.
#
If ($Mode -eq “Audit”) {

#
# User Right Assignment
# For multilingual support, a SID translation takes place and then the known SID values are compared with each other.
# The results are already available as SID (from secedit) and therefore the specifications are now also translated and still sorted.
#
If ($Finding.Method -eq ‘accesschk’) {

$SaveRecommendedValue = $Finding.RecommendedValue

If ($Result -ne ”) {

$ListRecommended = $Finding.RecommendedValue.Split(“;”)
$ListRecommendedSid = @()

# SID Translation
ForEach ($AccountName in $ListRecommended) {
$AccountSid = Translate-SidFromWellkownAccount -AccountName $AccountName
$ListRecommendedSid += $AccountSid
}
# Sort SID List
$ListRecommendedSid = $ListRecommendedSid | Sort-Object

# Build String
ForEach ($AccountName in $ListRecommendedSid) {
[String] $RecommendedValueSid += $AccountName + “;”
}

$RecommendedValueSid = $RecommendedValueSid -replace “.$”
$Finding.RecommendedValue = $RecommendedValueSid
Clear-Variable -Name (“RecommendedValueSid”)
}
}

#
# Exception handling for special registry keys
# Machine => Network access: Remotely accessible registry paths
# Hardened UNC Paths => Remove spaces in result and recommendation only if result is not null or empty
#
If ($Finding.Method -eq ‘Registry’ -and $Finding.RegistryItem -eq “Machine”) {
$Finding.RecommendedValue = $Finding.RecommendedValue.Replace(“;”, ” “)
} ElseIf ($Finding.Method -eq ‘Registry’ -and $Finding.RegistryPath -eq “HKLM:\Software\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths”) {
If (![string]::IsNullOrEmpty($Result)) {
$Result = $Result.Replace(” “, “”)
}
$Finding.RecommendedValue = $Finding.RecommendedValue.Replace(” “, “”)
}

#
# Handling for registry keys with an “advanced” format
#
If ($Finding.Method -eq ‘Registry’ -and $Finding.RegistryItem -eq “ASRRules”) {

$ResultAsr = $Result.Split(“|”)
ForEach ($AsrRow in $ResultAsr) {
$AsrRule = $AsrRow.Split(“=”)
If ($AsrRule[0] -eq $Finding.MethodArgument) {
$Result = $AsrRule[1]
Break
} Else {
$Result = $Finding.DefaultValue
}
}
}

$ResultPassed = $false
Switch ($Finding.Operator) {

“=” { If ([string] $Result -eq $Finding.RecommendedValue) { $ResultPassed = $true }; Break }
“<=" { try { If ([int]$Result -le [int]$Finding.RecommendedValue) { $ResultPassed = $true } } catch { $ResultPassed = $false }; Break } "<=!0" { try { If ([int]$Result -le [int]$Finding.RecommendedValue -and [int]$Result -ne 0) { $ResultPassed = $true } } catch { $ResultPassed = $false }; Break } ">=” { try { If ([int]$Result -ge [int]$Finding.RecommendedValue) { $ResultPassed = $true } } catch { $ResultPassed = $false }; Break }
“contains” { If ($Result.Contains($Finding.RecommendedValue)) { $ResultPassed = $true }; Break }
“!=” { If ([string] $Result -ne $Finding.RecommendedValue) { $ResultPassed = $true }; Break }
“=|0” { try { If ([string]$Result -eq $Finding.RecommendedValue -or $Result.Length -eq 0) { $ResultPassed = $true } } catch { $ResultPassed = $false }; Break }
}

#
# Restore Result after SID translation
# The results are already available as SID, for better readability they are translated into their names
#
If ($Finding.Method -eq ‘accesschk’) {

If ($Result -ne “”) {

$ListResult = $Result.Split(“;”)
ForEach ($AccountSid in $ListResult) {
$AccountName = Get-AccountFromSid -AccountSid $AccountSid
[String] $ResultName += $AccountName.Trim() + “;”
}
$ResultName = $ResultName -replace “.$”
$Result = $ResultName
Clear-Variable -Name (“ResultName”)
}

$Finding.RecommendedValue = $SaveRecommendedValue
}

If ($ResultPassed) {

# Passed
$TestResult = “Passed”
$Message = “ID ” + $Finding.ID + “, ” + $Finding.Name + “, Result=$Result, Recommended=” + $Finding.RecommendedValue + “, Severity=Passed”
Write-ResultEntry -Text $Message -SeverityLevel “Passed”

If ($Log) {
Add-MessageToFile -Text $Message -File $LogFile
}

If ($Report) {
$ReportResult = [ordered] @{
ID = $Finding.ID
Category = $Finding.Category
Name = $Finding.Name
Severity = “Passed”
Result = $Result
Recommended = $Finding.RecommendedValue
TestResult = $TestResult
SeverityFinding = $Finding.Severity
}
$ReportAllResults += $ReportResult
}

# Increment Counter
$StatsPassed++

} Else {

# Failed
$TestResult = “Failed”
If ($Finding.Operator -eq “!=”) {
$Message = “ID ” + $Finding.ID + “, ” + $Finding.Name + “, Result=$Result, Recommended=Not ” + $Finding.RecommendedValue + “, Severity=” + $Finding.Severity
} Else {
$Message = “ID ” + $Finding.ID + “, ” + $Finding.Name + “, Result=$Result, Recommended=” + $Finding.RecommendedValue + “, Severity=” + $Finding.Severity
}

Write-ResultEntry -Text $Message -SeverityLevel $Finding.Severity

If ($Log) {
Add-MessageToFile -Text $Message -File $LogFile
}

If ($Report) {
$ReportResult = [ordered] @{
ID = $Finding.ID
Category = $Finding.Category
Name = $Finding.Name
Severity = $Finding.Severity
Result = $Result
Recommended = $Finding.RecommendedValue
TestResult = $TestResult
SeverityFinding = $Finding.Severity
}
$ReportAllResults += $ReportResult
}

# Increment Counter
Switch ($Finding.Severity) {

“Low” { $StatsLow++; Break }
“Medium” { $StatsMedium++; Break }
“High” { $StatsHigh++; Break }
}
}

#
# Only return received value
#
} Elseif ($Mode -eq “Config”) {

$Message = “ID ” + $Finding.ID + “; ” + $Finding.Name + “; Result=$Result”
Write-ResultEntry -Text $Message

If ($Log) {
Add-MessageToFile -Text $Message -File $LogFile
}
If ($Report) {
$ReportResult = [ordered] @{
ID = $Finding.ID
Category = $Finding.Category
Name = $Finding.Name
Severity = “”
Result = $Result
Recommended = $Finding.RecommendedValue
TestResult = “”
SeverityFinding = “”
}
$ReportAllResults += $ReportResult
}
If ($Backup) {
$BackupResult = [ordered] @{
ID = $Finding.ID
Category = $Finding.Category
Name = $Finding.Name
Method = $Finding.Method
MethodArgument = $Finding.MethodArgument
RegistryPath = $Finding.RegistryPath
RegistryItem = $Finding.RegistryItem
ClassName =$Finding.ClassName
Namespace = $Finding.Namespace
Property = $Finding.Property
DefaultValue = $Finding.DefaultValue
RecommendedValue = $Result
Operator = $Finding.Operator
Severity = $Finding.Severity
}
$BackupAllResults += $BackupResult
}
}
}
}