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 } #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 } #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) { 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.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)) { if ($Force) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } else { Write-Error "Cannot save the report at $parent because the path does not exist." return } } 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 |