ATAPAuditor.psm1
|
using namespace Microsoft.PowerShell.Commands #region Initialization $RootPath = Split-Path $MyInvocation.MyCommand.Path -Parent . "$RootPath\Helpers\HashHelper.ps1" $script:atapReportsPath = $env:ATAPReportPath if (-not $script:atapReportsPath) { $script:atapReportsPath = [Environment]::GetFolderPath('MyDocuments') | Join-Path -ChildPath 'ATAPReports' } # for license status function. if called multiple times the cache will be used $LicenseStatusCache = $null #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 [FoundationReport] $FoundationReport } ################################################### ####### SYSTEM INFORMATION Classes ########## ################################################### class SystemInformation { [SoftwareInformation] $SoftwareInformation [HardwareInformation] $HardwareInformation } class SoftwareInformation { [string] $Hostname [string] $SystemUptime [string] $OperatingSystem [string] $BuildNumber [string] $OSArchitecture [string] $LicenseStatus [string] $InstallationLanguage [string] $DomainRole [string] $KernelVersion } class HardwareInformation { [string] $SystemManufacturer [string] $SystemSKU [string] $SystemModel [string] $SystemSerialnumber [string] $BiosVersion [string] $FreeDiskSpace [string] $FreePhysicalMemory } ### Begin Foundation Classes ### class FoundationReport { [ReportSection[]] $Sections } ### End Foundation Classes # 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 # Test whether powershell has the listed modules installed and loaded # If called without parameter, it will do the full module test # If called with a parameter, it will test only for this module and return a boolean function Start-ModuleTest { param( [Parameter(Mandatory = $false)] [string] $LookupModule = "*" ) process { # Here we handle the behavior when we only need to check for one module if($LookupModule -ne "*"){ if($null -eq (Get-Module -Name $LookupModule)){ return $false } return $true } # Here we check for the for all the default required modules $necessaryModules = @( "Microsoft.PowerShell.LocalAccounts", "Microsoft.PowerShell.Management", "Microsoft.PowerShell.Security", "Microsoft.PowerShell.Utility", "TrustedPlatformModule", "NetSecurity", "CimCmdlets", "SmbShare", "Defender", "DISM" ) $missingModules = @() # We create an ArrayList instead of normal array, as adding many items to a normal array is very inefficient $ModuleArrayList = New-Object System.Collections.ArrayList # We split the PSModule Path so we can iterate over it $ModulePathSplit = $env:PSModulePath.Split(";") for($i = 0; $i -lt $ModulePathSplit.Length; $i++){ Get-ChildItem $ModulePathSplit[$i] 2>$null | ForEach-Object($_) { # Here we add all of the available modules to the ArrayList $ModuleArrayList.Add($_.Name) > $null } } # Here we check whether the defined modules are available foreach($module in $necessaryModules){ if($module -notin $ModuleArrayList){ $missingModules += $module } } if ($missingModules.Count -gt 0) { Write-Warning "Missing module(s) found. Missing modules can lead to errors. Following modules are missing:" for ($i = 0; $i -lt $missingModules.Count; $i++) { Write-Warning $missingModules[$i] } Write-Warning "Check out this link on how to install modules: https://learn.microsoft.com/en-us/powershell/module/powershellget/install-module?view=powershellget-3.x" } } } # Get Windows Activation Status function Get-LicenseStatus { Write-Host "Checking operating system activation status" $license = "" # Here we test whether the system is 32 or 64 Bit # Using the 32 Bit version of cscript results in twice the performance when running slmgr if(Test-Path -Path C:\Windows\SysWOW64){ # 64 Bit System, so we specifically use the 32 Bit executable of cscript $license = (C:\Windows\SysWOW64\cscript C:\Windows\System32\slmgr.vbs /xpr)[4] }else{ # 32 Bit System $license = (C:\Windows\System32\cscript C:\Windows\System32\slmgr.vbs /xpr)[4] } # Trim it as the output has 4 spaces to the left by default # The output format for slmgr.vbs /xpr can be a bit hard to work with, so we are going to remove all . and " just to make sure it is clean $license = $license.Trim().Replace(".", "").Replace('"', "") # Here we check the slmgr ini files (The translation files), compare the output from the command with this list, and then take the key from that string $found Get-ChildItem C:\Windows\System32\slmgr | ForEach-Object($_){ $t1 = Get-Content C:\Windows\System32\slmgr\$_\slmgr.ini | Select-String -pattern "LicenseStatus" for ($i = 0; $i -lt $t1.Count; $i++) { $t1[$i].Line = $t1[$i].Line.Replace(".", "").Replace('"', "").Replace("%ENDDATE%", "") } $t2 = $t1 | ConvertFrom-StringData for ($i = 0; $i -lt $t2.Count; $i++) { if($license -match $t2[$i].Values){ $found = $t2[$i].Keys $found > $null # VSCode complains that $found isnt used, soooo, i m using it here to dismiss the complaint break } } } # Here we evaluate our findings, and return the actual name for the license status $LicenseStatusCache = switch ($found) { "L_MsgLicenseStatusUnlicensed" { "Unlicensed" } "L_MsgLicenseStatusLicensed" { "Licensed" } "L_MsgLicenseStatusInitialGrace" { "OOBGrace" } "L_MsgLicenseStatusAdditionalGrace" { "OOTGrace" } "L_MsgLicenseStatusNonGenuineGrace" { "NonGenuineGrace" } "L_MsgLicenseStatusNotification" { "Notification" } "L_MsgLicenseStatusExtendedGrace" { "ExtendedGrace" } } Write-Host "Operating system activation status retrieved" return $LicenseStatusCache } # Compares 2 Arrays 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 } # While this entire check is O(n*m), the arrays used are so small that this remains as the most efficient way to solve this check foreach ($a in $Array1) { # We only check whether the current item is NOT in the other array. # This check is good enough for now, as the system's registry is more secure the fewer items are in this keys we check # We only need to make sure that the specific key does not include values that are not approved if ($a -notin $Array2) { 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 switch ($domainRole) { 0 { $result = "Standalone Workstation" } 1 { $result = "Member Workstation" } 2 { $result = "Standalone Server" } 3 { $result = "Member Server" } 4 { $result = "Backup Domain Controller" } 5 { $result = "Primary Domain Controller" } } return $result } function checkReportNameWithOSSystem { [CmdletBinding()] param ( [Parameter()] [string] $ReportName ) # helpers function handleReportNameDiscrepancy { param ( [Parameter()] [string] $ReportName, [Parameter()] [string] $OsName, [Parameter()] [bool] $ShouldBeStandAlone = $False ) if ($ShouldBeStandAlone -eq $True) { Write-Host "You chose the Reportname $ReportName but the operating system is domain-joined. Be aware that a different report type could affect the result." } else { Write-Host "You chose the Reportname $ReportName but the operating system is $OsName. Be aware that a different report type could affect the result." } Write-Host "" Write-Host "Choose one of the following options:" Write-Host "[1] Continue [2] Exit Script" -ForegroundColor Yellow $in = Read-Host switch ($in) { 1 { Write-Host "You chose to continue" return $ReportName } 2 { Write-Host "You chose to exit the script" return "Exit" } default { Write-Host "Your input was invalid, call Save-ATAPHtmlReport again with your desired report" return "Exit" } } } function returnSuitingReportName { [CmdletBinding()] param ( [Parameter()] [string] $ReportName, [Parameter()] [string] $OsName, [Parameter()] [string] $OsType, [Parameter()] [bool] $ShouldBeStandAlone = $False ) ### # similarity check function isOsNameSimilarToType { [CmdletBinding()] param ( [Parameter()] [string] $OsName, [Parameter()] [string] $OsType ) if ($OsName -match $OsType) { return $true } return $false } if (-not(isOsNameSimilarToType -OsName $osName -OsType $osType)) { return handleReportNameDiscrepancy -ReportName $ReportName -OsName $osName } ### # should be standalone if ($ShouldBeStandAlone -eq $True) { function IsDomainedJoined { if ((Get-CimInstance win32_computersystem).partofdomain) { return $true } return $false } $isDomainJoined = IsDomainedJoined if ($isDomainJoined -eq $True) { return handleReportNameDiscrepancy -ReportName $ReportName -OsName $osName -ShouldBeStandAlone $True } } return $ReportName } #helpers end try { $osName = (Get-ComputerInfo OsName).OsName if ([string]::IsNullOrEmpty($osName)) { return $ReportName # return initial ReportName and skip comparison } function Get-OsType { switch ($ReportName) { "Microsoft Windows Server 2025" { return "Microsoft Windows Server 2025" } "Microsoft Windows Server 2022" { return "Microsoft Windows Server 2022" } "Microsoft Windows Server 2019" { return "Microsoft Windows Server 2019" } "Microsoft Windows Server 2016" { return "Microsoft Windows Server 2016" } "Microsoft Windows Server 2012" { return "Microsoft Windows Server 2012" } "Microsoft Windows 11" { return "Microsoft Windows 11" } "Microsoft Windows 11 Stand-alone" { return "Microsoft Windows 11" } "Microsoft Windows 10" { return "Microsoft Windows 10" } "Microsoft Windows 10 Stand-alone" { return "Microsoft Windows 10" } "Microsoft Windows 10 GDPR" { return "Microsoft Windows 10" } "Microsoft Windows 10 BSI" { return "Microsoft Windows 10" } "Microsoft Windows 7" { return "Microsoft Windows 7" } } } $osType = Get-OsType switch ($ReportName) { "Microsoft Windows Server 2025" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows Server 2022" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows Server 2019" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows Server 2016" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows Server 2012" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows 11" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows 11 Stand-alone" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType -ShouldBeStandAlone $True } "Microsoft Windows 10" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows 10 Stand-alone" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType -ShouldBeStandAlone $True } "Microsoft Windows 10 GDPR" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows 10 BSI" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } "Microsoft Windows 7" { return returnSuitingReportName -ReportName $ReportName -OsName $osName -OsType $osType } } return $ReportName } catch { return $ReportName } } ### begin Foundation functions ### function Get-FoundationReport { [CmdletBinding()] [OutputType([FoundationReport])] $Sections = @( [ReportSection] @{ Title = "Security Base Data" SubSections = @( [ReportSection] @{ Title = 'Platform Security' AuditInfos = Test-AuditGroup "SBD - Platform Security" } [ReportSection] @{ Title = 'Windows Base Security' AuditInfos = Test-AuditGroup "SBD - Windows Base Security" } [ReportSection] @{ Title = 'PowerShell Security' AuditInfos = Test-AuditGroup "SBD - PowerShell Security" } [ReportSection] @{ Title = 'Connectivity Security' AuditInfos = Test-AuditGroup "SBD - Connectivity Security" } [ReportSection] @{ Title = 'Application Control' AuditInfos = Test-AuditGroup "SBD - Application Control" } ) } ) return ([FoundationReport]@{ Sections = $Sections }) } # 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 Tests a single AuditGroup. .DESCRIPTION This cmdlet tests a single AuditGroup from folder "AuditGroups". All tests are printed on the console. Can be combined to create own report. .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 ) #Windows OS if ([System.Environment]::OSVersion.Platform -ne 'Unix') { $tests = . "$RootPath\AuditGroups\$($GroupName).ps1" } #Linux OS else { $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 audit test contains datatype "Constraints", proceed if ($test.Constraints) { $DomainRoleConstraint = $test.Constraints | Where-Object Property -eq "DomainRole" #get domain role of system $currentRole = Get-DomainRole #get domain roles, which are listed in AuditTest $domainRoles = $DomainRoleConstraint.Values if ($currentRole -notin $domainRoles) { $roleValue = (Get-CimInstance -Class Win32_ComputerSystem).DomainRole switch ($roleValue) { 0 { $message = 'Not applicable. This audit does not apply to Standalone Workstation.' $status = [AuditInfoStatus]::None } 1 { $message = 'Not applicable. This audit does not apply to Member Workstation.' $status = [AuditInfoStatus]::None } 2 { $message = 'Not applicable. This audit does not apply to Standalone Server.' $status = [AuditInfoStatus]::None } 3 { $message = 'Not applicable. This audit does not apply to Member Server.' $status = [AuditInfoStatus]::None } 4 { $message = 'Not applicable. This audit does not apply to Backup Domain Controller.' $status = [AuditInfoStatus]::None } 5 { $message = 'Not applicable. This audit does not apply to Primary Domain Controller.' $status = [AuditInfoStatus]::None } } Write-Output ([AuditInfo]@{ Id = $test.Id Task = $test.Task Message = $message Status = $status }) continue } } #Windows OS if ([System.Environment]::OSVersion.Platform -ne 'Unix') { $role = Get-Wmiobject -Class 'Win32_computersystem' -ComputerName $env:computername | Select-Object domainrole if ($test.Task -match "(DC only)") { if ($role.domainRole -ne 4 -and $role.domainRole -ne 5) { $message = 'Not applicable. This audit does not apply to Member Server systems.' $status = [AuditInfoStatus]::None Write-Output ([AuditInfo]@{ Id = $test.Id Task = $test.Task Message = $message Status = $status }) 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 ) #Windows OS if ([System.Environment]::OSVersion.Platform -ne 'Unix') { if ($null -eq $script:loadedResources) { return & "$RootPath\Resources\$($Name).ps1" } if (-not $script:loadedResources.ContainsKey($Name)) { $script:loadedResources[$Name] = (& "$RootPath\Resources\$($Name).ps1") } } #Linuxs OS else { 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 = "*" ) #Windows OS if ([System.Environment]::OSVersion.Platform -ne 'Unix') { return Get-ChildItem "$RootPath\Reports\*$ReportName*.ps1" | Select-Object -Property BaseName } #Linux OS 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 #Windows OS try { if ([System.Environment]::OSVersion.Platform -ne 'Unix') { $moduleInfo = Import-PowerShellDataFile -Path "$RootPath\ATAPAuditor.psd1" [string]$ReportName = checkReportNameWithOSSystem -ReportName $ReportName try { if ($ReportName -eq "Exit") { throw } } catch { Write-Host "Script halted: Exiting..." break } [Report]$report = (& "$RootPath\Reports\$ReportName.ps1") $report.RSReport = Get-RSFullReport $report.FoundationReport = Get-FoundationReport } #Linux OS else { $moduleInfo = Import-PowerShellDataFile -Path "$RootPath/ATAPAuditor.psd1" [Report]$report = (& "$RootPath/Reports/$ReportName.ps1") } } catch [System.Management.Automation.CommandNotFoundException] { Write-Host "Either your input for -Reportname is faulty or the report does not resolve due to a bug. Please report this bug with the following errormessage: 1. ErrorException: $_ 2. PositionMessage: $($_.InvocationInfo.PositionMessage) 3. ReportName: $ReportName" break } $report.AuditorVersion = $moduleInfo.ModuleVersion return $report } <# .SYNOPSIS The Audit Test Automation Package creates transparents reports about hardening compliance status .DESCRIPTION The Audit Test Automation Package gives you the ability to get an overview about the compliance status of several systems. You can easily create HTML-reports and have a transparent overview over compliance and non-compliance of explicit setttings and configurations in comparison to industry standards and hardening guides. .EXAMPLE PS C:\> Save-ATAPHtmlReport -ReportName "Microsoft Windows 10 Complete" -RiskScore -Path C:\Temp\report.html This runs the 'Microsoft Windows 10 Complete' report, adding RiskScore to it and stores the resulting html file under C:\Temp using the file name report.html .EXAMPLE PS C:\> Save-ATAPHtmlReport -ReportName "Microsoft Windows 10 BSI" -RiskScore -Path C:\Temp This runs the 'Microsoft Windows 10 BSI' report, adding RiskScore to it and stores the resulting html file under C:\Temp using the standard naming convention for file names <ReportName_Date_Time>.html .EXAMPLE PS C:\> Save-ATAPHtmlReport -ReportName "Microsoft Windows Server 2022" -Path C:\Temp This runs the 'Microsoft Windows Server 2022' report, without adding RiskScore to it and stores the resulting html file under C:\Temp using the standard naming convention for file names <ReportName_Date_Time>.html .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 .EXAMPLE PS C:\> Save-ATAPHtmlReport -ReportName "Ubuntu 20.04" This runs the 'Ubuntu 20.04' report and stores the resulting html file (by default) under ~\Documents\ATAPReports .PARAMETER ReportName Determine, which OS shall be tested. .PARAMETER Path The path where the result html document should be stored. .PARAMETER RiskScore Add a RiskScore-Matrix to report (works only on Windows OS) .PARAMETER MITRE Add a MITRE ATT&CK headmap to report (works only on Windows OS) .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"), [Parameter(Mandatory = $false)] [switch] $RiskScore, # [Parameter(Mandatory = $false)] # [switch] # $MITRE, [Parameter()] [switch] $Force ) if ([Environment]::Is64BitProcess -eq $false) { Write-Host "Please use 64-bit version of PowerShell in order to use AuditTAP. Closing..." -ForegroundColor red return; } # Check if the system is in FullLanguage mode, and abort if not $languageMode = $ExecutionContext.SessionState.LanguageMode if ($languagemode -ne "FullLanguage") { Write-Host "The current language mode is `"$languagemode`". Ensure that AuditTAP is run in `"FullLanguage`" mode. For further information, contact your administrator. Closing..." -ForegroundColor red return } $parent = $path if ($Path -match ".html") { $parent = Split-Path -Path $Path } #if input path is not default one if ($parent -ne $script:atapReportsPath) { $pathCheck = Test-Path -Path $parent -PathType Container #if path doesn't exist if ($pathCheck -eq $False) { if (-not [string]::IsNullOrEmpty($parent) -and -not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null Write-Warning "Could not find Path. Path will be created: $parent" } else { Write-Warning "Could not find Path. Report will be created inside default path: $($script:atapReportsPath)" $Path = $($script:atapReportsPath) } } } Write-Verbose "OS-Check" $isUnix = [System.Environment]::OSVersion.Platform -eq 'Unix' if ($isUnix) { [SystemInformation] $SystemInformation = (& "$PSScriptRoot\Helpers\ReportUnixOS.ps1") # This is a code snippet that makes sure that the shellscripts dont have CRLF as EOL character $BufferSize = 1024 $shellscripts = Get-ChildItem -Recurse -Include *.sh -Path "$RootPath/Helpers/ShellScripts" -File foreach($one_shellscript in $shellscripts){ try { # We open the file input stream, create a buffer, read the first 1024 bytes into the buffer (doesnt matter if the file has less bytes in total), and fill ChunkBytes with the individual bytes $Stream = [IO.File]::OpenRead($one_shellscript.FullName) $Buffer = New-Object byte[] $BufferSize $BytesRead = $Stream.Read($Buffer, 0, $BufferSize) $Stream.close() $ChunkBytes = $Buffer[0..($BytesRead-1)] # Here we check whether the bytecodes 0x0D (13) and 0x0A (10) appear in the file # If only 0x0A appears in the file, it is already in the LF format, if both appear (order doesnt matter here) then the file is in CRLF format # CR LF if(($ChunkBytes -contains 0x0D) -and ($ChunkBytes -contains 0x0A)) { # If the file is in CRLF format, then replace CRLF with LF, otherwise continue with the next file $one_shellscript_text = [IO.File]::ReadAllText($one_shellscript.FullName) -replace "`r`n", "`n" [IO.File]::WriteAllText($one_shellscript.FullName, $one_shellscript_text) } } catch { Write-Host $one_shellscript.FullName Write-Warning "This file had an error while fixing the EOL characters" } } } else { [SystemInformation] $SystemInformation = (& "$PSScriptRoot\Helpers\ReportWindowsOS.ps1") # Here we define all the reports that need a special module check switch ($ReportName) { "Microsoft IIS10" { if ((Start-ModuleTest "IISAdministration") -eq $false) { Write-Warning "IIS10 Report not executable! IISAdministration module not available. Please install this module and try again. Exiting..." return; } } "Microsoft SQL Server 2016" { if ((Start-ModuleTest "SQLServer") -eq $false) { Write-Warning "SQL Server 2016 Report not executable! SQLServer module not available. Please install this module and try again. Exiting..." return; } } } Start-ModuleTest Write-Verbose "PS-Check" $psVersion = $PSVersionTable.PSVersion #PowerShell Major version not 5.* if (($psVersion.Major -ne 5)) { Write-Warning "ATAPAuditor is only compatible with PowerShell Version 5.1. Your version is $psVersion. Please open a PowerShell Version 5.1 session to continue!" return; } #PowerShell version not 5.1 if (($psVersion.Major -eq 5) -and ($psVersion.Minor -eq 0)) { Write-Warning "ATAPAuditor is only compatible with PowerShell Version 5.1. Your version is $psVersion. You need to upgrade to a higher Windows version!" return; } } $report = Invoke-ATAPReport -ReportName $ReportName #hashes for each recommendation if (!$isUnix) { $SystemInformation.SoftwareInformation.LicenseStatus = Get-LicenseStatus } $hashtable_sha256 = GenerateHashTable $report # $report | Get-ATAPHtmlReport -Path $Path -RiskScore:$RiskScore -MITRE:$MITRE -hashtable_sha256:$hashtable_sha256 -LicenseStatus:$LicenseStatus -SystemInformation:$SystemInformation $report | Get-ATAPHtmlReport -Path $Path -RiskScore:$RiskScore -hashtable_sha256:$hashtable_sha256 -SystemInformation:$SystemInformation } 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 |