diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml index 94c9bc1..0b02f5e 100644 --- a/.github/workflows/powershell.yml +++ b/.github/workflows/powershell.yml @@ -36,10 +36,11 @@ jobs: with: # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options. # The below set up runs PSScriptAnalyzer to your entire repository and runs some basic security rules. - path: .\ + path: .\source recurse: true # Include your own basic security rules. Removing this option will run all the rules - includeRule: '"PSAvoidGlobalAliases", "PSAvoidUsingConvertToSecureStringWithPlainText"' + includeRule: '"PSAvoidGlobalAliases", "PSAvoidUsingConvertToSecureStringWithPlainText", "PSAvoidUsingPlainTextForPassword", "PSAvoidUsingInvokeExpression", "PSUseApprovedVerbs", "PSAvoidUsingPositionalParameters", "PSAvoidUsingEmptyCatchBlock", "PSAvoidUsingDeprecatedManifestFields", "PSAvoidUsingUserNameAndPasswordParams", "PSAvoidUsingCmdletAliases"' + output: results.sarif # Upload the SARIF file generated in the previous step diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f2c164..60c65fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,38 @@ The format is based on and uses the types of changes according to [Keep a Change ### Added +- Updated test definitions for CIS Microsoft 365 Foundations Benchmark for better error handling and object output when errors occur. +- Added a parameter to the `Initialize-CISAuditResult` function to allow for a static failed object to be created when an error occurs. +- Refactored `Invoke-M365SecurityAudit` to include a new private function `Invoke-TestFunction` for executing test functions and handling errors. +- Added a new private function `Measure-AuditResult` to calculate and display audit results. +- Enhanced error logging to capture failed test details and display them at the end of the audit. +- Added a private function `Get-RequiredModule` to initialize the `$requiredModules` variable for better code organization in the main script. +- Updated `Test-MailboxAuditingE3` and `Test-MailboxAuditingE5` functions to use `Format-MissingAction` for structuring missing actions into a pipe-separated table format. +- Added more verbose logging to `Test-BlockMailForwarding` and improved error handling for better troubleshooting. +- Improved `Test-RestrictCustomScripts` to handle long URL lengths better by extracting and replacing common hostnames, and provided detailed output. +- Added sorting to output. +- Created new functions for improved modularity. +- Parameter validation for excel and csv path in sync function +- Added Output type to tests. + +### Fixed + +- Ensured the `Invoke-TestFunction` returns a `CISAuditResult` object, which is then managed in the `Invoke-M365SecurityAudit` function. +- Corrected the usage of the join operation within `$details` in `Test-BlockMailForwarding` to handle arrays properly. +- Fixed the logic in `Test-RestrictCustomScripts` to accurately replace and manage URLs, ensuring compliance checks are correctly performed. +- Updated the `Test-MailboxAuditingE3` and `Test-MailboxAuditingE5` functions to handle the `$allFailures` variable correctly, ensuring accurate pass/fail results. +- Fixed the connections in helper CSV and connect function. +- Removed verbose preference from `Test-RestrictCustomScripts`. +- Ensured that the output in `Test-BlockMailForwarding` does not include extra spaces between table headers and data. +- Fixed output in `Test-MailboxAuditingE3` and `Test-MailboxAuditingE5` to correctly align with the new table format. +- Added step 1 and step 2 in `Test-BlockMailForwarding` details to ensure comprehensive compliance checks. +- Fixed the issue with the output in `Test-RestrictCustomScripts` to ensure no extra spaces between table headers and data. + + +## [0.1.4] - 2024-05-30 + +### Added + - Test definitions filter function. - Logging function for future use. - Test grade written to console. @@ -87,4 +119,4 @@ The format is based on and uses the types of changes according to [Keep a Change - A dynamic test loading system based on CSV input for flexibility in defining audit tests. - Comprehensive verbose logging to detail the steps being performed during an audit. - Comment-help documentation for the `Invoke-M365SecurityAudit` function with examples and usage details. -- Attribution to CIS and licensing information under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License in the README. \ No newline at end of file +- Attribution to CIS and licensing information under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License in the README. diff --git a/README.md b/README.md index f57576e..f583057 100644 Binary files a/README.md and b/README.md differ diff --git a/docs/index.html b/docs/index.html index acf3db0..20f1530 100644 Binary files a/docs/index.html and b/docs/index.html differ diff --git a/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3.ps1 b/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3.ps1 index abce35b..62a5675 100644 --- a/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3.ps1 +++ b/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3.ps1 @@ -60,7 +60,7 @@ function Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3 { } if ($missingActions) { - $formattedActions = Format-MissingActions $missingActions + $formattedActions = Format-MissingAction $missingActions $allFailures += "$userUPN`: AuditEnabled - True; $formattedActions" } # Mark the user as processed @@ -83,7 +83,7 @@ function Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3 { } } -function Format-MissingActions { +function Format-MissingAction { param ([array]$missingActions) $actionGroups = @{ diff --git a/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3.ps1 b/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3.ps1 index 1dd6b92..5525a67 100644 --- a/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3.ps1 +++ b/helpers/CIS 365 v3.0.0 Controls/Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3.ps1 @@ -60,7 +60,7 @@ function Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3 { } if ($missingActions) { - $formattedActions = Format-MissingActions $missingActions + $formattedActions = Format-MissingAction $missingActions $allFailures += "$userUPN`: AuditEnabled - True; $formattedActions" } else { @@ -92,7 +92,7 @@ function Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3 { } } -function Format-MissingActions { +function Format-MissingAction { param ([array]$missingActions) $actionGroups = @{ diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index 97d9e4c..468d068 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -11,13 +11,13 @@ function Connect-M365Suite { $VerbosePreference = "SilentlyContinue" try { - if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") { + if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Cyan Connect-AzureAD | Out-Null Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green } - if ($RequiredConnections -contains "Microsoft Graph") { + if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { Write-Host "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Cyan try { Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null @@ -30,7 +30,7 @@ function Connect-M365Suite { } } - if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") { + if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan Connect-ExchangeOnline | Out-Null Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green diff --git a/source/Private/Format-MissingAction.ps1 b/source/Private/Format-MissingAction.ps1 new file mode 100644 index 0000000..1932fb1 --- /dev/null +++ b/source/Private/Format-MissingAction.ps1 @@ -0,0 +1,25 @@ +function Format-MissingAction { + param ([array]$missingActions) + + $actionGroups = @{ + "Admin" = @() + "Delegate" = @() + "Owner" = @() + } + + foreach ($action in $missingActions) { + if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") { + $type = $matches[1] + $actionName = $matches[2] + $actionGroups[$type] += $actionName + } + } + + $formattedResults = @{ + Admin = $actionGroups["Admin"] -join ', ' + Delegate = $actionGroups["Delegate"] -join ', ' + Owner = $actionGroups["Owner"] -join ', ' + } + + return $formattedResults +} \ No newline at end of file diff --git a/source/Private/Get-MostCommonWord.ps1 b/source/Private/Get-MostCommonWord.ps1 new file mode 100644 index 0000000..b626b5e --- /dev/null +++ b/source/Private/Get-MostCommonWord.ps1 @@ -0,0 +1,22 @@ +function Get-MostCommonWord { + param ( + [Parameter(Mandatory = $true)] + [string[]]$InputStrings + ) + + # Combine all strings into one large string + $allText = $InputStrings -join ' ' + + # Split the large string into words + $words = $allText -split '\s+' + + # Group words and count occurrences + $wordGroups = $words | Group-Object | Sort-Object Count -Descending + + # Return the most common word if it occurs at least 3 times + if ($wordGroups.Count -gt 0 -and $wordGroups[0].Count -ge 3) { + return $wordGroups[0].Name + } else { + return $null + } +} \ No newline at end of file diff --git a/source/Private/Get-RequiredModule.ps1 b/source/Private/Get-RequiredModule.ps1 new file mode 100644 index 0000000..015036b --- /dev/null +++ b/source/Private/Get-RequiredModule.ps1 @@ -0,0 +1,37 @@ +function Get-RequiredModule { + [CmdletBinding(DefaultParameterSetName = 'AuditFunction')] + [OutputType([System.Object[]])] + param ( + [Parameter(Mandatory = $true, ParameterSetName = 'AuditFunction')] + [switch]$AuditFunction, + + [Parameter(Mandatory = $true, ParameterSetName = 'SyncFunction')] + [switch]$SyncFunction + ) + + switch ($PSCmdlet.ParameterSetName) { + 'AuditFunction' { + return @( + @{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0" }, + @{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Authentication" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Users" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Groups" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "DirectoryObjects" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Domains" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Reports" }, + @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Mail" }, + @{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000" }, + @{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0" } + ) + } + 'SyncFunction' { + return @( + @{ ModuleName = "ImportExcel"; RequiredVersion = "7.8.9" } + ) + } + default { + throw "Please specify either -AuditFunction or -SyncFunction switch." + } + } +} diff --git a/source/Private/Initialize-CISAuditResult.ps1 b/source/Private/Initialize-CISAuditResult.ps1 index b1e2e09..3b2cc94 100644 --- a/source/Private/Initialize-CISAuditResult.ps1 +++ b/source/Private/Initialize-CISAuditResult.ps1 @@ -1,19 +1,23 @@ function Initialize-CISAuditResult { + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Rec, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Full')] [bool]$Result, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Full')] [string]$Status, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Full')] [string]$Details, - [Parameter(Mandatory = $true)] - [string]$FailureReason + [Parameter(Mandatory = $true, ParameterSetName = 'Full')] + [string]$FailureReason, + + [Parameter(ParameterSetName = 'Error')] + [switch]$Failure ) # Import the test definitions CSV file @@ -22,6 +26,10 @@ function Initialize-CISAuditResult { # Find the row that matches the provided recommendation (Rec) $testDefinition = $testDefinitions | Where-Object { $_.Rec -eq $Rec } + if (-not $testDefinition) { + throw "Test definition for recommendation '$Rec' not found." + } + # Create an instance of CISAuditResult and populate it $auditResult = [CISAuditResult]::new() $auditResult.Rec = $Rec @@ -36,10 +44,18 @@ function Initialize-CISAuditResult { $auditResult.Automated = [bool]::Parse($testDefinition.Automated) $auditResult.Connection = $testDefinition.Connection $auditResult.CISControlVer = 'v8' - $auditResult.Result = $Result - $auditResult.Status = $Status - $auditResult.Details = $Details - $auditResult.FailureReason = $FailureReason + + if ($PSCmdlet.ParameterSetName -eq 'Full') { + $auditResult.Result = $Result + $auditResult.Status = $Status + $auditResult.Details = $Details + $auditResult.FailureReason = $FailureReason + } elseif ($PSCmdlet.ParameterSetName -eq 'Error') { + $auditResult.Result = $false + $auditResult.Status = 'Fail' + $auditResult.Details = "An error occurred while processing the test." + $auditResult.FailureReason = "Initialization error: Failed to process the test." + } return $auditResult } diff --git a/source/Private/Invoke-TestFunction.ps1 b/source/Private/Invoke-TestFunction.ps1 new file mode 100644 index 0000000..d9a5997 --- /dev/null +++ b/source/Private/Invoke-TestFunction.ps1 @@ -0,0 +1,34 @@ +function Invoke-TestFunction { + param ( + [Parameter(Mandatory = $true)] + [PSObject]$FunctionFile, + + [Parameter(Mandatory = $true)] + [string]$DomainName + ) + + $functionName = $FunctionFile.BaseName + $functionCmd = Get-Command -Name $functionName + + # Check if the test function needs DomainName parameter + $paramList = @{} + if ('DomainName' -in $functionCmd.Parameters.Keys) { + $paramList.DomainName = $DomainName + } + + # Use splatting to pass parameters + Write-Verbose "Running $functionName..." + try { + $result = & $functionName @paramList + # Assuming each function returns an array of CISAuditResult or a single CISAuditResult + return $result + } + catch { + Write-Error "An error occurred during the test: $_" + $script:FailedTests.Add([PSCustomObject]@{ Test = $functionName; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $functionName -Failure + return $auditResult + } +} diff --git a/source/Private/Measure-AuditResult.ps1 b/source/Private/Measure-AuditResult.ps1 new file mode 100644 index 0000000..d03265e --- /dev/null +++ b/source/Private/Measure-AuditResult.ps1 @@ -0,0 +1,31 @@ +function Measure-AuditResult { + param ( + [Parameter(Mandatory = $true)] + [System.Collections.ArrayList]$AllAuditResults, + + [Parameter(Mandatory = $false)] + [System.Collections.ArrayList]$FailedTests + ) + + # Calculate the total number of tests + $totalTests = $AllAuditResults.Count + + # Calculate the number of passed tests + $passedTests = $AllAuditResults.ToArray() | Where-Object { $_.Result -eq $true } | Measure-Object | Select-Object -ExpandProperty Count + + # Calculate the pass percentage + $passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) } + + # Display the pass percentage to the user + Write-Host "Audit completed. $passedTests out of $totalTests tests passed." -ForegroundColor Cyan + Write-Host "Your passing percentage is $passPercentage%." + + # Display details of failed tests + if ($FailedTests.Count -gt 0) { + Write-Host "The following tests failed to complete:" -ForegroundColor Red + foreach ($failedTest in $FailedTests) { + Write-Host "Test: $($failedTest.Test)" -ForegroundColor Yellow + Write-Host "Error: $($failedTest.Error)" -ForegroundColor Yellow + } + } +} diff --git a/source/Private/Merge-CISExcelAndCsvData.ps1 b/source/Private/Merge-CISExcelAndCsvData.ps1 index bc09c22..0922c9b 100644 --- a/source/Private/Merge-CISExcelAndCsvData.ps1 +++ b/source/Private/Merge-CISExcelAndCsvData.ps1 @@ -1,5 +1,5 @@ function Merge-CISExcelAndCsvData { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'CsvInput')] param ( [Parameter(Mandatory = $true)] [string]$ExcelPath, @@ -7,40 +7,35 @@ function Merge-CISExcelAndCsvData { [Parameter(Mandatory = $true)] [string]$WorksheetName, - [Parameter(Mandatory = $true)] - [string]$CsvPath + [Parameter(Mandatory = $true, ParameterSetName = 'CsvInput')] + [string]$CsvPath, + + [Parameter(Mandatory = $true, ParameterSetName = 'ObjectInput')] + [CISAuditResult[]]$AuditResults ) process { - # Import data from Excel and CSV + # Import data from Excel $import = Import-Excel -Path $ExcelPath -WorksheetName $WorksheetName - $csvData = Import-Csv -Path $CsvPath - # Define a function to create a merged object - function CreateMergedObject($excelItem, $csvRow) { - $newObject = New-Object PSObject - - foreach ($property in $excelItem.PSObject.Properties) { - $newObject | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value - } - $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Connection' -Value $csvRow.Connection - $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Status' -Value $csvRow.Status - $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Details' -Value $csvRow.Details - $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_FailureReason' -Value $csvRow.FailureReason - return $newObject + # Import data from CSV or use provided object + $csvData = if ($PSCmdlet.ParameterSetName -eq 'CsvInput') { + Import-Csv -Path $CsvPath + } else { + $AuditResults } - # Iterate over each item in the imported Excel object and merge with CSV data + # Iterate over each item in the imported Excel object and merge with CSV data or audit results $mergedData = foreach ($item in $import) { $csvRow = $csvData | Where-Object { $_.Rec -eq $item.'recommendation #' } if ($csvRow) { - CreateMergedObject -excelItem $item -csvRow $csvRow + New-MergedObject -ExcelItem $item -CsvRow $csvRow } else { - CreateMergedObject -excelItem $item -csvRow ([PSCustomObject]@{Connection=$null;Status=$null; Details=$null; FailureReason=$null }) + New-MergedObject -ExcelItem $item -CsvRow ([PSCustomObject]@{Connection=$null;Status=$null; Details=$null; FailureReason=$null }) } } # Return the merged data return $mergedData } -} +} \ No newline at end of file diff --git a/source/Private/New-MergedObject.ps1 b/source/Private/New-MergedObject.ps1 new file mode 100644 index 0000000..4840d73 --- /dev/null +++ b/source/Private/New-MergedObject.ps1 @@ -0,0 +1,20 @@ +function New-MergedObject { + param ( + [Parameter(Mandatory = $true)] + [psobject]$ExcelItem, + + [Parameter(Mandatory = $true)] + [psobject]$CsvRow + ) + + $newObject = New-Object PSObject + + foreach ($property in $ExcelItem.PSObject.Properties) { + $newObject | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value + } + $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Connection' -Value $CsvRow.Connection + $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Status' -Value $CsvRow.Status + $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Details' -Value $CsvRow.Details + $newObject | Add-Member -MemberType NoteProperty -Name 'CSV_FailureReason' -Value $CsvRow.FailureReason + return $newObject +} diff --git a/source/Private/Update-CISExcelWorksheet.ps1 b/source/Private/Update-CISExcelWorksheet.ps1 index 6de5747..a0d7ae1 100644 --- a/source/Private/Update-CISExcelWorksheet.ps1 +++ b/source/Private/Update-CISExcelWorksheet.ps1 @@ -25,7 +25,7 @@ function Update-CISExcelWorksheet { # Update the worksheet with the provided data - Update-WorksheetCells -Worksheet $worksheet -Data $Data -StartingRowIndex $StartingRowIndex + Update-WorksheetCell -Worksheet $worksheet -Data $Data -StartingRowIndex $StartingRowIndex # Save and close the Excel package Close-ExcelPackage $excelPackage diff --git a/source/Private/Update-WorksheetCells.ps1 b/source/Private/Update-WorksheetCell.ps1 similarity index 95% rename from source/Private/Update-WorksheetCells.ps1 rename to source/Private/Update-WorksheetCell.ps1 index eae857f..622f963 100644 --- a/source/Private/Update-WorksheetCells.ps1 +++ b/source/Private/Update-WorksheetCell.ps1 @@ -1,4 +1,4 @@ - function Update-WorksheetCells { + function Update-WorksheetCell { param ( $Worksheet, $Data, diff --git a/source/Public/Get-AdminRoleUserLicense.ps1 b/source/Public/Get-AdminRoleUserLicense.ps1 index fa2a49a..9cd86c3 100644 --- a/source/Public/Get-AdminRoleUserLicense.ps1 +++ b/source/Public/Get-AdminRoleUserLicense.ps1 @@ -25,6 +25,8 @@ https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-AdminRoleUserLicense #> function Get-AdminRoleUserLicense { + # Set output type to System.Collections.ArrayList + [OutputType([System.Collections.ArrayList])] [CmdletBinding()] param ( [Parameter(Mandatory = $false)] diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index e8a29ee..aa22711 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -63,10 +63,12 @@ function Invoke-M365SecurityAudit { [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')] [OutputType([CISAuditResult[]])] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'.")] + [ValidatePattern('^https://[a-zA-Z0-9-]+-admin\.sharepoint\.com$')] [string]$TenantAdminUrl, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, HelpMessage = "The domain name of your organization, e.g., 'example.com'.")] + [ValidatePattern('^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$')] [string]$DomainName, # E-Level with optional ProfileLevel selection @@ -92,12 +94,12 @@ function Invoke-M365SecurityAudit { [Parameter(Mandatory = $true, ParameterSetName = 'RecFilter')] [ValidateSet( '1.1.1', '1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', ` - '2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', ` - '5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', ` - '6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', ` - '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', ` - '8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', ` - '8.5.7', '8.6.1' + '2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', ` + '5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', ` + '6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', ` + '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', ` + '8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', ` + '8.5.7', '8.6.1' )] [string[]]$IncludeRecommendation, @@ -105,12 +107,12 @@ function Invoke-M365SecurityAudit { [Parameter(Mandatory = $true, ParameterSetName = 'SkipRecFilter')] [ValidateSet( '1.1.1', '1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', ` - '2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', ` - '5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', ` - '6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', ` - '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', ` - '8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', ` - '8.5.7', '8.6.1' + '2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', ` + '5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', ` + '6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', ` + '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', ` + '8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', ` + '8.5.7', '8.6.1' )] [string[]]$SkipRecommendation, @@ -120,43 +122,23 @@ function Invoke-M365SecurityAudit { [switch]$NoModuleCheck ) - - Begin { if ($script:MaximumFunctionCount -lt 8192) { $script:MaximumFunctionCount = 8192 } # Ensure required modules are installed - # Define the required modules and versions in a hashtable if (!($NoModuleCheck)) { - $requiredModules = @( - @{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0" }, - @{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Authentication" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Users" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Groups" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "DirectoryObjects" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Domains" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Reports" }, - @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Mail" }, - @{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000" }, - @{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0" } - ) + $requiredModules = Get-RequiredModule -AuditFunction foreach ($module in $requiredModules) { Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModuleName $module.SubModuleName } } - - - # Loop through each required module and assert its availability - - # Establishing connections - # Load test definitions from CSV $testDefinitionsPath = Join-Path -Path $PSScriptRoot -ChildPath "helper\TestDefinitions.csv" $testDefinitions = Import-Csv -Path $testDefinitionsPath # Load the Test Definitions into the script scope for use in other functions $script:TestDefinitionsObject = $testDefinitions + # Apply filters based on parameter sets $params = @{ TestDefinitions = $testDefinitions @@ -167,58 +149,54 @@ function Invoke-M365SecurityAudit { SkipRecommendation = $SkipRecommendation } $testDefinitions = Get-TestDefinitionsObject @params - # End switch ($PSCmdlet.ParameterSetName) # Extract unique connections needed $requiredConnections = $testDefinitions.Connection | Sort-Object -Unique - # Establishing connections if required if (!($DoNotConnect)) { Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections } # Determine which test files to load based on filtering $testsToLoad = $testDefinitions.TestFileName | ForEach-Object { $_ -replace '.ps1$', '' } - - # Display the tests that would be loaded if the function is called with -WhatIf - Write-Verbose "The $(($testsToLoad).count) test/s that would be loaded based on filter criteria:" $testsToLoad | ForEach-Object { Write-Verbose " $_" } + # Initialize a collection to hold failed test details + $script:FailedTests = [System.Collections.ArrayList]::new() } # End Begin - Process { - $allAuditResults = [System.Collections.ArrayList]::new() #@() # Initialize a collection to hold all results - + $allAuditResults = [System.Collections.ArrayList]::new() # Initialize a collection to hold all results # Dynamically dot-source the test scripts $testsFolderPath = Join-Path -Path $PSScriptRoot -ChildPath "tests" $testFiles = Get-ChildItem -Path $testsFolderPath -Filter "Test-*.ps1" | Where-Object { $testsToLoad -contains $_.BaseName } + $totalTests = $testFiles.Count + $currentTestIndex = 0 + # Import the test functions $testFiles | ForEach-Object { + $currentTestIndex++ + Write-Progress -Activity "Loading Test Scripts" -Status "Loading $($currentTestIndex) of $($totalTests): $($_.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100) Try { + # Dot source the test function . $_.FullName } Catch { + # Log the error and add the test to the failed tests collection Write-Error "Failed to load test function $($_.Name): $_" + $script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) } } + $currentTestIndex = 0 # Execute each test function from the prepared list foreach ($testFunction in $testFiles) { + $currentTestIndex++ + Write-Progress -Activity "Executing Tests" -Status "Executing $($currentTestIndex) of $($totalTests): $($testFunction.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100) $functionName = $testFunction.BaseName - $functionCmd = Get-Command -Name $functionName - - # Check if the test function needs DomainName parameter - $paramList = @{} - if ('DomainName' -in $functionCmd.Parameters.Keys) { - $paramList.DomainName = $DomainName - } - - # Use splatting to pass parameters if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { - Write-Host "Running $functionName..." - $result = & $functionName @paramList - # Assuming each function returns an array of CISAuditResult or a single CISAuditResult - [void]($allAuditResults.add($Result)) + $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $DomainName + # Add the result to the collection + [void]$allAuditResults.Add($auditResult) } } } @@ -228,20 +206,10 @@ function Invoke-M365SecurityAudit { # Clean up sessions Disconnect-M365Suite -RequiredConnections $requiredConnections } - # Calculate the total number of tests - $totalTests = $allAuditResults.Count - - # Calculate the number of passed tests - $passedTests = $allAuditResults.ToArray() | Where-Object { $_.Result -eq $true } | Measure-Object | Select-Object -ExpandProperty Count - - # Calculate the pass percentage - $passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) } - - # Display the pass percentage to the user - Write-Host "Audit completed. $passedTests out of $totalTests tests passed." -ForegroundColor Cyan - Write-Host "Your passing percentage is $passPercentage%." + # Call the private function to calculate and display results + Measure-AuditResult -AllAuditResults $allAuditResults -FailedTests $script:FailedTests # Return all collected audit results - return $allAuditResults.ToArray() - # Check if the Disconnect switch is present + return $allAuditResults.ToArray() | Sort-Object -Property Rec } -} \ No newline at end of file +} + diff --git a/source/Public/Sync-CISExcelAndCsvData.ps1 b/source/Public/Sync-CISExcelAndCsvData.ps1 index 378aba4..4b79249 100644 --- a/source/Public/Sync-CISExcelAndCsvData.ps1 +++ b/source/Public/Sync-CISExcelAndCsvData.ps1 @@ -1,14 +1,16 @@ <# .SYNOPSIS - Synchronizes data between an Excel file and a CSV file and optionally updates the Excel worksheet. + Synchronizes data between an Excel file and either a CSV file or an output object from Invoke-M365SecurityAudit, and optionally updates the Excel worksheet. .DESCRIPTION - The Sync-CISExcelAndCsvData function merges data from a specified Excel file and a CSV file based on a common key. It can also update the Excel worksheet with the merged data. This function is particularly useful for updating Excel records with additional data from a CSV file while preserving the original formatting and structure of the Excel worksheet. + The Sync-CISExcelAndCsvData function merges data from a specified Excel file with data from either a CSV file or an output object from Invoke-M365SecurityAudit based on a common key. It can also update the Excel worksheet with the merged data. This function is particularly useful for updating Excel records with additional data from a CSV file or audit results while preserving the original formatting and structure of the Excel worksheet. .PARAMETER ExcelPath The path to the Excel file that contains the original data. This parameter is mandatory. .PARAMETER WorksheetName The name of the worksheet within the Excel file that contains the data to be synchronized. This parameter is mandatory. .PARAMETER CsvPath - The path to the CSV file containing data to be merged with the Excel data. This parameter is mandatory. + The path to the CSV file containing data to be merged with the Excel data. This parameter is mandatory when using the CsvInput parameter set. + .PARAMETER AuditResults + An array of CISAuditResult objects from Invoke-M365SecurityAudit to be merged with the Excel data. This parameter is mandatory when using the ObjectInput parameter set. .PARAMETER SkipUpdate If specified, the function will return the merged data object without updating the Excel worksheet. This is useful for previewing the merged data. .EXAMPLE @@ -17,6 +19,14 @@ .EXAMPLE PS> $mergedData = Sync-CISExcelAndCsvData -ExcelPath "path\to\excel.xlsx" -WorksheetName "DataSheet" -CsvPath "path\to\data.csv" -SkipUpdate Retrieves the merged data object for preview without updating the Excel worksheet. + .EXAMPLE + PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://tenant-admin.url" -DomainName "example.com" + PS> Sync-CISExcelAndCsvData -ExcelPath "path\to\excel.xlsx" -WorksheetName "DataSheet" -AuditResults $auditResults + Merges data from the audit results into 'excel.xlsx' on the 'DataSheet' worksheet and updates the worksheet with the merged data. + .EXAMPLE + PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://tenant-admin.url" -DomainName "example.com" + PS> $mergedData = Sync-CISExcelAndCsvData -ExcelPath "path\to\excel.xlsx" -WorksheetName "DataSheet" -AuditResults $auditResults -SkipUpdate + Retrieves the merged data object for preview without updating the Excel worksheet. .INPUTS None. You cannot pipe objects to Sync-CISExcelAndCsvData. .OUTPUTS @@ -30,25 +40,41 @@ https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Sync-CISExcelAndCsvData #> + function Sync-CISExcelAndCsvData { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'CsvInput')] param ( [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path $_ })] [string]$ExcelPath, [Parameter(Mandatory = $true)] [string]$WorksheetName, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'CsvInput')] + [ValidateScript({ Test-Path $_ })] [string]$CsvPath, + [Parameter(Mandatory = $true, ParameterSetName = 'ObjectInput')] + [CISAuditResult[]]$AuditResults, + [Parameter(Mandatory = $false)] [switch]$SkipUpdate ) process { - # Merge Excel and CSV data - $mergedData = Merge-CISExcelAndCsvData -ExcelPath $ExcelPath -WorksheetName $WorksheetName -CsvPath $CsvPath + # Verify ImportExcel module is available + $requiredModules = Get-RequiredModule -SyncFunction + foreach ($module in $requiredModules) { + Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModuleName $module.SubModuleName + } + + # Merge Excel and CSV data or Audit Results + if ($PSCmdlet.ParameterSetName -eq 'CsvInput') { + $mergedData = Merge-CISExcelAndCsvData -ExcelPath $ExcelPath -WorksheetName $WorksheetName -CsvPath $CsvPath + } else { + $mergedData = Merge-CISExcelAndCsvData -ExcelPath $ExcelPath -WorksheetName $WorksheetName -AuditResults $AuditResults + } # Output the merged data if the user chooses to skip the update if ($SkipUpdate) { @@ -59,3 +85,5 @@ function Sync-CISExcelAndCsvData { } } } + + diff --git a/source/helper/TestDefinitions.csv b/source/helper/TestDefinitions.csv index a284e76..b499047 100644 --- a/source/helper/TestDefinitions.csv +++ b/source/helper/TestDefinitions.csv @@ -18,8 +18,8 @@ 17,Test-RestrictTenantCreation.ps1,5.1.2.3,Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes',E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,Microsoft Graph 18,Test-PasswordHashSync.ps1,5.1.8.1,Ensure password hash sync is enabled for hybrid deployments,E3,L1,6.7,Centralize Access Control,FALSE,TRUE,TRUE,TRUE,Microsoft Graph 19,Test-AuditDisabledFalse.ps1,6.1.1,Ensure 'AuditDisabled' organizationally is set to 'False',E3,L1,8.2,Collect Audit Logs,TRUE,TRUE,TRUE,TRUE,Microsoft Graph -20,Test-MailboxAuditingE3.ps1,6.1.2,Ensure mailbox auditing for Office E3 users is Enabled,E3,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO -21,Test-MailboxAuditingE5.ps1,6.1.3,Ensure mailbox auditing for Office E5 users is Enabled,E5,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO +20,Test-MailboxAuditingE3.ps1,6.1.2,Ensure mailbox auditing for Office E3 users is Enabled,E3,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO | Microsoft Graph +21,Test-MailboxAuditingE5.ps1,6.1.3,Ensure mailbox auditing for Office E5 users is Enabled,E5,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO | Microsoft Graph 22,Test-BlockMailForwarding.ps1,6.2.1,Ensure all forms of mail forwarding are blocked and/or disabled,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO 23,Test-NoWhitelistDomains.ps1,6.2.2,Ensure mail transport rules do not whitelist specific domains,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO 24,Test-IdentifyExternalEmail.ps1,6.2.3,Ensure email from external senders is identified,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO diff --git a/source/tests/Test-AdministrativeAccountCompliance.ps1 b/source/tests/Test-AdministrativeAccountCompliance.ps1 index 21ab959..1b22bdb 100644 --- a/source/tests/Test-AdministrativeAccountCompliance.ps1 +++ b/source/tests/Test-AdministrativeAccountCompliance.ps1 @@ -1,83 +1,113 @@ function Test-AdministrativeAccountCompliance { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( - # Aligned # Parameters can be added if needed ) begin { - #. .\source\Classes\CISAuditResult.ps1 $validLicenses = @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $recnum = "1.1.1" } process { - $adminRoles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { $_.DisplayName -like "*Admin*" } - $adminRoleUsers = @() + try { + # Retrieve all necessary data outside the loops + $adminRoles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { $_.DisplayName -like "*Admin*" } + $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment + $principalIds = $roleAssignments.PrincipalId | Select-Object -Unique - foreach ($role in $adminRoles) { - $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$($role.Id)'" + # Fetch user details using filter + $userDetailsList = @{} + $licensesList = @{} - foreach ($assignment in $roleAssignments) { - $userDetails = Get-MgUser -UserId $assignment.PrincipalId -Property "DisplayName, UserPrincipalName, Id, OnPremisesSyncEnabled" -ErrorAction SilentlyContinue - if ($userDetails) { - $licenses = Get-MgUserLicenseDetail -UserId $assignment.PrincipalId -ErrorAction SilentlyContinue - $licenseString = if ($licenses) { ($licenses.SkuPartNumber -join '|') } else { "No Licenses Found" } + $userDetails = Get-MgUser -Filter "id in ('$($principalIds -join "','")')" -Property "DisplayName, UserPrincipalName, Id, OnPremisesSyncEnabled" -ErrorAction SilentlyContinue + foreach ($user in $userDetails) { + $userDetailsList[$user.Id] = $user + } - $adminRoleUsers += [PSCustomObject]@{ - UserName = $userDetails.UserPrincipalName - RoleName = $role.DisplayName - UserId = $userDetails.Id - HybridUser = $userDetails.OnPremisesSyncEnabled - Licenses = $licenseString + # Fetch user licenses for each unique principal ID + foreach ($principalId in $principalIds) { + $licensesList[$principalId] = Get-MgUserLicenseDetail -UserId $principalId -ErrorAction SilentlyContinue + } + + $adminRoleUsers = @() + + foreach ($role in $adminRoles) { + foreach ($assignment in $roleAssignments | Where-Object { $_.RoleDefinitionId -eq $role.Id }) { + $userDetails = $userDetailsList[$assignment.PrincipalId] + if ($userDetails) { + $licenses = $licensesList[$assignment.PrincipalId] + $licenseString = if ($licenses) { ($licenses.SkuPartNumber -join '|') } else { "No Licenses Found" } + + $adminRoleUsers += [PSCustomObject]@{ + UserName = $userDetails.UserPrincipalName + RoleName = $role.DisplayName + UserId = $userDetails.Id + HybridUser = $userDetails.OnPremisesSyncEnabled + Licenses = $licenseString + } } } } - } - $uniqueAdminRoleUsers = $adminRoleUsers | Group-Object -Property UserName | ForEach-Object { - $first = $_.Group | Select-Object -First 1 - $roles = ($_.Group.RoleName -join ', ') - $licenses = (($_.Group | Select-Object -ExpandProperty Licenses) -join ',').Split(',') | Select-Object -Unique + $uniqueAdminRoleUsers = $adminRoleUsers | Group-Object -Property UserName | ForEach-Object { + $first = $_.Group | Select-Object -First 1 + $roles = ($_.Group.RoleName -join ', ') + $licenses = (($_.Group | Select-Object -ExpandProperty Licenses) -join ',').Split(',') | Select-Object -Unique - $first | Select-Object UserName, UserId, HybridUser, @{Name = 'Roles'; Expression = { $roles } }, @{Name = 'Licenses'; Expression = { $licenses -join '|' } } - } + $first | Select-Object UserName, UserId, HybridUser, @{Name = 'Roles'; Expression = { $roles } }, @{Name = 'Licenses'; Expression = { $licenses -join '|' } } + } - $nonCompliantUsers = $uniqueAdminRoleUsers | Where-Object { - $_.HybridUser -or - -not ($_.Licenses -split '\|' | Where-Object { $validLicenses -contains $_ }) - } + $nonCompliantUsers = $uniqueAdminRoleUsers | Where-Object { + $_.HybridUser -or + -not ($_.Licenses -split '\|' | Where-Object { $validLicenses -contains $_ }) + } - $failureReasons = $nonCompliantUsers | ForEach-Object { - $accountType = if ($_.HybridUser) { "Hybrid" } else { "Cloud-Only" } - $missingLicenses = $validLicenses | Where-Object { $_ -notin ($_.Licenses -split '\|') } - "$($_.UserName)|$($_.Roles)|$accountType|Missing: $($missingLicenses -join ',')" - } - $failureReasons = $failureReasons -join "`n" - $details = if ($nonCompliantUsers) { - "Non-Compliant Accounts: $($nonCompliantUsers.Count)`nDetails:`n" + ($nonCompliantUsers | ForEach-Object { $_.UserName }) -join "`n" - } - else { - "Compliant Accounts: $($uniqueAdminRoleUsers.Count)" - } + $failureReasons = $nonCompliantUsers | ForEach-Object { + $accountType = if ($_.HybridUser) { "Hybrid" } else { "Cloud-Only" } + $missingLicenses = $validLicenses | Where-Object { $_ -notin ($_.Licenses -split '\|') } + "$($_.UserName)|$($_.Roles)|$accountType|$($missingLicenses -join ',')" + } + $failureReasons = $failureReasons -join "`n" - $result = $nonCompliantUsers.Count -eq 0 - $status = if ($result) { 'Pass' } else { 'Fail' } - $failureReason = if ($nonCompliantUsers) { "Non-compliant accounts: `nUsername | Roles | HybridStatus | Missing Licence`n$failureReasons" } else { "N/A" } + $details = if ($nonCompliantUsers) { + "Non-compliant accounts: `nUsername | Roles | HybridStatus | Missing Licence`n$failureReasons" + } else { + "Compliant Accounts: $($uniqueAdminRoleUsers.Count)" + } - # Create the parameter splat - $params = @{ - Rec = "1.1.1" - Result = $result - Status = $status - Details = $details - FailureReason = $failureReason + $failureReason = if ($nonCompliantUsers) { + "Non-Compliant Accounts: $($nonCompliantUsers.Count)`nDetails:`n" + ($nonCompliantUsers | ForEach-Object { $_.UserName }) -join "`n" + } else { + "N/A" + } + + $result = $nonCompliantUsers.Count -eq 0 + $status = if ($result) { 'Pass' } else { 'Fail' } + + $params = @{ + Rec = $recnum + Result = $result + Status = $status + Details = $details + FailureReason = $failureReason + } + + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $auditResult = Initialize-CISAuditResult @params + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } - end { - # Output the result return $auditResult } } diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 88b103e..4e4de8c 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -1,5 +1,6 @@ function Test-AntiPhishingPolicy { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -10,66 +11,82 @@ function Test-AntiPhishingPolicy { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed #$auditResults = @() + $recnum = "2.1.7" } process { - # 2.1.7 Ensure that an anti-phishing policy has been created - # Retrieve and validate the anti-phishing policies - $antiPhishPolicies = Get-AntiPhishPolicy - $validatedPolicies = $antiPhishPolicies | Where-Object { - $_.Enabled -eq $true -and - $_.PhishThresholdLevel -ge 2 -and - $_.EnableMailboxIntelligenceProtection -eq $true -and - $_.EnableMailboxIntelligence -eq $true -and - $_.EnableSpoofIntelligence -eq $true - } + try { + # 2.1.7 Ensure that an anti-phishing policy has been created - # Check if there is at least one policy that meets the requirements - $nonCompliantItems = $antiPhishPolicies | Where-Object { - $_.Enabled -ne $true -or - $_.PhishThresholdLevel -lt 2 -or - $_.EnableMailboxIntelligenceProtection -ne $true -or - $_.EnableMailboxIntelligence -ne $true -or - $_.EnableSpoofIntelligence -ne $true - } - $compliantItems = $validatedPolicies - $isCompliant = $compliantItems.Count -gt 0 + # Retrieve and validate the anti-phishing policies + $antiPhishPolicies = Get-AntiPhishPolicy + $validatedPolicies = $antiPhishPolicies | Where-Object { + $_.Enabled -eq $true -and + $_.PhishThresholdLevel -ge 2 -and + $_.EnableMailboxIntelligenceProtection -eq $true -and + $_.EnableMailboxIntelligence -eq $true -and + $_.EnableSpoofIntelligence -eq $true + } - # Prepare failure reasons for non-compliant items - $nonCompliantNames = $nonCompliantItems | ForEach-Object { $_.Name } - $failureReasons = if ($nonCompliantNames.Count -gt 0) { - "Reason: Does not meet one or more compliance criteria.`nNon-compliant Policies:`n" + ($nonCompliantNames -join "`n") - } - else { - "N/A" - } + # Check if there is at least one policy that meets the requirements + $nonCompliantItems = $antiPhishPolicies | Where-Object { + $_.Enabled -ne $true -or + $_.PhishThresholdLevel -lt 2 -or + $_.EnableMailboxIntelligenceProtection -ne $true -or + $_.EnableMailboxIntelligence -ne $true -or + $_.EnableSpoofIntelligence -ne $true + } + $compliantItems = $validatedPolicies + $isCompliant = $compliantItems.Count -gt 0 - # Prepare details for non-compliant items - $nonCompliantDetails = $nonCompliantItems | ForEach-Object { - "Policy: $($_.Name)" - } - $nonCompliantDetails = $nonCompliantDetails -join "`n" + # Prepare failure reasons for non-compliant items + $nonCompliantNames = $nonCompliantItems | ForEach-Object { $_.Name } + $failureReasons = if ($nonCompliantNames.Count -gt 0) { + "Reason: Does not meet one or more compliance criteria.`nNon-compliant Policies:`n" + ($nonCompliantNames -join "`n") + } + else { + "N/A" + } - # Prepare details based on compliance - $details = if ($nonCompliantItems) { - "Non-Compliant Items: $($nonCompliantItems.Count)`nDetails:`n$nonCompliantDetails" - } - else { - "Compliant Items: $($compliantItems.Count)" - } + # Prepare details for non-compliant items + $nonCompliantDetails = $nonCompliantItems | ForEach-Object { + "Policy: $($_.Name)" + } + $nonCompliantDetails = $nonCompliantDetails -join "`n" - # Parameter splat for Initialize-CISAuditResult function - $params = @{ - Rec = "2.1.7" - Result = $nonCompliantItems.Count -eq 0 - Status = if ($isCompliant) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons - } + # Prepare details based on compliance + $details = if ($nonCompliantItems) { + "Non-Compliant Items: $($nonCompliantItems.Count)`nDetails:`n$nonCompliantDetails" + } + else { + "Compliant Items: $($compliantItems.Count)" + } - # Create and populate the CISAuditResult object - $auditResult = Initialize-CISAuditResult @params + # Parameter splat for Initialize-CISAuditResult function + $params = @{ + Rec = $recnum + Result = $nonCompliantItems.Count -eq 0 + Status = if ($isCompliant) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + + # Create and populate the CISAuditResult object + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-AuditDisabledFalse.ps1 b/source/tests/Test-AuditDisabledFalse.ps1 index 3f91a83..8a26323 100644 --- a/source/tests/Test-AuditDisabledFalse.ps1 +++ b/source/tests/Test-AuditDisabledFalse.ps1 @@ -1,5 +1,6 @@ function Test-AuditDisabledFalse { [CmdletBinding()] + [OutputType([CISAuditResult])] # Aligned param ( # Parameters can be added if needed @@ -9,39 +10,55 @@ function Test-AuditDisabledFalse { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.1.1" } process { - # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' - # Retrieve the AuditDisabled configuration - $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled - $auditNotDisabled = -not $auditDisabledConfig.AuditDisabled + try { + # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $auditNotDisabled) { - "AuditDisabled is set to True" - } - else { - "N/A" - } + # Retrieve the AuditDisabled configuration + $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled + $auditNotDisabled = -not $auditDisabledConfig.AuditDisabled - $details = if ($auditNotDisabled) { - "Audit is not disabled organizationally" - } - else { - "Audit is disabled organizationally" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $auditNotDisabled) { + "AuditDisabled is set to True" + } + else { + "N/A" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.1.1" - Result = $auditNotDisabled - Status = if ($auditNotDisabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $details = if ($auditNotDisabled) { + "Audit is not disabled organizationally" + } + else { + "Audit is disabled organizationally" + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $auditNotDisabled + Status = if ($auditNotDisabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-AuditLogSearch.ps1 b/source/tests/Test-AuditLogSearch.ps1 index 023f569..bbedbca 100644 --- a/source/tests/Test-AuditLogSearch.ps1 +++ b/source/tests/Test-AuditLogSearch.ps1 @@ -1,5 +1,6 @@ function Test-AuditLogSearch { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,40 +10,55 @@ function Test-AuditLogSearch { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "3.1.1" } process { - # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - # Retrieve the audit log configuration - $auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled - $auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled + try { + # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $auditLogResult) { - "Audit log search is not enabled" - } - else { - "N/A" - } + # Retrieve the audit log configuration + $auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled + $auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled - $details = if ($auditLogResult) { - "UnifiedAuditLogIngestionEnabled: True" - } - else { - "UnifiedAuditLogIngestionEnabled: False" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $auditLogResult) { + "Audit log search is not enabled" + } + else { + "N/A" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "3.1.1" - Result = $auditLogResult - Status = if ($auditLogResult) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons - } - $auditResult = Initialize-CISAuditResult @params + $details = if ($auditLogResult) { + "UnifiedAuditLogIngestionEnabled: True" + } + else { + "UnifiedAuditLogIngestionEnabled: False" + } + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $auditLogResult + Status = if ($auditLogResult) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-BlockChannelEmails.ps1 b/source/tests/Test-BlockChannelEmails.ps1 index c8ed086..5165b61 100644 --- a/source/tests/Test-BlockChannelEmails.ps1 +++ b/source/tests/Test-BlockChannelEmails.ps1 @@ -1,5 +1,6 @@ function Test-BlockChannelEmails { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added here if needed @@ -9,9 +10,12 @@ function Test-BlockChannelEmails { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.1.2" } process { + + try { # 8.1.2 (L1) Ensure users can't send emails to a channel email address # Retrieve Teams client configuration @@ -35,13 +39,26 @@ function Test-BlockChannelEmails { # Create and populate the CISAuditResult object $params = @{ - Rec = "8.1.2" + Rec = $recnum Result = -not $allowEmailIntoChannel Status = if (-not $allowEmailIntoChannel) { "Pass" } else { "Fail" } Details = $details FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-BlockMailForwarding.ps1 b/source/tests/Test-BlockMailForwarding.ps1 index 8039d06..0ea1c1b 100644 --- a/source/tests/Test-BlockMailForwarding.ps1 +++ b/source/tests/Test-BlockMailForwarding.ps1 @@ -1,7 +1,7 @@ function Test-BlockMailForwarding { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( - # Aligned Compare # Parameters can be added if needed ) @@ -9,40 +9,78 @@ function Test-BlockMailForwarding { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.2.1" } process { - # 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled + try { + # 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled - # Retrieve the transport rules that redirect messages - $transportRules = Get-TransportRule | Where-Object { $null -ne $_.RedirectMessageTo } - $forwardingBlocked = $transportRules.Count -eq 0 + # Step 1: Retrieve the transport rules that redirect messages + $transportRules = Get-TransportRule | Where-Object { $null -ne $_.RedirectMessageTo } + $transportForwardingBlocked = $transportRules.Count -eq 0 - # Prepare failure reasons and details based on compliance - $failureReasons = if ($transportRules.Count -gt 0) { - "Mail forwarding rules found: $($transportRules.Name -join ', ')" - } - else { - "N/A" - } + # Step 2: Check all anti-spam outbound policies + $outboundSpamPolicies = Get-HostedOutboundSpamFilterPolicy + $nonCompliantSpamPolicies = $outboundSpamPolicies | Where-Object { $_.AutoForwardingMode -ne 'Off' } + $nonCompliantSpamPoliciesArray = @($nonCompliantSpamPolicies) + $spamForwardingBlocked = $nonCompliantSpamPoliciesArray.Count -eq 0 - $details = if ($transportRules.Count -gt 0) { - $transportRules | ForEach-Object { - "$($_.Name) redirects to $($_.RedirectMessageTo)" - } -join " | " - } - else { - "Step 1: No forwarding rules found. Please proceed with Step 2 described in CIS Benchmark." - } + # Determine overall compliance + $forwardingBlocked = $transportForwardingBlocked -and $spamForwardingBlocked - $params = @{ - Rec = "6.2.1" - Result = $forwardingBlocked - Status = if ($forwardingBlocked) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Prepare failure reasons and details based on compliance + $failureReasons = @() + $details = @() + + if ($transportRules.Count -gt 0) { + $failureReasons += "Mail forwarding rules found: $($transportRules.Name -join ', ')" + $details += "Transport Rules Details:`nRule Name|Redirects To" + $details += $transportRules | ForEach-Object { + "$($_.Name)|$($_.RedirectMessageTo -join ', ')" + } + $details += "`n" + } + + if ($nonCompliantSpamPoliciesArray.Count -gt 0) { + $failureReasons += "Outbound spam policies allowing automatic forwarding found." + $details += "Outbound Spam Policies Details:`nPolicy|AutoForwardingMode" + $details += $nonCompliantSpamPoliciesArray | ForEach-Object { + "$($_.Name)|$($_.AutoForwardingMode)" + } + } + + if ($failureReasons.Count -eq 0) { + $failureReasons = "N/A" + $details = "Both transport rules and outbound spam policies are configured correctly to block forwarding." + } + else { + $failureReasons = $failureReasons -join " | " + $details = $details -join "`n" + } + + # Populate the audit result + $params = @{ + Rec = $recnum + Result = $forwardingBlocked + Status = if ($forwardingBlocked) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index 7f4711b..d1af582 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -1,5 +1,6 @@ function Test-BlockSharedMailboxSignIn { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,41 +10,57 @@ function Test-BlockSharedMailboxSignIn { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.2.2" } process { - # 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked - # Retrieve shared mailbox details - $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox - $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } - $enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName } - $allBlocked = $enabledMailboxes.Count -eq 0 + try { + # 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $allBlocked) { - "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" - } - else { - "N/A" - } + # Retrieve shared mailbox details + $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox + $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } + $enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName } + $allBlocked = $enabledMailboxes.Count -eq 0 - $details = if ($allBlocked) { - "All shared mailboxes have sign-in blocked." - } - else { - "Enabled Mailboxes: $($enabledMailboxes -join ', ')" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $allBlocked) { + "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" + } + else { + "N/A" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.2.2" - Result = $allBlocked - Status = if ($allBlocked) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $details = if ($allBlocked) { + "All shared mailboxes have sign-in blocked." + } + else { + "Enabled Mailboxes: $($enabledMailboxes -join ', ')" + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $allBlocked + Status = if ($allBlocked) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 9b60797..3341ca5 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -1,5 +1,6 @@ function Test-CommonAttachmentFilter { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,39 +10,54 @@ function Test-CommonAttachmentFilter { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.2" } process { - # 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled + try { + # 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled - # Retrieve the attachment filter policy - $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter - $result = $attachmentFilter.EnableFileFilter + # Retrieve the attachment filter policy + $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter + $result = $attachmentFilter.EnableFileFilter - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $result) { - "Common Attachment Types Filter is disabled" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $result) { + "Common Attachment Types Filter is disabled" + } + else { + "N/A" + } - $details = if ($result) { - "File Filter Enabled: True" - } - else { - "File Filter Enabled: False" - } + $details = if ($result) { + "File Filter Enabled: True" + } + else { + "File Filter Enabled: False" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "2.1.2" - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index c0723f9..8396ffc 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -1,5 +1,6 @@ function Test-CustomerLockbox { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -9,9 +10,12 @@ function Test-CustomerLockbox { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.3.6" } process { + + try { # 1.3.6 (L2) Ensure the customer lockbox feature is enabled # Retrieve the organization configuration @@ -35,13 +39,26 @@ function Test-CustomerLockbox { # Create and populate the CISAuditResult object # $params = @{ - Rec = "1.3.6" + Rec = $recnum Result = $customerLockboxEnabled Status = if ($customerLockboxEnabled) { "Pass" } else { "Fail" } Details = $details FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-DialInBypassLobby.ps1 b/source/tests/Test-DialInBypassLobby.ps1 index dd2a47b..b7088ee 100644 --- a/source/tests/Test-DialInBypassLobby.ps1 +++ b/source/tests/Test-DialInBypassLobby.ps1 @@ -1,5 +1,6 @@ function Test-DialInBypassLobby { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,39 +10,55 @@ function Test-DialInBypassLobby { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.4" } process { - # 8.5.4 (L1) Ensure users dialing in can't bypass the lobby - # Retrieve Teams meeting policy for PSTN users - $CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby - $PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby + try { + # 8.5.4 (L1) Ensure users dialing in can't bypass the lobby - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $PSTNBypassDisabled) { - "Users dialing in can bypass the lobby" - } - else { - "N/A" - } + # Retrieve Teams meeting policy for PSTN users + $CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby + $PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby - $details = if ($PSTNBypassDisabled) { - "AllowPSTNUsersToBypassLobby is set to False" - } - else { - "AllowPSTNUsersToBypassLobby is set to True" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $PSTNBypassDisabled) { + "Users dialing in can bypass the lobby" + } + else { + "N/A" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.4" - Result = $PSTNBypassDisabled - Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $details = if ($PSTNBypassDisabled) { + "AllowPSTNUsersToBypassLobby is set to False" + } + else { + "AllowPSTNUsersToBypassLobby is set to True" + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $PSTNBypassDisabled + Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-DisallowInfectedFilesDownload.ps1 b/source/tests/Test-DisallowInfectedFilesDownload.ps1 index 3ce9b3a..514dda7 100644 --- a/source/tests/Test-DisallowInfectedFilesDownload.ps1 +++ b/source/tests/Test-DisallowInfectedFilesDownload.ps1 @@ -1,5 +1,6 @@ function Test-DisallowInfectedFilesDownload { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -10,40 +11,55 @@ function Test-DisallowInfectedFilesDownload { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.3.1" } process { - # 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download - # Retrieve the SharePoint tenant configuration - $SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload - $isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload + try { + # 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isDisallowInfectedFileDownloadEnabled) { - "Downloading infected files is not disallowed." - } - else { - "N/A" - } + # Retrieve the SharePoint tenant configuration + $SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload + $isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload - $details = if ($isDisallowInfectedFileDownloadEnabled) { - "DisallowInfectedFileDownload: True" - } - else { - "DisallowInfectedFileDownload: False" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isDisallowInfectedFileDownloadEnabled) { + "Downloading infected files is not disallowed." + } + else { + "N/A" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.3.1" - Result = $isDisallowInfectedFileDownloadEnabled - Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons - } - $auditResult = Initialize-CISAuditResult @params + $details = if ($isDisallowInfectedFileDownloadEnabled) { + "DisallowInfectedFileDownload: True" + } + else { + "DisallowInfectedFileDownload: False" + } + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isDisallowInfectedFileDownloadEnabled + Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index db7bac6..a6662e1 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -1,5 +1,6 @@ function Test-EnableDKIM { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,9 +10,12 @@ function Test-EnableDKIM { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.9" } process { + + try { # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains # Retrieve DKIM configuration for all domains @@ -36,13 +40,26 @@ function Test-EnableDKIM { # Create and populate the CISAuditResult object $params = @{ - Rec = "2.1.9" + Rec = $recnum Result = $dkimResult Status = if ($dkimResult) { "Pass" } else { "Fail" } Details = $details FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-ExternalNoControl.ps1 b/source/tests/Test-ExternalNoControl.ps1 index 8d52da7..1a8c82d 100644 --- a/source/tests/Test-ExternalNoControl.ps1 +++ b/source/tests/Test-ExternalNoControl.ps1 @@ -1,5 +1,6 @@ function Test-ExternalNoControl { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -10,9 +11,12 @@ function Test-ExternalNoControl { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.7" } process { + + try { # 8.5.7 (L1) Ensure external participants can't give or request control # Retrieve Teams meeting policy for external participant control @@ -36,13 +40,26 @@ function Test-ExternalNoControl { # Create and populate the CISAuditResult object $params = @{ - Rec = "8.5.7" + Rec = $recnum Result = $externalControlRestricted Status = if ($externalControlRestricted) { "Pass" } else { "Fail" } Details = $details FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index 9053bbb..ed5e941 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -1,5 +1,6 @@ function Test-ExternalSharingCalendars { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -10,48 +11,64 @@ function Test-ExternalSharingCalendars { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.3.3" } process { - # 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated) - # Retrieve sharing policies related to calendar sharing - $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } + try { + # 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated) - # Check if calendar sharing is disabled in all applicable policies - $isExternalSharingDisabled = $true - $sharingPolicyDetails = @() - foreach ($policy in $sharingPolicies) { - if ($policy.Enabled -eq $true) { - $isExternalSharingDisabled = $false - $sharingPolicyDetails += "$($policy.Name): Enabled" + # Retrieve sharing policies related to calendar sharing + $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } + + # Check if calendar sharing is disabled in all applicable policies + $isExternalSharingDisabled = $true + $sharingPolicyDetails = @() + foreach ($policy in $sharingPolicies) { + if ($policy.Enabled -eq $true) { + $isExternalSharingDisabled = $false + $sharingPolicyDetails += "$($policy.Name): Enabled" + } } - } - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isExternalSharingDisabled) { - "Calendar sharing with external users is enabled in one or more policies." - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isExternalSharingDisabled) { + "Calendar sharing with external users is enabled in one or more policies." + } + else { + "N/A" + } - $details = if ($isExternalSharingDisabled) { - "Calendar sharing with external users is disabled." - } - else { - "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" - } + $details = if ($isExternalSharingDisabled) { + "Calendar sharing with external users is disabled." + } + else { + "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.3.3" - Result = $isExternalSharingDisabled - Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isExternalSharingDisabled + Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-GlobalAdminsCount.ps1 b/source/tests/Test-GlobalAdminsCount.ps1 index c57110f..0c71f28 100644 --- a/source/tests/Test-GlobalAdminsCount.ps1 +++ b/source/tests/Test-GlobalAdminsCount.ps1 @@ -1,5 +1,6 @@ function Test-GlobalAdminsCount { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -10,39 +11,56 @@ function Test-GlobalAdminsCount { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.1.3" } process { - # 1.1.3 (L1) Ensure that between two and four global admins are designated + try { + # 1.1.3 (L1) Ensure that between two and four global admins are designated - # Retrieve global admin role and members - $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" - $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id - $globalAdminCount = $globalAdmins.AdditionalProperties.Count - $globalAdminUsernames = ($globalAdmins | ForEach-Object { $_.AdditionalProperties["displayName"] }) -join ', ' + # Retrieve global admin role and members + $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" + $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id + $globalAdminCount = $globalAdmins.Count + $globalAdminUsernames = ($globalAdmins | ForEach-Object { + "$($_.AdditionalProperties["displayName"]) ($($_.AdditionalProperties["userPrincipalName"]))" + }) -join ', ' - # Prepare failure reasons and details based on compliance - $failureReasons = if ($globalAdminCount -lt 2) { - "Less than 2 global admins: $globalAdminUsernames" - } - elseif ($globalAdminCount -gt 4) { - "More than 4 global admins: $globalAdminUsernames" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($globalAdminCount -lt 2) { + "Less than 2 global admins: $globalAdminUsernames" + } + elseif ($globalAdminCount -gt 4) { + "More than 4 global admins: $globalAdminUsernames" + } + else { + "N/A" + } - $details = "Count: $globalAdminCount; Users: $globalAdminUsernames" + $details = "Count: $globalAdminCount; Users: $globalAdminUsernames" - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.1.3" - Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4 - Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4 + Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-GuestAccessExpiration.ps1 b/source/tests/Test-GuestAccessExpiration.ps1 index 4a0b2b6..986c8d7 100644 --- a/source/tests/Test-GuestAccessExpiration.ps1 +++ b/source/tests/Test-GuestAccessExpiration.ps1 @@ -1,5 +1,6 @@ function Test-GuestAccessExpiration { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -10,9 +11,12 @@ function Test-GuestAccessExpiration { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.9" } process { + + try { # 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically # Retrieve SharePoint tenant settings related to guest access expiration @@ -31,13 +35,26 @@ function Test-GuestAccessExpiration { # Create and populate the CISAuditResult object $params = @{ - Rec = "7.2.9" + Rec = $recnum Result = $isGuestAccessExpirationConfiguredCorrectly Status = if ($isGuestAccessExpirationConfiguredCorrectly) { "Pass" } else { "Fail" } Details = $details FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } } end { diff --git a/source/tests/Test-GuestUsersBiweeklyReview.ps1 b/source/tests/Test-GuestUsersBiweeklyReview.ps1 index 730f039..757c4d3 100644 --- a/source/tests/Test-GuestUsersBiweeklyReview.ps1 +++ b/source/tests/Test-GuestUsersBiweeklyReview.ps1 @@ -1,5 +1,6 @@ function Test-GuestUsersBiweeklyReview { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -10,41 +11,56 @@ function Test-GuestUsersBiweeklyReview { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.1.4" } process { - # 1.1.4 (L1) Ensure Guest Users are reviewed at least biweekly + try { + # 1.1.4 (L1) Ensure Guest Users are reviewed at least biweekly - # Retrieve guest users from Microsoft Graph - # Connect-MgGraph -Scopes "User.Read.All" - $guestUsers = Get-MgUser -All -Filter "UserType eq 'Guest'" + # Retrieve guest users from Microsoft Graph + # Connect-MgGraph -Scopes "User.Read.All" + $guestUsers = Get-MgUser -All -Filter "UserType eq 'Guest'" - # Prepare failure reasons and details based on compliance - $failureReasons = if ($guestUsers) { - "Guest users present: $($guestUsers.Count)" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($guestUsers) { + "Guest users present: $($guestUsers.Count)" + } + else { + "N/A" + } - $details = if ($guestUsers) { - $auditCommand = "Get-MgUser -All -Property UserType,UserPrincipalName | Where {`$_.UserType -ne 'Member'} | Format-Table UserPrincipalName, UserType" - "Manual review required. To list guest users, run: `"$auditCommand`"." - } - else { - "No guest users found." - } + $details = if ($guestUsers) { + $auditCommand = "Get-MgUser -All -Property UserType,UserPrincipalName | Where {`$_.UserType -ne 'Member'} | Format-Table UserPrincipalName, UserType" + "Manual review required. To list guest users, run: `"$auditCommand`"." + } + else { + "No guest users found." + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.1.4" - Result = -not $guestUsers - Status = if ($guestUsers) { "Fail" } else { "Pass" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = -not $guestUsers + Status = if ($guestUsers) { "Fail" } else { "Pass" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-IdentifyExternalEmail.ps1 b/source/tests/Test-IdentifyExternalEmail.ps1 index 623d986..63bce34 100644 --- a/source/tests/Test-IdentifyExternalEmail.ps1 +++ b/source/tests/Test-IdentifyExternalEmail.ps1 @@ -1,5 +1,6 @@ function Test-IdentifyExternalEmail { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -10,34 +11,50 @@ function Test-IdentifyExternalEmail { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.2.3" } process { - # 6.2.3 (L1) Ensure email from external senders is identified - # Retrieve external sender tagging configuration - $externalInOutlook = Get-ExternalInOutlook - $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true + try { + # 6.2.3 (L1) Ensure email from external senders is identified - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $externalTaggingEnabled) { - "External sender tagging is disabled" - } - else { - "N/A" + # Retrieve external sender tagging configuration + $externalInOutlook = Get-ExternalInOutlook + $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true + + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $externalTaggingEnabled) { + "External sender tagging is disabled" + } + else { + "N/A" + } + + $details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $externalTaggingEnabled + Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.2.3" - Result = $externalTaggingEnabled - Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-LinkSharingRestrictions.ps1 b/source/tests/Test-LinkSharingRestrictions.ps1 index b117da3..24cd22b 100644 --- a/source/tests/Test-LinkSharingRestrictions.ps1 +++ b/source/tests/Test-LinkSharingRestrictions.ps1 @@ -1,5 +1,6 @@ function Test-LinkSharingRestrictions { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -10,34 +11,50 @@ function Test-LinkSharingRestrictions { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.7" } process { - # 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive + try { + # 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive - # Retrieve link sharing configuration for SharePoint and OneDrive - $SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType - $isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation + # Retrieve link sharing configuration for SharePoint and OneDrive + $SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType + $isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation + + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isLinkSharingRestricted) { + "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" + } + else { + "N/A" + } + + $details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isLinkSharingRestricted + Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isLinkSharingRestricted) { - "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" - } - else { - "N/A" } + catch { + Write-Error "An error occurred during the test: $_" - $details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.2.7" - Result = $isLinkSharingRestricted - Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-MailTipsEnabled.ps1 b/source/tests/Test-MailTipsEnabled.ps1 index 12224c0..2ccf106 100644 --- a/source/tests/Test-MailTipsEnabled.ps1 +++ b/source/tests/Test-MailTipsEnabled.ps1 @@ -1,5 +1,6 @@ function Test-MailTipsEnabled { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -11,40 +12,55 @@ function Test-MailTipsEnabled { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "6.5.2" } process { - # 6.5.2 (L2) Ensure MailTips are enabled for end users + try { + # 6.5.2 (L2) Ensure MailTips are enabled for end users - # Retrieve organization configuration for MailTips settings - $orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold - $allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25 - $externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled + # Retrieve organization configuration for MailTips settings + $orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold + $allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25 + $externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not ($allTipsEnabled -and $externalRecipientsTipsEnabled)) { - "One or more MailTips settings are not configured as required." - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not ($allTipsEnabled -and $externalRecipientsTipsEnabled)) { + "One or more MailTips settings are not configured as required." + } + else { + "N/A" + } - $details = if ($allTipsEnabled -and $externalRecipientsTipsEnabled) { - "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)" - } - else { - "One or more MailTips settings are not configured as required." - } + $details = if ($allTipsEnabled -and $externalRecipientsTipsEnabled) { + "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)" + } + else { + "One or more MailTips settings are not configured as required." + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.5.2" - Result = $allTipsEnabled -and $externalRecipientsTipsEnabled - Status = if ($allTipsEnabled -and $externalRecipientsTipsEnabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $allTipsEnabled -and $externalRecipientsTipsEnabled + Status = if ($allTipsEnabled -and $externalRecipientsTipsEnabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 79373b7..ca9efcd 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -1,8 +1,7 @@ function Test-MailboxAuditingE3 { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( - # Aligned - # Create Table for Details # Parameters can be added if needed ) @@ -15,19 +14,20 @@ function Test-MailboxAuditingE3 { $DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules") $OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") - $allFailures = @() $allUsers = Get-AzureADUser -All $true $processedUsers = @{} # Dictionary to track processed users + $recnum = "6.1.2" } process { - foreach ($user in $allUsers) { - if ($processedUsers.ContainsKey($user.UserPrincipalName)) { - Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" - continue - } - try { + try { + foreach ($user in $allUsers) { + if ($processedUsers.ContainsKey($user.UserPrincipalName)) { + Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" + continue + } + $licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName $hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0 Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license." @@ -47,68 +47,63 @@ function Test-MailboxAuditingE3 { foreach ($action in $OwnerActions) { if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" } } + + if ($missingActions.Count -gt 0) { + $formattedActions = Format-MissingAction -missingActions $missingActions + $allFailures += "$userUPN|True|$($formattedActions.Admin)|$($formattedActions.Delegate)|$($formattedActions.Owner)" + } } else { - $allFailures += "$userUPN`: AuditEnabled - False" - continue + $allFailures += "$userUPN|False|||" } - if ($missingActions) { - $formattedActions = Format-MissingActions $missingActions - $allFailures += "$userUPN`: AuditEnabled - True; $formattedActions" - } # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } } - catch { - Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_" + + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } + $details = if ($allFailures.Count -eq 0) { + "All Office E3 users have correct mailbox audit settings." + } + else { + "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") } - } - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } - $details = if ($allFailures.Count -eq 0) { "All Office E3 users have correct mailbox audit settings." } else { $allFailures -join " | " } - - # Populate the audit result - $params = @{ - Rec = "6.1.2" - Result = $allFailures.Count -eq 0 - Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Populate the audit result + $params = @{ + Rec = $recnum + Result = $allFailures.Count -eq 0 + Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { + #$verbosePreference = 'Continue' + $detailsLength = $details.Length + Write-Verbose "Character count of the details: $detailsLength" + + if ($detailsLength -gt 32767) { + Write-Verbose "Warning: The character count exceeds the limit for Excel cells." + } + #$verbosePreference = 'SilentlyContinue' return $auditResult } } - -function Format-MissingActions { - param ([array]$missingActions) - - $actionGroups = @{ - "Admin" = @() - "Delegate" = @() - "Owner" = @() - } - - foreach ($action in $missingActions) { - if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") { - $type = $matches[1] - $actionName = $matches[2] - $actionGroups[$type] += $actionName - } - } - - $formattedResults = @() - foreach ($type in $actionGroups.Keys) { - if ($actionGroups[$type].Count -gt 0) { - $formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')" - } - } - - return $formattedResults -join '; ' -} diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index 8f6796c..b21f6a2 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -1,8 +1,7 @@ function Test-MailboxAuditingE5 { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( - # Aligned - # Create Table for Details # Parameters can be added if needed ) @@ -15,23 +14,24 @@ function Test-MailboxAuditingE5 { $DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules") $OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") - - $allFailures = @() $allUsers = Get-AzureADUser -All $true $processedUsers = @{} # Dictionary to track processed users + $recnum = "6.1.3" } process { - foreach ($user in $allUsers) { - if ($processedUsers.ContainsKey($user.UserPrincipalName)) { - continue - } + try { + foreach ($user in $allUsers) { + if ($processedUsers.ContainsKey($user.UserPrincipalName)) { + Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" + continue + } - try { $licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName $hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0 Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license." + if ($hasOfficeE5) { $userUPN = $user.UserPrincipalName $mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit @@ -47,74 +47,63 @@ function Test-MailboxAuditingE5 { foreach ($action in $OwnerActions) { if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" } } + + if ($missingActions.Count -gt 0) { + $formattedActions = Format-MissingAction -missingActions $missingActions + $allFailures += "$userUPN|True|$($formattedActions.Admin)|$($formattedActions.Delegate)|$($formattedActions.Owner)" + } } else { - $allFailures += "$userUPN`: AuditEnabled - False" - continue + $allFailures += "$userUPN|False|||" } - if ($missingActions) { - $formattedActions = Format-MissingActions $missingActions - $allFailures += "$userUPN`: AuditEnabled - True; $formattedActions" - } - else { - Write-Verbose "User $($user.UserPrincipalName) passed the mailbox audit checks." - } + # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } - else { - # Adding verbose output to indicate the user does not have an E5 license - Write-Verbose "User $($user.UserPrincipalName) does not have an Office E5 license." - } } - catch { - Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_" + + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } + $details = if ($allFailures.Count -eq 0) { + "All Office E5 users have correct mailbox audit settings." + } + else { + "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") } - } - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } - $details = if ($allFailures.Count -eq 0) { "All Office E5 users have correct mailbox audit settings." } else { $allFailures -join " | " } - - # Populate the audit result - $params = @{ - Rec = "6.1.3" - Result = $allFailures.Count -eq 0 - Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Populate the audit result + $params = @{ + Rec = $recnum + Result = $allFailures.Count -eq 0 + Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { + #$verbosePreference = 'Continue' + $detailsLength = $details.Length + Write-Verbose "Character count of the details: $detailsLength" + + if ($detailsLength -gt 32767) { + Write-Verbose "Warning: The character count exceeds the limit for Excel cells." + } + #$verbosePreference = 'SilentlyContinue' return $auditResult } } - -function Format-MissingActions { - param ([array]$missingActions) - - $actionGroups = @{ - "Admin" = @() - "Delegate" = @() - "Owner" = @() - } - - foreach ($action in $missingActions) { - if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") { - $type = $matches[1] - $actionName = $matches[2] - $actionGroups[$type] += $actionName - } - } - - $formattedResults = @() - foreach ($type in $actionGroups.Keys) { - if ($actionGroups[$type].Count -gt 0) { - $formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')" - } - } - - return $formattedResults -join '; ' -} diff --git a/source/tests/Test-ManagedApprovedPublicGroups.ps1 b/source/tests/Test-ManagedApprovedPublicGroups.ps1 index 80fb4fa..073d540 100644 --- a/source/tests/Test-ManagedApprovedPublicGroups.ps1 +++ b/source/tests/Test-ManagedApprovedPublicGroups.ps1 @@ -1,5 +1,6 @@ function Test-ManagedApprovedPublicGroups { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,40 +10,54 @@ function Test-ManagedApprovedPublicGroups { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed - + $recnum = "1.2.1" } process { - # 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated) + try { + # 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated) - # Retrieve all public groups - $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility + # Retrieve all public groups + $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility - # Prepare failure reasons and details based on compliance - $failureReasons = if ($null -ne $allGroups -and $allGroups.Count -gt 0) { - "There are public groups present that are not organizationally managed/approved." - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($null -ne $allGroups -and $allGroups.Count -gt 0) { + "There are public groups present that are not organizationally managed/approved." + } + else { + "N/A" + } - $details = if ($null -eq $allGroups -or $allGroups.Count -eq 0) { - "No public groups found." - } - else { - $groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" } - "Public groups found: $($groupDetails -join ', ')" - } + $details = if ($null -eq $allGroups -or $allGroups.Count -eq 0) { + "No public groups found." + } + else { + $groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" } + "Public groups found: $($groupDetails -join ', ')" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.2.1" - Result = $null -eq $allGroups -or $allGroups.Count -eq 0 - Status = if ($null -eq $allGroups -or $allGroups.Count -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $null -eq $allGroups -or $allGroups.Count -eq 0 + Status = if ($null -eq $allGroups -or $allGroups.Count -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-MeetingChatNoAnonymous.ps1 b/source/tests/Test-MeetingChatNoAnonymous.ps1 index 29428b1..3524c6b 100644 --- a/source/tests/Test-MeetingChatNoAnonymous.ps1 +++ b/source/tests/Test-MeetingChatNoAnonymous.ps1 @@ -1,5 +1,6 @@ function Test-MeetingChatNoAnonymous { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,36 +10,51 @@ function Test-MeetingChatNoAnonymous { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.5" } process { - # 8.5.5 (L2) Ensure meeting chat does not allow anonymous users + try { + # 8.5.5 (L2) Ensure meeting chat does not allow anonymous users - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - # Retrieve the Teams meeting policy for meeting chat - $CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType - $chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous' + # Retrieve the Teams meeting policy for meeting chat + $CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType + $chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous' - # Prepare failure reasons and details based on compliance - $failureReasons = if ($chatAnonDisabled) { - "N/A" - } - else { - "Meeting chat allows anonymous users" + # Prepare failure reasons and details based on compliance + $failureReasons = if ($chatAnonDisabled) { + "N/A" + } + else { + "Meeting chat allows anonymous users" + } + + $details = "MeetingChatEnabledType is set to $($CsTeamsMeetingPolicyChat.MeetingChatEnabledType)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $chatAnonDisabled + Status = if ($chatAnonDisabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "MeetingChatEnabledType is set to $($CsTeamsMeetingPolicyChat.MeetingChatEnabledType)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.5" - Result = $chatAnonDisabled - Status = if ($chatAnonDisabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-ModernAuthExchangeOnline.ps1 b/source/tests/Test-ModernAuthExchangeOnline.ps1 index 811679b..8735fa4 100644 --- a/source/tests/Test-ModernAuthExchangeOnline.ps1 +++ b/source/tests/Test-ModernAuthExchangeOnline.ps1 @@ -1,5 +1,6 @@ function Test-ModernAuthExchangeOnline { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,6 +10,7 @@ function Test-ModernAuthExchangeOnline { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.5.1" } process { @@ -30,18 +32,28 @@ function Test-ModernAuthExchangeOnline { # Create and populate the CISAuditResult object $params = @{ - Rec = "6.5.1" - Result = $orgConfig.OAuth2ClientProfileEnabled - Status = if ($orgConfig.OAuth2ClientProfileEnabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Rec = $recnum + Result = $orgConfig.OAuth2ClientProfileEnabled + Status = if ($orgConfig.OAuth2ClientProfileEnabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params } catch { - Write-Error "An error occurred while testing modern authentication for Exchange Online: $_" + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } + } end { diff --git a/source/tests/Test-ModernAuthSharePoint.ps1 b/source/tests/Test-ModernAuthSharePoint.ps1 index 3f7ae76..c56ef59 100644 --- a/source/tests/Test-ModernAuthSharePoint.ps1 +++ b/source/tests/Test-ModernAuthSharePoint.ps1 @@ -1,5 +1,6 @@ function Test-ModernAuthSharePoint { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,32 +10,47 @@ function Test-ModernAuthSharePoint { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.1" } process { - # 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required - $SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled - $modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled + try { + # 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required + $SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled + $modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $modernAuthForSPRequired) { - "Legacy authentication protocols are enabled" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $modernAuthForSPRequired) { + "Legacy authentication protocols are enabled" + } + else { + "N/A" + } - $details = "LegacyAuthProtocolsEnabled: $($SPOTenant.LegacyAuthProtocolsEnabled)" + $details = "LegacyAuthProtocolsEnabled: $($SPOTenant.LegacyAuthProtocolsEnabled)" - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.2.1" - Result = $modernAuthForSPRequired - Status = if ($modernAuthForSPRequired) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $modernAuthForSPRequired + Status = if ($modernAuthForSPRequired) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-NoAnonymousMeetingJoin.ps1 b/source/tests/Test-NoAnonymousMeetingJoin.ps1 index fbeb7dd..e28ebdb 100644 --- a/source/tests/Test-NoAnonymousMeetingJoin.ps1 +++ b/source/tests/Test-NoAnonymousMeetingJoin.ps1 @@ -1,5 +1,6 @@ function Test-NoAnonymousMeetingJoin { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,35 +10,50 @@ function Test-NoAnonymousMeetingJoin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.1" } process { - # 8.5.1 (L2) Ensure anonymous users can't join a meeting + try { + # 8.5.1 (L2) Ensure anonymous users can't join a meeting - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - $teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global - $allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting + $teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global + $allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allowAnonymousUsersToJoinMeeting) { - "Anonymous users are allowed to join meetings" - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allowAnonymousUsersToJoinMeeting) { + "Anonymous users are allowed to join meetings" + } + else { + "N/A" + } + + $details = "AllowAnonymousUsersToJoinMeeting is set to $allowAnonymousUsersToJoinMeeting" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = -not $allowAnonymousUsersToJoinMeeting + Status = if (-not $allowAnonymousUsersToJoinMeeting) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "AllowAnonymousUsersToJoinMeeting is set to $allowAnonymousUsersToJoinMeeting" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.1" - Result = -not $allowAnonymousUsersToJoinMeeting - Status = if (-not $allowAnonymousUsersToJoinMeeting) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-NoAnonymousMeetingStart.ps1 b/source/tests/Test-NoAnonymousMeetingStart.ps1 index 01c680f..5ffa6e2 100644 --- a/source/tests/Test-NoAnonymousMeetingStart.ps1 +++ b/source/tests/Test-NoAnonymousMeetingStart.ps1 @@ -1,5 +1,6 @@ function Test-NoAnonymousMeetingStart { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,35 +10,50 @@ function Test-NoAnonymousMeetingStart { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.2" } process { - # 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting + try { + # 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - $CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting - $anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting + $CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting + $anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting - # Prepare failure reasons and details based on compliance - $failureReasons = if ($anonymousStartDisabled) { - "N/A" - } - else { - "Anonymous users and dial-in callers can start a meeting" + # Prepare failure reasons and details based on compliance + $failureReasons = if ($anonymousStartDisabled) { + "N/A" + } + else { + "Anonymous users and dial-in callers can start a meeting" + } + + $details = "AllowAnonymousUsersToStartMeeting is set to $($CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $anonymousStartDisabled + Status = if ($anonymousStartDisabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "AllowAnonymousUsersToStartMeeting is set to $($CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.2" - Result = $anonymousStartDisabled - Status = if ($anonymousStartDisabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-NoWhitelistDomains.ps1 b/source/tests/Test-NoWhitelistDomains.ps1 index 820ff7b..d51fdc1 100644 --- a/source/tests/Test-NoWhitelistDomains.ps1 +++ b/source/tests/Test-NoWhitelistDomains.ps1 @@ -1,5 +1,6 @@ function Test-NoWhitelistDomains { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,39 +10,54 @@ function Test-NoWhitelistDomains { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.2.2" } process { - # 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains + try { + # 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains - # Retrieve transport rules that whitelist specific domains - $whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $null -ne $_.SenderDomainIs } + # Retrieve transport rules that whitelist specific domains + $whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $null -ne $_.SenderDomainIs } - # Prepare failure reasons and details based on compliance - $failureReasons = if ($whitelistedRules) { - "There are transport rules whitelisting specific domains." - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($whitelistedRules) { + "There are transport rules whitelisting specific domains." + } + else { + "N/A" + } - $details = if ($whitelistedRules) { - $ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') } - "Whitelisted Rules: $($ruleDetails -join '; ')" - } - else { - "No transport rules whitelisting specific domains found." - } + $details = if ($whitelistedRules) { + $ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') } + "Whitelisted Rules: $($ruleDetails -join '; ')" + } + else { + "No transport rules whitelisting specific domains found." + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.2.2" - Result = -not $whitelistedRules - Status = if ($whitelistedRules) { "Fail" } else { "Pass" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = -not $whitelistedRules + Status = if ($whitelistedRules) { "Fail" } else { "Pass" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index ba962d6..1e466fb 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -1,5 +1,6 @@ function Test-NotifyMalwareInternal { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,48 +10,63 @@ function Test-NotifyMalwareInternal { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.3" } process { - # 2.1.3 Ensure notifications for internal users sending malware is Enabled + try { + # 2.1.3 Ensure notifications for internal users sending malware is Enabled - # Retrieve all 'Custom' malware filter policies and check notification settings - $malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' } - $policiesToReport = @() + # Retrieve all 'Custom' malware filter policies and check notification settings + $malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' } + $policiesToReport = @() - foreach ($policy in $malwareNotifications) { - if ($policy.EnableInternalSenderAdminNotifications -ne $true) { - $policiesToReport += "$($policy.Identity): Notifications Disabled" + foreach ($policy in $malwareNotifications) { + if ($policy.EnableInternalSenderAdminNotifications -ne $true) { + $policiesToReport += "$($policy.Identity): Notifications Disabled" + } } - } - # Determine the result based on the presence of custom policies without notifications - $result = $policiesToReport.Count -eq 0 + # Determine the result based on the presence of custom policies without notifications + $result = $policiesToReport.Count -eq 0 - # Prepare failure reasons and details based on compliance - $failureReasons = if ($result) { - "N/A" - } - else { - "Some custom policies do not have notifications for internal users sending malware enabled." - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($result) { + "N/A" + } + else { + "Some custom policies do not have notifications for internal users sending malware enabled." + } - $details = if ($result) { - "All custom malware policies have notifications enabled." - } - else { - "Misconfigured Policies: $($policiesToReport -join ', ')" - } + $details = if ($result) { + "All custom malware policies have notifications enabled." + } + else { + "Misconfigured Policies: $($policiesToReport -join ', ')" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "2.1.3" - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-OneDriveContentRestrictions.ps1 b/source/tests/Test-OneDriveContentRestrictions.ps1 index 7a69c37..0043aa9 100644 --- a/source/tests/Test-OneDriveContentRestrictions.ps1 +++ b/source/tests/Test-OneDriveContentRestrictions.ps1 @@ -1,5 +1,6 @@ function Test-OneDriveContentRestrictions { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,39 +10,54 @@ function Test-OneDriveContentRestrictions { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.4" } process { - # 7.2.4 (L2) Ensure OneDrive content sharing is restricted + try { + # 7.2.4 (L2) Ensure OneDrive content sharing is restricted - # Retrieve OneDrive sharing capability settings - $SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability - $isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled' + # Retrieve OneDrive sharing capability settings + $SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability + $isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled' - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isOneDriveSharingRestricted) { - "OneDrive content sharing is not restricted to 'Disabled'. Current setting: $($SPOTenant.OneDriveSharingCapability)" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isOneDriveSharingRestricted) { + "OneDrive content sharing is not restricted to 'Disabled'. Current setting: $($SPOTenant.OneDriveSharingCapability)" + } + else { + "N/A" + } - $details = if ($isOneDriveSharingRestricted) { - "OneDrive content sharing is restricted." - } - else { - "OneDriveSharingCapability: $($SPOTenant.OneDriveSharingCapability)" - } + $details = if ($isOneDriveSharingRestricted) { + "OneDrive content sharing is restricted." + } + else { + "OneDriveSharingCapability: $($SPOTenant.OneDriveSharingCapability)" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.2.4" - Result = $isOneDriveSharingRestricted - Status = if ($isOneDriveSharingRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isOneDriveSharingRestricted + Status = if ($isOneDriveSharingRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-OneDriveSyncRestrictions.ps1 b/source/tests/Test-OneDriveSyncRestrictions.ps1 index 5357cda..436899a 100644 --- a/source/tests/Test-OneDriveSyncRestrictions.ps1 +++ b/source/tests/Test-OneDriveSyncRestrictions.ps1 @@ -1,5 +1,6 @@ function Test-OneDriveSyncRestrictions { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,39 +10,54 @@ function Test-OneDriveSyncRestrictions { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.3.2" } process { - # 7.3.2 (L2) Ensure OneDrive sync is restricted for unmanaged devices + try { + # 7.3.2 (L2) Ensure OneDrive sync is restricted for unmanaged devices - # Retrieve OneDrive sync client restriction settings - $SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList - $isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList + # Retrieve OneDrive sync client restriction settings + $SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList + $isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isSyncRestricted) { - "OneDrive sync is not restricted to managed devices. TenantRestrictionEnabled should be True and AllowedDomainList should contain trusted domains GUIDs." - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isSyncRestricted) { + "OneDrive sync is not restricted to managed devices. TenantRestrictionEnabled should be True and AllowedDomainList should contain trusted domains GUIDs." + } + else { + "N/A" + } - $details = if ($isSyncRestricted) { - "OneDrive sync is restricted for unmanaged devices." - } - else { - "TenantRestrictionEnabled: $($SPOTenantSyncClientRestriction.TenantRestrictionEnabled); AllowedDomainList: $($SPOTenantSyncClientRestriction.AllowedDomainList -join ', ')" - } + $details = if ($isSyncRestricted) { + "OneDrive sync is restricted for unmanaged devices." + } + else { + "TenantRestrictionEnabled: $($SPOTenantSyncClientRestriction.TenantRestrictionEnabled); AllowedDomainList: $($SPOTenantSyncClientRestriction.AllowedDomainList -join ', ')" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.3.2" - Result = $isSyncRestricted - Status = if ($isSyncRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isSyncRestricted + Status = if ($isSyncRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-OrgOnlyBypassLobby.ps1 b/source/tests/Test-OrgOnlyBypassLobby.ps1 index 7a7e668..735c86d 100644 --- a/source/tests/Test-OrgOnlyBypassLobby.ps1 +++ b/source/tests/Test-OrgOnlyBypassLobby.ps1 @@ -1,5 +1,6 @@ function Test-OrgOnlyBypassLobby { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,41 +10,56 @@ function Test-OrgOnlyBypassLobby { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.3" } process { - # 8.5.3 (L1) Ensure only people in my org can bypass the lobby + try { + # 8.5.3 (L1) Ensure only people in my org can bypass the lobby - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - # Retrieve the Teams meeting policy for lobby bypass settings - $CsTeamsMeetingPolicyLobby = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AutoAdmittedUsers - $lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests' + # Retrieve the Teams meeting policy for lobby bypass settings + $CsTeamsMeetingPolicyLobby = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AutoAdmittedUsers + $lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests' - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $lobbyBypassRestricted) { - "External participants can bypass the lobby" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $lobbyBypassRestricted) { + "External participants can bypass the lobby" + } + else { + "N/A" + } - $details = if ($lobbyBypassRestricted) { - "Only people in the organization can bypass the lobby." - } - else { - "AutoAdmittedUsers is set to $($CsTeamsMeetingPolicyLobby.AutoAdmittedUsers)" - } + $details = if ($lobbyBypassRestricted) { + "Only people in the organization can bypass the lobby." + } + else { + "AutoAdmittedUsers is set to $($CsTeamsMeetingPolicyLobby.AutoAdmittedUsers)" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.3" - Result = $lobbyBypassRestricted - Status = if ($lobbyBypassRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $lobbyBypassRestricted + Status = if ($lobbyBypassRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-OrganizersPresent.ps1 b/source/tests/Test-OrganizersPresent.ps1 index 337e671..014d13d 100644 --- a/source/tests/Test-OrganizersPresent.ps1 +++ b/source/tests/Test-OrganizersPresent.ps1 @@ -1,5 +1,6 @@ function Test-OrganizersPresent { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,41 +10,56 @@ function Test-OrganizersPresent { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.5.6" } process { - # 8.5.6 (L2) Ensure only organizers and co-organizers can present + try { + # 8.5.6 (L2) Ensure only organizers and co-organizers can present - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - # Retrieve the Teams meeting policy for presenters - $CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode - $presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride' + # Retrieve the Teams meeting policy for presenters + $CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode + $presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride' - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $presenterRoleRestricted) { - "Others besides organizers and co-organizers can present" - } - else { - "N/A" - } + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $presenterRoleRestricted) { + "Others besides organizers and co-organizers can present" + } + else { + "N/A" + } - $details = if ($presenterRoleRestricted) { - "Only organizers and co-organizers can present." - } - else { - "DesignatedPresenterRoleMode is set to $($CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode)" - } + $details = if ($presenterRoleRestricted) { + "Only organizers and co-organizers can present." + } + else { + "DesignatedPresenterRoleMode is set to $($CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode)" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.5.6" - Result = $presenterRoleRestricted - Status = if ($presenterRoleRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $presenterRoleRestricted + Status = if ($presenterRoleRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-PasswordHashSync.ps1 b/source/tests/Test-PasswordHashSync.ps1 index 583d65e..83e6d9d 100644 --- a/source/tests/Test-PasswordHashSync.ps1 +++ b/source/tests/Test-PasswordHashSync.ps1 @@ -1,5 +1,6 @@ function Test-PasswordHashSync { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,35 +10,50 @@ function Test-PasswordHashSync { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "5.1.8.1" } process { - # 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments - # Pass if OnPremisesSyncEnabled is True. Fail otherwise. + try { + # 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments + # Pass if OnPremisesSyncEnabled is True. Fail otherwise. - # Retrieve password hash sync status - $passwordHashSync = Get-MgOrganization | Select-Object -ExpandProperty OnPremisesSyncEnabled - $hashSyncResult = $passwordHashSync + # Retrieve password hash sync status + $passwordHashSync = Get-MgOrganization | Select-Object -ExpandProperty OnPremisesSyncEnabled + $hashSyncResult = $passwordHashSync - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $hashSyncResult) { - "Password hash sync for hybrid deployments is not enabled" - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $hashSyncResult) { + "Password hash sync for hybrid deployments is not enabled" + } + else { + "N/A" + } + + $details = "OnPremisesSyncEnabled: $($passwordHashSync)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $hashSyncResult + Status = if ($hashSyncResult) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "OnPremisesSyncEnabled: $($passwordHashSync)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "5.1.8.1" - Result = $hashSyncResult - Status = if ($hashSyncResult) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-PasswordNeverExpirePolicy.ps1 b/source/tests/Test-PasswordNeverExpirePolicy.ps1 index 530d481..a4f9a07 100644 --- a/source/tests/Test-PasswordNeverExpirePolicy.ps1 +++ b/source/tests/Test-PasswordNeverExpirePolicy.ps1 @@ -1,5 +1,6 @@ function Test-PasswordNeverExpirePolicy { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned [Parameter(Mandatory)] @@ -10,34 +11,49 @@ function Test-PasswordNeverExpirePolicy { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "1.3.1" } process { - # 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire' - # Pass if PasswordValidityPeriodInDays is 0. Fail otherwise. + try { + # 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire' + # Pass if PasswordValidityPeriodInDays is 0. Fail otherwise. - # Retrieve password expiration policy - $passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object -ExpandProperty PasswordValidityPeriodInDays + # Retrieve password expiration policy + $passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object -ExpandProperty PasswordValidityPeriodInDays - # Prepare failure reasons and details based on compliance - $failureReasons = if ($passwordPolicy -ne 0) { - "Password expiration is not set to never expire" - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if ($passwordPolicy -ne 0) { + "Password expiration is not set to never expire" + } + else { + "N/A" + } + + $details = "Validity Period: $passwordPolicy days" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $passwordPolicy -eq 0 + Status = if ($passwordPolicy -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "Validity Period: $passwordPolicy days" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "1.3.1" - Result = $passwordPolicy -eq 0 - Status = if ($passwordPolicy -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-ReauthWithCode.ps1 b/source/tests/Test-ReauthWithCode.ps1 index 28ac0f0..6adfd31 100644 --- a/source/tests/Test-ReauthWithCode.ps1 +++ b/source/tests/Test-ReauthWithCode.ps1 @@ -1,5 +1,6 @@ function Test-ReauthWithCode { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,34 +10,49 @@ function Test-ReauthWithCode { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.10" } process { - # 7.2.10 (L1) Ensure reauthentication with verification code is restricted + try { + # 7.2.10 (L1) Ensure reauthentication with verification code is restricted - # Retrieve reauthentication settings for SharePoint Online - $SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays - $isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15 + # Retrieve reauthentication settings for SharePoint Online + $SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays + $isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15 - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isReauthenticationRestricted) { - "Reauthentication with verification code does not require reauthentication within 15 days or less." - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isReauthenticationRestricted) { + "Reauthentication with verification code does not require reauthentication within 15 days or less." + } + else { + "N/A" + } + + $details = "EmailAttestationRequired: $($SPOTenantReauthentication.EmailAttestationRequired); EmailAttestationReAuthDays: $($SPOTenantReauthentication.EmailAttestationReAuthDays)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isReauthenticationRestricted + Status = if ($isReauthenticationRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "EmailAttestationRequired: $($SPOTenantReauthentication.EmailAttestationRequired); EmailAttestationReAuthDays: $($SPOTenantReauthentication.EmailAttestationReAuthDays)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.2.10" - Result = $isReauthenticationRestricted - Status = if ($isReauthenticationRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index f79cf17..9c889dd 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -1,5 +1,6 @@ function Test-ReportSecurityInTeams { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -9,44 +10,59 @@ function Test-ReportSecurityInTeams { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "8.6.1" } process { - # 8.6.1 (L1) Ensure users can report security concerns in Teams + try { + # 8.6.1 (L1) Ensure users can report security concerns in Teams - # Retrieve the necessary settings for Teams and Exchange Online - $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting - $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled + # Retrieve the necessary settings for Teams and Exchange Online + $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting + $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled - $securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and - $ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and - $ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress -and - $ReportSubmissionPolicy.ReportPhishToCustomizedAddress -and - $ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled + $securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and + $ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and + $ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress -and + $ReportSubmissionPolicy.ReportPhishToCustomizedAddress -and + $ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $securityReportEnabled) { - "Users cannot report security concerns in Teams due to one or more incorrect settings" - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $securityReportEnabled) { + "Users cannot report security concerns in Teams due to one or more incorrect settings" + } + else { + "N/A" + } + + $details = "AllowSecurityEndUserReporting: $($CsTeamsMessagingPolicy.AllowSecurityEndUserReporting); " + + "ReportJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportJunkToCustomizedAddress); " + + "ReportNotJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress); " + + "ReportPhishToCustomizedAddress: $($ReportSubmissionPolicy.ReportPhishToCustomizedAddress); " + + "ReportChatMessageToCustomizedAddressEnabled: $($ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $securityReportEnabled + Status = if ($securityReportEnabled) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "AllowSecurityEndUserReporting: $($CsTeamsMessagingPolicy.AllowSecurityEndUserReporting); " + - "ReportJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportJunkToCustomizedAddress); " + - "ReportNotJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress); " + - "ReportPhishToCustomizedAddress: $($ReportSubmissionPolicy.ReportPhishToCustomizedAddress); " + - "ReportChatMessageToCustomizedAddressEnabled: $($ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "8.6.1" - Result = $securityReportEnabled - Status = if ($securityReportEnabled) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-RestrictCustomScripts.ps1 b/source/tests/Test-RestrictCustomScripts.ps1 index 43a8eef..fea4a0f 100644 --- a/source/tests/Test-RestrictCustomScripts.ps1 +++ b/source/tests/Test-RestrictCustomScripts.ps1 @@ -1,60 +1,126 @@ function Test-RestrictCustomScripts { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( - # Aligned # Define your parameters here if needed ) begin { # Dot source the class script if necessary - #. .\source\Classes\CISAuditResult.ps1 + # . .\source\Classes\CISAuditResult.ps1 + # Initialization code, if needed + $recnum = "7.3.4" } process { - # 7.3.4 (L1) Ensure custom script execution is restricted on site collections - # Retrieve all site collections and select necessary properties - $SPOSitesCustomScript = Get-SPOSite -Limit All | Select-Object Title, Url, DenyAddAndCustomizePages + try { + # 7.3.4 (L1) Ensure custom script execution is restricted on site collections - # Find sites where custom scripts are allowed (DenyAddAndCustomizePages is not 'Enabled') - $customScriptAllowedSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -ne 'Enabled' } + # Retrieve all site collections and select necessary properties + $SPOSitesCustomScript = Get-SPOSite -Limit All | Select-Object Title, Url, DenyAddAndCustomizePages - # Compliance is true if no sites allow custom scripts - $complianceResult = $customScriptAllowedSites.Count -eq 0 + # Replace 'sharepoint.com' with '' + $processedUrls = $SPOSitesCustomScript | ForEach-Object { + $_.Url = $_.Url -replace 'sharepoint\.com', '' + $_ + } - # Gather details for non-compliant sites (where custom scripts are allowed) - $nonCompliantSiteDetails = $customScriptAllowedSites | ForEach-Object { - "$($_.Title) ($($_.Url)): Custom Script Allowed" + # Find sites where custom scripts are allowed + $customScriptAllowedSites = $processedUrls | Where-Object { $_.DenyAddAndCustomizePages -ne 'Enabled' } + #$verbosePreference = 'Continue' + # Check the total length of URLs + $totalUrlLength = ($customScriptAllowedSites.Url -join '').Length + Write-Verbose "Total length of URLs: $totalUrlLength" + + # Extract hostnames from allowed sites if the total length exceeds the limit + $mostUsedHostname = $null + if ($totalUrlLength -gt 20000) { + Write-Verbose "Extracting hostnames from URLs..." + $hostnames = $customScriptAllowedSites.Url | ForEach-Object { + if ($_ -match '^https://([^\.]+)\.') { + $matches[1] + } + } + Write-Verbose "Extracted hostnames: $($hostnames -join ', ')" + + # Find the most used hostname using the Get-MostCommonWord function + $mostUsedHostname = Get-MostCommonWord -InputStrings $hostnames + Write-Verbose "Most used hostname: $mostUsedHostname" + } + #$verbosePreference = 'SilentlyContinue' + # Compliance is true if no sites allow custom scripts + $complianceResult = $customScriptAllowedSites.Count -eq 0 + + # Gather details for non-compliant sites (where custom scripts are allowed) + $nonCompliantSiteDetails = $customScriptAllowedSites | ForEach-Object { + $url = $_.Url + if ($null -ne $mostUsedHostname -and $url -match "^https://$mostUsedHostname\.") { + $url = $url -replace "^https://$mostUsedHostname\.", "https://." + } + "$(if ($_.Title) {$_.Title} else {"NoTitle"})|$url" + } + + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $complianceResult) { + "Some site collections are not restricting custom script execution. Review Details property for sites that are not aligned with the benchmark." + } + else { + "N/A" + } + + $details = if ($complianceResult) { + "All site collections have custom script execution restricted" + } + else { + "Title|Url`n" + ($nonCompliantSiteDetails -join "`n") + } + + # Convert details to PSObject and check length + $detailsPSObject = $details | ConvertFrom-Csv -Delimiter '|' + $detailsLength = ($detailsPSObject | ForEach-Object { $_.Url }).Length + + if ($detailsLength -gt 32767) { + # Create a preview of the first 10 results + $preview = $detailsPSObject | Select-Object -First 10 | ForEach-Object { "$($_.Title)|$($_.Url)" } + $details = "The output is too large. Here is a preview of the first 10 results:`n`n" + ($preview -join "`n") + "`n`nPlease run the test with the following commands to get the full details:`n`nGet-SPOSite -Limit All | Where-Object { `$.DenyAddAndCustomizePages -ne 'Enabled' } | Select-Object Title, Url" + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $complianceResult + Status = if ($complianceResult) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $complianceResult) { - "The following site collections allow custom script execution: " + ($nonCompliantSiteDetails -join "; ") - } - else { - "N/A" - } + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - $details = if ($complianceResult) { - "All site collections have custom script execution restricted" - } - else { - $nonCompliantSiteDetails -join "; " - } + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.3.4" - Result = $complianceResult - Status = if ($complianceResult) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { + # Measure the character count of the details + #$verbosePreference = 'Continue' + $detailsLength = $details.Length + Write-Verbose "Character count of the details: $detailsLength" + + if ($detailsLength -gt 32767) { + Write-Verbose "Warning: The character count exceeds the limit for Excel cells." + } + #$verbosePreference = 'SilentlyContinue' # Return auditResult return $auditResult } diff --git a/source/tests/Test-RestrictExternalSharing.ps1 b/source/tests/Test-RestrictExternalSharing.ps1 index f1b0568..04b00eb 100644 --- a/source/tests/Test-RestrictExternalSharing.ps1 +++ b/source/tests/Test-RestrictExternalSharing.ps1 @@ -1,5 +1,6 @@ function Test-RestrictExternalSharing { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -9,34 +10,49 @@ function Test-RestrictExternalSharing { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "7.2.3" } process { - # 7.2.3 (L1) Ensure external content sharing is restricted + try { + # 7.2.3 (L1) Ensure external content sharing is restricted - # Retrieve the SharingCapability setting for the SharePoint tenant - $SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability - $isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled') + # Retrieve the SharingCapability setting for the SharePoint tenant + $SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability + $isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled') - # Prepare failure reasons and details based on compliance - $failureReasons = if (-not $isRestricted) { - "External content sharing is not adequately restricted. Current setting: $($SPOTenantSharingCapability.SharingCapability)" - } - else { - "N/A" + # Prepare failure reasons and details based on compliance + $failureReasons = if (-not $isRestricted) { + "External content sharing is not adequately restricted. Current setting: $($SPOTenantSharingCapability.SharingCapability)" + } + else { + "N/A" + } + + $details = "SharingCapability: $($SPOTenantSharingCapability.SharingCapability)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isRestricted + Status = if ($isRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "SharingCapability: $($SPOTenantSharingCapability.SharingCapability)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "7.2.3" - Result = $isRestricted - Status = if ($isRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-RestrictOutlookAddins.ps1 b/source/tests/Test-RestrictOutlookAddins.ps1 index 4438b50..39f1ae6 100644 --- a/source/tests/Test-RestrictOutlookAddins.ps1 +++ b/source/tests/Test-RestrictOutlookAddins.ps1 @@ -1,5 +1,6 @@ function Test-RestrictOutlookAddins { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters could include credentials or other necessary data @@ -12,63 +13,78 @@ function Test-RestrictOutlookAddins { $customPolicyFailures = @() $defaultPolicyFailureDetails = @() $relevantRoles = @('My Custom Apps', 'My Marketplace Apps', 'My ReadWriteMailbox Apps') + $recnum = "6.3.1" } process { - # 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed + try { + # 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed - # Check all mailboxes for custom policies with unallowed add-ins - $roleAssignmentPolicies = Get-EXOMailbox | Select-Object -Unique RoleAssignmentPolicy + # Check all mailboxes for custom policies with unallowed add-ins + $roleAssignmentPolicies = Get-EXOMailbox | Select-Object -Unique RoleAssignmentPolicy - if ($roleAssignmentPolicies.RoleAssignmentPolicy) { - foreach ($policy in $roleAssignmentPolicies) { - if ($policy.RoleAssignmentPolicy) { - $rolePolicyDetails = Get-RoleAssignmentPolicy -Identity $policy.RoleAssignmentPolicy - $foundRoles = $rolePolicyDetails.AssignedRoles | Where-Object { $_ -in $relevantRoles } - if ($foundRoles) { - $customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')" + if ($roleAssignmentPolicies.RoleAssignmentPolicy) { + foreach ($policy in $roleAssignmentPolicies) { + if ($policy.RoleAssignmentPolicy) { + $rolePolicyDetails = Get-RoleAssignmentPolicy -Identity $policy.RoleAssignmentPolicy + $foundRoles = $rolePolicyDetails.AssignedRoles | Where-Object { $_ -in $relevantRoles } + if ($foundRoles) { + $customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')" + } } } } - } - # Check Default Role Assignment Policy - $defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy" - $defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles } - if ($defaultPolicyRoles) { - $defaultPolicyFailureDetails = $defaultPolicyRoles - } + # Check Default Role Assignment Policy + $defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy" + $defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles } + if ($defaultPolicyRoles) { + $defaultPolicyFailureDetails = $defaultPolicyRoles + } - # Prepare result details string - $detailsString = "" - if ($customPolicyFailures) { - $detailsString += "Custom Policy Failures: | " - $detailsString += ($customPolicyFailures -join " | ") - } - else { - $detailsString += "Custom Policy Failures: None | " - } + # Prepare result details string + $detailsString = "" + if ($customPolicyFailures) { + $detailsString += "Custom Policy Failures: | " + $detailsString += ($customPolicyFailures -join " | ") + } + else { + $detailsString += "Custom Policy Failures: None | " + } - $detailsString += "Default Role Assignment Policy: " - if ($defaultPolicyFailureDetails) { - $detailsString += ($defaultPolicyFailureDetails -join ', ') - } - else { - $detailsString += "Compliant" - } + $detailsString += "Default Role Assignment Policy: " + if ($defaultPolicyFailureDetails) { + $detailsString += ($defaultPolicyFailureDetails -join ', ') + } + else { + $detailsString += "Compliant" + } - # Determine result based on findings - $isCompliant = -not ($customPolicyFailures -or $defaultPolicyFailureDetails) + # Determine result based on findings + $isCompliant = -not ($customPolicyFailures -or $defaultPolicyFailureDetails) - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.3.1" - Result = $isCompliant - Status = if ($isCompliant) { "Pass" } else { "Fail" } - Details = $detailsString - FailureReason = if ($isCompliant) { "N/A" } else { "Unauthorized Outlook add-ins found in custom or default policies." } + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $isCompliant + Status = if ($isCompliant) { "Pass" } else { "Fail" } + Details = $detailsString + FailureReason = if ($isCompliant) { "N/A" } else { "Unauthorized Outlook add-ins found in custom or default policies." } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 index 8400d72..fa7b256 100644 --- a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 +++ b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 @@ -1,5 +1,6 @@ function Test-RestrictStorageProvidersOutlook { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added here if needed @@ -9,42 +10,57 @@ function Test-RestrictStorageProvidersOutlook { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "6.5.3" } process { - # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web + try { + # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web - # Retrieve all OwaMailbox policies - $owaPolicies = Get-OwaMailboxPolicy - $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } + # Retrieve all OwaMailbox policies + $owaPolicies = Get-OwaMailboxPolicy + $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } - # Determine compliance - $allPoliciesRestricted = $nonCompliantPolicies.Count -eq 0 + # Determine compliance + $allPoliciesRestricted = $nonCompliantPolicies.Count -eq 0 - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allPoliciesRestricted) { - "N/A" - } - else { - "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allPoliciesRestricted) { + "N/A" + } + else { + "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." + } - $details = if ($allPoliciesRestricted) { - "All OwaMailbox policies restrict AdditionalStorageProvidersAvailable" - } - else { - "Non-compliant OwaMailbox policies: $($nonCompliantPolicies.Name -join ', ')" - } + $details = if ($allPoliciesRestricted) { + "All OwaMailbox policies restrict AdditionalStorageProvidersAvailable" + } + else { + "Non-compliant OwaMailbox policies: $($nonCompliantPolicies.Name -join ', ')" + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "6.5.3" - Result = $allPoliciesRestricted - Status = if ($allPoliciesRestricted) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $allPoliciesRestricted + Status = if ($allPoliciesRestricted) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-RestrictTenantCreation.ps1 b/source/tests/Test-RestrictTenantCreation.ps1 index 96bfb0f..ede53c4 100644 --- a/source/tests/Test-RestrictTenantCreation.ps1 +++ b/source/tests/Test-RestrictTenantCreation.ps1 @@ -1,5 +1,6 @@ function Test-RestrictTenantCreation { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,34 +10,49 @@ function Test-RestrictTenantCreation { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "5.1.2.3" } process { - # 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' + try { + # 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' - # Retrieve the tenant creation policy - $tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants - $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants + # Retrieve the tenant creation policy + $tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants + $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants - # Prepare failure reasons and details based on compliance - $failureReasons = if ($tenantCreationResult) { - "N/A" - } - else { - "Non-admin users can create tenants" + # Prepare failure reasons and details based on compliance + $failureReasons = if ($tenantCreationResult) { + "N/A" + } + else { + "Non-admin users can create tenants" + } + + $details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)" + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $tenantCreationResult + Status = if ($tenantCreationResult) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)" + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "5.1.2.3" - Result = $tenantCreationResult - Status = if ($tenantCreationResult) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index 48597ce..18a90a2 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -1,5 +1,6 @@ function Test-SafeAttachmentsPolicy { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,39 +10,54 @@ function Test-SafeAttachmentsPolicy { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.4" } process { - # 2.1.4 (L2) Ensure Safe Attachments policy is enabled + try { + # 2.1.4 (L2) Ensure Safe Attachments policy is enabled - # Retrieve all Safe Attachment policies where Enable is set to True - $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } + # Retrieve all Safe Attachment policies where Enable is set to True + $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } - # Determine result and details based on the presence of enabled policies - $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 - $details = if ($result) { - "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" - } - else { - "No Safe Attachments Policies are enabled." - } + # Determine result and details based on the presence of enabled policies + $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 + $details = if ($result) { + "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" + } + else { + "No Safe Attachments Policies are enabled." + } - $failureReasons = if ($result) { - "N/A" - } - else { - "Safe Attachments policy is not enabled." - } + $failureReasons = if ($result) { + "N/A" + } + else { + "Safe Attachments policy is not enabled." + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "2.1.4" - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index a1cb453..1fae789 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -1,5 +1,6 @@ function Test-SafeAttachmentsTeams { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -9,46 +10,61 @@ function Test-SafeAttachmentsTeams { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.5" } process { - # 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled + try { + # 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled - # Retrieve the ATP policies for Office 365 and check Safe Attachments settings - $atpPolicies = Get-AtpPolicyForO365 + # Retrieve the ATP policies for Office 365 and check Safe Attachments settings + $atpPolicies = Get-AtpPolicyForO365 - # Check if the required ATP policies are enabled - $atpPolicyResult = $atpPolicies | Where-Object { - $_.EnableATPForSPOTeamsODB -eq $true -and - $_.EnableSafeDocs -eq $true -and - $_.AllowSafeDocsOpen -eq $false - } + # Check if the required ATP policies are enabled + $atpPolicyResult = $atpPolicies | Where-Object { + $_.EnableATPForSPOTeamsODB -eq $true -and + $_.EnableSafeDocs -eq $true -and + $_.AllowSafeDocsOpen -eq $false + } - # Determine the result based on the ATP policy settings - $result = $null -ne $atpPolicyResult - $details = if ($result) { - "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." - } - else { - "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." - } + # Determine the result based on the ATP policy settings + $result = $null -ne $atpPolicyResult + $details = if ($result) { + "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." + } + else { + "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." + } - $failureReasons = if ($result) { - "N/A" - } - else { - "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." - } + $failureReasons = if ($result) { + "N/A" + } + else { + "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." + } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "2.1.5" - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 46ee852..aa30dbc 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -1,5 +1,6 @@ function Test-SafeLinksOfficeApps { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here if needed @@ -9,53 +10,68 @@ function Test-SafeLinksOfficeApps { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed + $recnum = "2.1.1" } process { - # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled + try { + # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled - # Retrieve all Safe Links policies - $policies = Get-SafeLinksPolicy + # Retrieve all Safe Links policies + $policies = Get-SafeLinksPolicy - # Initialize the details collection - $misconfiguredDetails = @() + # Initialize the details collection + $misconfiguredDetails = @() - foreach ($policy in $policies) { - # Get the detailed configuration of each policy - $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name + foreach ($policy in $policies) { + # Get the detailed configuration of each policy + $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name - # Check each required property and record failures - $failures = @() - if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } - if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } - if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } - if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } - if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } - if ($policyDetails.ScanUrls -ne $true) { $failures += "ScanUrls: False" } - if ($policyDetails.EnableForInternalSenders -ne $true) { $failures += "EnableForInternalSenders: False" } - if ($policyDetails.DeliverMessageAfterScan -ne $true) { $failures += "DeliverMessageAfterScan: False" } - if ($policyDetails.DisableUrlRewrite -ne $false) { $failures += "DisableUrlRewrite: True" } + # Check each required property and record failures + $failures = @() + if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } + if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } + if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } + if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } + if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } + if ($policyDetails.ScanUrls -ne $true) { $failures += "ScanUrls: False" } + if ($policyDetails.EnableForInternalSenders -ne $true) { $failures += "EnableForInternalSenders: False" } + if ($policyDetails.DeliverMessageAfterScan -ne $true) { $failures += "DeliverMessageAfterScan: False" } + if ($policyDetails.DisableUrlRewrite -ne $false) { $failures += "DisableUrlRewrite: True" } - # Only add details for policies that have misconfigurations - if ($failures.Count -gt 0) { - $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" + # Only add details for policies that have misconfigurations + if ($failures.Count -gt 0) { + $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" + } } - } - # Prepare the final result - $result = $misconfiguredDetails.Count -eq 0 - $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' } - $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } + # Prepare the final result + $result = $misconfiguredDetails.Count -eq 0 + $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' } + $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } - # Create and populate the CISAuditResult object - $params = @{ - Rec = "2.1.1" - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SharePointAADB2B.ps1 b/source/tests/Test-SharePointAADB2B.ps1 index a9af0c4..c95e415 100644 --- a/source/tests/Test-SharePointAADB2B.ps1 +++ b/source/tests/Test-SharePointAADB2B.ps1 @@ -1,5 +1,6 @@ function Test-SharePointAADB2B { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -11,21 +12,36 @@ function Test-SharePointAADB2B { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "7.2.2" } process { - # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled - $SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration + try { + # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + $SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration - # Populate the auditResult object with the required properties - $params = @{ - Rec = "7.2.2" - Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration - Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" } - Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)" - FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled" } else { "N/A" } + # Populate the auditResult object with the required properties + $params = @{ + Rec = $recnum + Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration + Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" } + Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)" + FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled" } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SharePointExternalSharingDomains.ps1 b/source/tests/Test-SharePointExternalSharingDomains.ps1 index 25f4213..ee309d7 100644 --- a/source/tests/Test-SharePointExternalSharingDomains.ps1 +++ b/source/tests/Test-SharePointExternalSharingDomains.ps1 @@ -1,5 +1,6 @@ function Test-SharePointExternalSharingDomains { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -11,22 +12,37 @@ function Test-SharePointExternalSharingDomains { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "7.2.6" } process { - # 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists - $SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList - $isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList' + try { + # 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists + $SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList + $isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList' - # Populate the auditResult object with the required properties - $params = @{ - Rec = "7.2.6" - Result = $isDomainRestrictionConfigured - Status = if ($isDomainRestrictionConfigured) { "Pass" } else { "Fail" } - Details = "SharingDomainRestrictionMode: $($SPOTenant.SharingDomainRestrictionMode); SharingAllowedDomainList: $($SPOTenant.SharingAllowedDomainList)" - FailureReason = if (-not $isDomainRestrictionConfigured) { "Domain restrictions for SharePoint external sharing are not configured to 'AllowList'. Current setting: $($SPOTenant.SharingDomainRestrictionMode)" } else { "N/A" } + # Populate the auditResult object with the required properties + $params = @{ + Rec = $recnum + Result = $isDomainRestrictionConfigured + Status = if ($isDomainRestrictionConfigured) { "Pass" } else { "Fail" } + Details = "SharingDomainRestrictionMode: $($SPOTenant.SharingDomainRestrictionMode); SharingAllowedDomainList: $($SPOTenant.SharingAllowedDomainList)" + FailureReason = if (-not $isDomainRestrictionConfigured) { "Domain restrictions for SharePoint external sharing are not configured to 'AllowList'. Current setting: $($SPOTenant.SharingDomainRestrictionMode)" } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SharePointGuestsItemSharing.ps1 b/source/tests/Test-SharePointGuestsItemSharing.ps1 index 6f35325..75159b4 100644 --- a/source/tests/Test-SharePointGuestsItemSharing.ps1 +++ b/source/tests/Test-SharePointGuestsItemSharing.ps1 @@ -1,5 +1,6 @@ function Test-SharePointGuestsItemSharing { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Define your parameters here @@ -11,22 +12,37 @@ function Test-SharePointGuestsItemSharing { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "7.2.5" } process { - # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own - $SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing - $isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing + try { + # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own + $SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing + $isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing - # Populate the auditResult object with the required properties - $params = @{ - Rec = "7.2.5" - Result = $isGuestResharingPrevented - Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" } - Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented" - FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own." } else { "N/A" } + # Populate the auditResult object with the required properties + $params = @{ + Rec = $recnum + Result = $isGuestResharingPrevented + Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" } + Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented" + FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own." } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index 05be9aa..8f100c7 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -1,5 +1,6 @@ function Test-SpamPolicyAdminNotify { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added if needed @@ -11,37 +12,52 @@ function Test-SpamPolicyAdminNotify { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "2.1.6" } process { - # 2.1.6 Ensure Exchange Online Spam Policies are set to notify administrators + try { + # 2.1.6 Ensure Exchange Online Spam Policies are set to notify administrators - # Get the default hosted outbound spam filter policy - $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } + # Get the default hosted outbound spam filter policy + $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } - # Check if both settings are enabled - $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail - $notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam - $areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled + # Check if both settings are enabled + $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail + $notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam + $areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled - # Prepare failure details if any setting is not enabled - $failureDetails = @() - if (-not $bccSuspiciousOutboundMailEnabled) { - $failureDetails += "BccSuspiciousOutboundMail is not enabled." - } - if (-not $notifyOutboundSpamEnabled) { - $failureDetails += "NotifyOutboundSpam is not enabled." + # Prepare failure details if any setting is not enabled + $failureDetails = @() + if (-not $bccSuspiciousOutboundMailEnabled) { + $failureDetails += "BccSuspiciousOutboundMail is not enabled." + } + if (-not $notifyOutboundSpamEnabled) { + $failureDetails += "NotifyOutboundSpam is not enabled." + } + + # Create an instance of CISAuditResult and populate it + $params = @{ + Rec = $recnum + Result = $areSettingsEnabled + Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" } + Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' } + FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - # Create an instance of CISAuditResult and populate it - $params = @{ - Rec = "2.1.6" - Result = $areSettingsEnabled - Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" } - Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' } - FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" } + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-TeamsExternalAccess.ps1 b/source/tests/Test-TeamsExternalAccess.ps1 index 8b0cad2..aec7caa 100644 --- a/source/tests/Test-TeamsExternalAccess.ps1 +++ b/source/tests/Test-TeamsExternalAccess.ps1 @@ -1,5 +1,6 @@ function Test-TeamsExternalAccess { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be defined here if needed @@ -11,32 +12,47 @@ function Test-TeamsExternalAccess { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "8.2.1" } process { - # 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center + try { + # 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center - # Connect to Teams PowerShell using Connect-MicrosoftTeams + # Connect to Teams PowerShell using Connect-MicrosoftTeams - $externalAccessConfig = Get-CsTenantFederationConfiguration + $externalAccessConfig = Get-CsTenantFederationConfiguration - $allowedDomainsLimited = $false - if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) { - $allowedDomainsLimited = $true + $allowedDomainsLimited = $false + if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) { + $allowedDomainsLimited = $true + } + + # Check if the configurations are as recommended + $isCompliant = -not $externalAccessConfig.AllowTeamsConsumer -and -not $externalAccessConfig.AllowPublicUsers -and (-not $externalAccessConfig.AllowFederatedUsers -or $allowedDomainsLimited) + + # Create an instance of CISAuditResult and populate it + $params = @{ + Rec = $recnum + Result = $isCompliant + Status = if ($isCompliant) { "Pass" } else { "Fail" } + Details = "AllowTeamsConsumer: $($externalAccessConfig.AllowTeamsConsumer); AllowPublicUsers: $($externalAccessConfig.AllowPublicUsers); AllowFederatedUsers: $($externalAccessConfig.AllowFederatedUsers); AllowedDomains limited: $allowedDomainsLimited" + FailureReason = if (-not $isCompliant) { "One or more external access configurations are not compliant." } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - # Check if the configurations are as recommended - $isCompliant = -not $externalAccessConfig.AllowTeamsConsumer -and -not $externalAccessConfig.AllowPublicUsers -and (-not $externalAccessConfig.AllowFederatedUsers -or $allowedDomainsLimited) + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create an instance of CISAuditResult and populate it - $params = @{ - Rec = "8.2.1" - Result = $isCompliant - Status = if ($isCompliant) { "Pass" } else { "Fail" } - Details = "AllowTeamsConsumer: $($externalAccessConfig.AllowTeamsConsumer); AllowPublicUsers: $($externalAccessConfig.AllowPublicUsers); AllowFederatedUsers: $($externalAccessConfig.AllowFederatedUsers); AllowedDomains limited: $allowedDomainsLimited" - FailureReason = if (-not $isCompliant) { "One or more external access configurations are not compliant." } else { "N/A" } + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/source/tests/Test-TeamsExternalFileSharing.ps1 b/source/tests/Test-TeamsExternalFileSharing.ps1 index fd4e653..92ef78c 100644 --- a/source/tests/Test-TeamsExternalFileSharing.ps1 +++ b/source/tests/Test-TeamsExternalFileSharing.ps1 @@ -1,5 +1,6 @@ function Test-TeamsExternalFileSharing { [CmdletBinding()] + [OutputType([CISAuditResult])] param ( # Aligned # Parameters can be added here if needed @@ -11,36 +12,51 @@ function Test-TeamsExternalFileSharing { # Initialization code, if needed $auditResult = [CISAuditResult]::new() + $recnum = "8.1.1" } process { - # 8.1.1 (L2) Ensure external file sharing in Teams is enabled for only approved cloud storage services - # Connect to Teams PowerShell using Connect-MicrosoftTeams + try { + # 8.1.1 (L2) Ensure external file sharing in Teams is enabled for only approved cloud storage services + # Connect to Teams PowerShell using Connect-MicrosoftTeams - # Assuming that 'approvedProviders' is a list of approved cloud storage service names - # This list must be defined according to your organization's approved cloud storage services - $approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte") - $clientConfig = Get-CsTeamsClientConfiguration + # Assuming that 'approvedProviders' is a list of approved cloud storage service names + # This list must be defined according to your organization's approved cloud storage services + $approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte") + $clientConfig = Get-CsTeamsClientConfiguration - $isCompliant = $true - $nonCompliantProviders = @() + $isCompliant = $true + $nonCompliantProviders = @() - foreach ($provider in $approvedProviders) { - if (-not $clientConfig.$provider) { - $isCompliant = $false - $nonCompliantProviders += $provider + foreach ($provider in $approvedProviders) { + if (-not $clientConfig.$provider) { + $isCompliant = $false + $nonCompliantProviders += $provider + } } - } - # Create an instance of CISAuditResult and populate it - $params = @{ - Rec = "8.1.1" - Result = $isCompliant - Status = if ($isCompliant) { "Pass" } else { "Fail" } - Details = if (-not $isCompliant) { "Non-approved providers enabled: $($nonCompliantProviders -join ', ')" } else { "All cloud storage services are approved providers" } - FailureReason = if (-not $isCompliant) { "The following non-approved providers are enabled: $($nonCompliantProviders -join ', ')" } else { "N/A" } + # Create an instance of CISAuditResult and populate it + $params = @{ + Rec = $recnum + Result = $isCompliant + Status = if ($isCompliant) { "Pass" } else { "Fail" } + Details = if (-not $isCompliant) { "Non-approved providers enabled: $($nonCompliantProviders -join ', ')" } else { "All cloud storage services are approved providers" } + FailureReason = if (-not $isCompliant) { "The following non-approved providers are enabled: $($nonCompliantProviders -join ', ')" } else { "N/A" } + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - $auditResult = Initialize-CISAuditResult @params } end { diff --git a/tests/Unit/Private/Get-MostCommonWord.tests.ps1 b/tests/Unit/Private/Get-MostCommonWord.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-MostCommonWord.tests.ps1 @@ -0,0 +1,27 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) + }).BaseName + + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe Get-PrivateFunction { + Context 'Default' { + BeforeEach { + $return = Get-PrivateFunction -PrivateData 'string' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Returns a string based on the parameter PrivateData' { + $return | Should -Be 'string' + } + } + } +} + diff --git a/tests/Unit/Private/Get-RequiredModule.tests.ps1 b/tests/Unit/Private/Get-RequiredModule.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-RequiredModule.tests.ps1 @@ -0,0 +1,27 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) + }).BaseName + + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe Get-PrivateFunction { + Context 'Default' { + BeforeEach { + $return = Get-PrivateFunction -PrivateData 'string' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Returns a string based on the parameter PrivateData' { + $return | Should -Be 'string' + } + } + } +} + diff --git a/tests/Unit/Private/Invoke-TestFunction.tests.ps1 b/tests/Unit/Private/Invoke-TestFunction.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Invoke-TestFunction.tests.ps1 @@ -0,0 +1,27 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) + }).BaseName + + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe Get-PrivateFunction { + Context 'Default' { + BeforeEach { + $return = Get-PrivateFunction -PrivateData 'string' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Returns a string based on the parameter PrivateData' { + $return | Should -Be 'string' + } + } + } +} + diff --git a/tests/Unit/Private/Measure-AuditResult.tests.ps1 b/tests/Unit/Private/Measure-AuditResult.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Measure-AuditResult.tests.ps1 @@ -0,0 +1,27 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) + }).BaseName + + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe Get-PrivateFunction { + Context 'Default' { + BeforeEach { + $return = Get-PrivateFunction -PrivateData 'string' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Returns a string based on the parameter PrivateData' { + $return | Should -Be 'string' + } + } + } +} + diff --git a/tests/Unit/Private/New-MergedObject.tests.ps1 b/tests/Unit/Private/New-MergedObject.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/New-MergedObject.tests.ps1 @@ -0,0 +1,27 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) + }).BaseName + + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe Get-PrivateFunction { + Context 'Default' { + BeforeEach { + $return = Get-PrivateFunction -PrivateData 'string' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + It 'Returns a string based on the parameter PrivateData' { + $return | Should -Be 'string' + } + } + } +} +