ATAPAuditor.psm1
using namespace Microsoft.PowerShell.Commands #region Initialization $RootPath = Split-Path $MyInvocation.MyCommand.Path -Parent $script:atapReportsPath = $env:ATAPReportPath if (-not $script:atapReportsPath) { $script:atapReportsPath = [Environment]::GetFolderPath('MyDocuments') | Join-Path -ChildPath 'ATAPReports' } #endregion #region Classes class AuditTest { [string] $Id [string] $Task [hashtable[]] $Constraints [scriptblock] $Test } enum AuditInfoStatus { True False Warning None Error } class AuditInfo { [string] $Id [string] $Task [AuditInfoStatus] $Status [string] $Message } class ReportSection { [string] $Title [string] $Description [AuditInfo[]] $AuditInfos [ReportSection[]] $SubSections } class Report { [string] $Title [string] $ModuleName [string] $AuditorVersion [hashtable] $HostInformation [string[]] $BasedOn [ReportSection[]] $Sections [RSFullReport] $RSReport } # RiskScore Classes enum RSEndResult { Critical High Medium Low Unknown } class RSFullReport { [RSSeverityReport] $RSSeverityReport [RSQuantityReport] $RSQuantityReport } class RSSeverityReport { [AuditInfo[]] $AuditInfos [ResultTable[]] $ResultTable [RSEndResult] $Endresult } class RSQuantityReport { } class ResultTable { [int] $Success [int] $Failed } #endregion #region helpers function Test-ArrayEqual { [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyCollection()] [array] $Array1, [Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyCollection()] [array] $Array2 ) if ($null -eq $Array1) { $Array1 = @() } if ($null -eq $Array2) { $Array2 = @() } if ($Array1.Count -ne $Array2.Count) { return $false } for ($i = 0; $i -lt $Array1.Count; $i++) { if ($Array1[$i] -ne $Array2[$i]) { return $false } } return $true } # Get domain role # 0 {"Standalone Workstation"} # 1 {"Member Workstation"} # 2 {"Standalone Server"} # 3 {"Member Server"} # 4 {"Backup Domain Controller"} # 5 {"Primary Domain Controller"} function Get-DomainRole { [DomainRole](Get-CimInstance -Class Win32_ComputerSystem).DomainRole } # region for RiskScore functions # function that calls all RiskScore-Subfunctions and generates the RSFullReport function Get-RSFullReport { [CmdletBinding()] [OutputType([RSFullReport])] $severity = Get-RSSeverityReport return ([RSFullReport]@{ RSSeverityReport = $severity }) } # function to generate RiskSeverityReport function Get-RSSeverityReport { [CmdletBinding()] [OutputType([RSSeverityReport])] # Initialization [AuditInfo[]]$tests = Test-AuditGroup "RSSeverityTests" # gather results of tests and save it in resultTable $resultTable = [ResultTable]::new() foreach ($test in $tests) { if ($test.AuditInfoStatus -EQ "True") { $resultTable.Success += 1 } if ($test.AuditInfostatus -ne "True") { $resultTable.Failed += 1 } } return ([RSSeverityReport]@{ AuditInfos = $tests ResultTable = $resultTable Endresult = Get-RSSeverityEndResult($resultTable) }) } # helper for EndResult of RiskScoreSeverity function Get-RSSeverityEndResult { [CmdletBinding()] [OutputType([RSEndResult])] param ( [Parameter(Mandatory = $true)] [ResultTable[]] $resultTable ) $result = "Unknown" $f = $resultTable.Failed if ($f -eq 0) { $result = "Low" } if ($f -ge 1) { $result = "Critical" } return $result } #endregion <# .SYNOPSIS Runs the tests of an AuditGroup. .DESCRIPTION Runs the tests of an AuditGroup file. .EXAMPLE PS C:\> Test-AuditGroup "Google Chrome-CIS-2.0.0#RegistrySettings" This runs tests defined in the AuditGroup file called 'Google Chrome-CIS-2.0.0#RegistrySettings'. .PARAMETER GroupName The name of the AuditGroup. #> function Test-AuditGroup { [CmdletBinding()] [OutputType([AuditInfo[]])] param( [Parameter(Mandatory = $true)] [string] $GroupName ) $tests = . "$RootPath\AuditGroups\$($GroupName).ps1" $i = 1 foreach ($test in $tests) { [int]$p = $i++ / $tests.Count * 100 Write-Progress -Activity "Testing Report for '$GroupName'" -Status "Progress:" -PercentComplete $p Write-Verbose "Testing $($test.Id)" $message = "Test not implemented yet." $status = [AuditInfoStatus]::None if ($test.Constraints) { $DomainRoleConstraint = $test.Constraints | Where-Object Property -EQ "DomainRole" $currentRole = Get-DomainRole $domainRoles = $DomainRoleConstraint.Values if ($currentRole -notin $domainRoles) { $roleValue = (Get-CimInstance -Class Win32_ComputerSystem).DomainRole if($roleValue -eq 4 -or $roleValue -eq 5){ $message = 'Not applicable. This audit only applies to Domain controllers.' $status = [AuditInfoStatus]::None } if($roleValue -ne 4 -or $roleValue -ne 5){ $message = 'Not applicable. This audit does not apply to Domain controllers.' $status = [AuditInfoStatus]::None } if($roleValue -eq 0 -or $roleValue -eq 2){ $message = 'Not applicable. This audit does not apply to Standalone systems.' $status = [AuditInfoStatus]::None } # Write-Output ([AuditInfo]@{ # Id = $test.Id # Task = $test.Task # Message = 'Not applicable. This audit applies only to {0}.' -f ($DomainRoleConstraint.Values -join ' and ') # Status = [AuditInfoStatus]::None # }) continue } } try { $innerResult = & $test.Test if ($null -ne $innerResult) { $message = $innerResult.Message $status = [AuditInfoStatus]$innerResult.Status } } catch { Write-Error $_ $message = "An error occured!" $status = [AuditInfoStatus]::Error } Write-Output ([AuditInfo]@{ Id = $test.Id Task = $test.Task Message = $message Status = $status }) } } <# .SYNOPSIS Get an audit resource. .DESCRIPTION A resource provides abstration over an existing system resource. It is used by AuditTests. .PARAMETER Name The name of the resource. .EXAMPLE PS C:\> Get-AuditResource -Name "WindowsSecurityPolicy" Gets the WindowsSecurityPolicy resource. #> function Get-AuditResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name ) if ($null -eq $script:loadedResources) { return & "$RootPath\Resources\$($Name).ps1" } if (-not $script:loadedResources.ContainsKey($Name)) { $script:loadedResources[$Name] = (& "$RootPath\Resources\$($Name).ps1") } return $script:loadedResources[$Name] } <# .SYNOPSIS Get all reports. .DESCRIPTION Find the reports installed on the system. .PARAMETER ReportName The name of the report. .EXAMPLE PS C:\> Get-ATAPReport Gets all reports. #> function Get-ATAPReport { [CmdletBinding()] param ( [Parameter()] [string] $ReportName = "*" ) return Get-ChildItem "$RootPath\Reports\$ReportName.ps1" | Select-Object -Property BaseName } <# .SYNOPSIS Invokes an ATAPReport .DESCRIPTION Long description .EXAMPLE PS C:\> ATAPReport -ReportName "Google Chrome" This runs the report and outputs the logical report data. .PARAMETER ReportName The name of the report. .OUTPUTS Logical report data. #> function Invoke-ATAPReport { [CmdletBinding()] param ( [Alias('RN')] [Parameter(Mandatory = $true)] [string] $ReportName ) $script:loadedResources = @{} # Load the module manifest $moduleInfo = Import-PowerShellDataFile -Path "$RootPath\ATAPAuditor.psd1" [Report]$report = (& "$RootPath\Reports\$ReportName.ps1") $report.RSReport = Get-RSFullReport $report.AuditorVersion = $moduleInfo.ModuleVersion return $report } <# .SYNOPSIS Saves an ATAPHtmlReport .DESCRIPTION Runs the specified ATAPReport and creates a report. .EXAMPLE PS C:\> Save-ATAPHtmlReport -ReportName "Google Chrome" This runs the 'Google Chrome' report and stores the resulting html file (by default) under ~\Documents\ATAPReports .PARAMETER ReportName The name of the report. .PARAMETER Path The path where the result html document should be stored. .PARAMETER DarkMode By default the report is displayed in light mode. If specified the report will be displayed in dark mode. .PARAMETER Force If the parent directory doesn't exist it will be created. .OUTPUTS None. #> function Save-ATAPHtmlReport { [CmdletBinding()] param( [Alias('RN')] [Parameter(Mandatory = $true)] [string] $ReportName, [Parameter(Mandatory = $false)] [string] $Path = ($script:atapReportsPath | Join-Path -ChildPath "$($ReportName)_$(Get-Date -UFormat %Y%m%d_%H%M%S).html"), [switch] $DarkMode, [Parameter()] [switch] $Force ) $parent = Split-Path $Path if (-not [string]::IsNullOrEmpty($parent) -and -not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } Invoke-ATAPReport -ReportName $ReportName | Get-ATAPHtmlReport -Path $Path -DarkMode:$DarkMode } New-Alias -Name 'shr' -Value Save-ATAPHtmlReport $completer = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) Get-ChildItem "$RootPath\Reports\*.ps1" ` | Select-Object -ExpandProperty BaseName ` | ForEach-Object { "`"$_`"" } ` | Where-Object { $_ -like "*$wordToComplete*" } }.GetNewClosure() Register-ArgumentCompleter -CommandName Save-ATAPHtmlReport -ParameterName ReportName -ScriptBlock $completer Register-ArgumentCompleter -CommandName shr -ParameterName ReportName -ScriptBlock $completer |