From b18780d52e58702f7652595a24bd7899458af830 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:17:22 -0500 Subject: [PATCH 01/42] fix: Update 1.3.1 output and test logic to include notification window. --- .../tests/Test-PasswordNeverExpirePolicy.ps1 | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/source/tests/Test-PasswordNeverExpirePolicy.ps1 b/source/tests/Test-PasswordNeverExpirePolicy.ps1 index 5d0723e..c6a41c0 100644 --- a/source/tests/Test-PasswordNeverExpirePolicy.ps1 +++ b/source/tests/Test-PasswordNeverExpirePolicy.ps1 @@ -17,7 +17,7 @@ function Test-PasswordNeverExpirePolicy { $failureReasonsList = @() # Add headers for the details - $detailsList += "Domain|Validity Period|IsDefault" + $detailsList += "Domain|Validity Period|Notification Window|IsDefault" # Conditions for 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)' # @@ -26,12 +26,14 @@ function Test-PasswordNeverExpirePolicy { # - Specific conditions to check: # - Condition A: Password expiration policy is set to "Set passwords to never expire" in the Microsoft 365 admin center. # - Condition B: Using Microsoft Graph PowerShell, the `PasswordPolicies` property for all users is set to `DisablePasswordExpiration`. + # - Condition C: Notification window for password expiration is set to 30 days. # # Validate test for a fail: # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. # - Specific conditions to check: # - Condition A: Password expiration policy is not set to "Set passwords to never expire" in the Microsoft 365 admin center. # - Condition B: Using Microsoft Graph PowerShell, the `PasswordPolicies` property for one or more users is not set to `DisablePasswordExpiration`. + # - Condition C: Notification window for password expiration is not set to 30 days. } process { @@ -46,21 +48,23 @@ function Test-PasswordNeverExpirePolicy { foreach ($domain in $domains) { $domainName = $domain.Id $isDefault = $domain.IsDefault + # Step (Condition C): Determine if the notification window is set to 30 days + $notificationWindow = $domain.PasswordNotificationWindowInDays + $notificationPolIsCompliant = $notificationWindow -eq 30 # Step (Condition A): Retrieve password expiration policy $passwordPolicy = $domain.PasswordValidityPeriodInDays - + $pwPolIsCompliant = $passwordPolicy -eq 2147483647 # Step (Condition A & B): Determine if the policy is compliant - $isCompliant = $passwordPolicy -eq 0 - $overallResult = $overallResult -and $isCompliant + $overallResult = $overallResult -and $notificationPolIsCompliant -and $pwPolIsCompliant # Step (Condition A & B): Prepare failure reasons and details based on compliance - $failureReasons = if ($isCompliant) { + $failureReasons = if ($notificationPolIsCompliant -and $pwPolIsCompliant) { "N/A" } else { - "Password expiration is not set to never expire for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30`n" + "Password expiration is not set to never expire or notification window is not set to 30 days for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30`n" } - $details = "$domainName|$passwordPolicy days|$isDefault" + $details = "$domainName|$passwordPolicy days|$notificationWindow days|$isDefault" # Add details and failure reasons to the lists $detailsList += $details From 359d2890f8b4ae0e8f6b75316fcd298c65b89e01 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:19:48 -0500 Subject: [PATCH 02/42] docs: Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42cf719..4996d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +### Fixed + +- Fixed test 1.3.1 to include notification window for password expiration. + +## [0.1.13] - 2024-06-18 + ### Added - Added tenant output to connect function. From ab0ef53bbd7b3e4d28eb5302c95409cd3a607f32 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:12:01 -0500 Subject: [PATCH 03/42] add: Export to excel option for table exports --- .../Public/Export-M365SecurityAuditTable.ps1 | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/source/Public/Export-M365SecurityAuditTable.ps1 b/source/Public/Export-M365SecurityAuditTable.ps1 index 0bbe517..8542ebb 100644 --- a/source/Public/Export-M365SecurityAuditTable.ps1 +++ b/source/Public/Export-M365SecurityAuditTable.ps1 @@ -16,6 +16,8 @@ The path where the CSV files will be exported. .PARAMETER ExportOriginalTests Switch to export the original audit results to a CSV file. + .PARAMETER ExportToExcel + Switch to export the results to an Excel file. .INPUTS [CISAuditResult[]], [string] .OUTPUTS @@ -69,9 +71,15 @@ function Export-M365SecurityAuditTable { [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")] - [switch]$ExportOriginalTests - ) + [switch]$ExportOriginalTests, + [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] + [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")] + [switch]$ExportToExcel + ) + if ($ExportToExcel) { + Assert-ModuleAvailability -ModuleName ImportExcel -RequiredVersion "7.8.9" + } if ($PSCmdlet.ParameterSetName -like "ExportAllResultsFromCsv" -or $PSCmdlet.ParameterSetName -eq "OutputObjectFromCsvSingle") { $AuditResults = Import-Csv -Path $CsvPath | ForEach-Object { $params = @{ @@ -166,7 +174,13 @@ function Export-M365SecurityAuditTable { } else { if (($result.Details -ne "No M365 E3 licenses found.") -and ($result.Details -ne "No M365 E5 licenses found.")) { - $result.Details | Export-Csv -Path $fileName -NoTypeInformation + if ($ExportToExcel) { + $xlsxPath = [System.IO.Path]::ChangeExtension($fileName, '.xlsx') + $result.Details | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2 + } + else { + $result.Details | Export-Csv -Path $fileName -NoTypeInformation + } $exportedTests += $result.TestNumber } } @@ -191,7 +205,13 @@ function Export-M365SecurityAuditTable { # Check for large details and update the AuditResults array $updatedAuditResults = Get-ExceededLengthResultDetail -AuditResults $AuditResults -TestNumbersToCheck $TestNumbersToCheck -ExportedTests $exportedTests -DetailsLengthLimit 30000 -PreviewLineCount 25 $originalFileName = "$ExportPath\$timestamp`_M365FoundationsAudit.csv" + if ($ExportToExcel) { + $xlsxPath = [System.IO.Path]::ChangeExtension($originalFileName, '.xlsx') + $updatedAuditResults | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2 + } + else { $updatedAuditResults | Export-Csv -Path $originalFileName -NoTypeInformation + } } } elseif ($OutputTestNumber) { From 1d462572c1e9b29427e06ea0a96780e76d6c961b Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:13:42 -0500 Subject: [PATCH 04/42] docs: Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4996d60..89e5f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed test 1.3.1 to include notification window for password expiration. +### Added + +- Added export to excel to `Export-M365SecurityAuditTable` function. + ## [0.1.13] - 2024-06-18 ### Added From 0f3587ab15db4bdd876bdf4050c5d69df1a629de Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:16:12 -0500 Subject: [PATCH 05/42] docs: Update README and Help --- README.md | Bin 35932 -> 36208 bytes docs/index.html | Bin 93256 -> 94070 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index 4da28d8248171fe4a60bb34877001e4b4e6fc60d..eb83ba81775750fdc48597384cae132c76f6fa26 100644 GIT binary patch delta 170 zcmcaJgXzO8rVR_&HqT)TV3yWpaAl}qC}7BEC}JpK2m#U{elkNUL(b$tE7QquI2<-d zaTPEUQn-0mJs;y{Hr^c8$r}V@cu@_y*d#mogrLUcDNNJ? diff --git a/docs/index.html b/docs/index.html index 92b6eb2e0ce6e87f216d7c2d6a0f2e0f65ff9d01..a1b4a6f731d19afd4a1cfa57679014a42889c2ca 100644 GIT binary patch delta 172 zcmX?cgZ0}z)(L0ijTj6V^cg^;5rYYX0)rugF@qJ5Gy#%E45k}jr)x^nAl_6(x;WEd`b=oDHv@00#soPwrvr}w(f)1n2C0E8wl{{R30 delta 87 zcmex%kM+b2)(L0i4H+yL^cjp841m~#L4m;#NLm4TW Date: Sun, 23 Jun 2024 10:03:37 -0500 Subject: [PATCH 06/42] add: skip msol connnection for Get-MFAStatus --- source/Public/Get-MFAStatus.ps1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 index 53f6112..a073ba9 100644 --- a/source/Public/Get-MFAStatus.ps1 +++ b/source/Public/Get-MFAStatus.ps1 @@ -33,7 +33,8 @@ function Get-MFAStatus { param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] - [string]$UserId + [string]$UserId, + [switch]$SkipMSOLConnectionChecks ) begin { @@ -43,7 +44,9 @@ function Get-MFAStatus { process { if (Get-Module MSOnline){ - Connect-MsolService + if (-not $SkipMSOLConnectionChecks) { + Connect-MsolService + } Write-Host "Finding Azure Active Directory Accounts..." # Get all users, excluding guests $Users = if ($PSBoundParameters.ContainsKey('UserId')) { @@ -87,13 +90,15 @@ function Get-MFAStatus { MFAPhoneNumber = $MFAPhoneNumber PrimarySMTP = ($PrimarySMTP -join ',') Aliases = ($Aliases -join ',') + isLicensed = $User.isLicensed } $Report.Add($ReportLine) } Write-Host "Processing complete." - return $Report | Select-Object UserPrincipalName, DisplayName, MFAState, MFADefaultMethod, MFAPhoneNumber, PrimarySMTP, Aliases | Sort-Object UserPrincipalName + Write-Host "To disconnect from the MsolService close the powershell session or wait for the session to expire." + return $Report | Select-Object UserPrincipalName, DisplayName, MFAState, MFADefaultMethod, MFAPhoneNumber, PrimarySMTP, Aliases, isLicensed | Sort-Object UserPrincipalName } else { Write-Host "You must first install MSOL using:`nInstall-Module MSOnline -Scope CurrentUser -Force" From 84c16ac16ee8cfab0b9d1028b5c68bfebdce9346 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 10:06:05 -0500 Subject: [PATCH 07/42] fix 6.1.1 test definition so it uses EXO --- source/helper/TestDefinitions.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/helper/TestDefinitions.csv b/source/helper/TestDefinitions.csv index d3f4e8c..e12213b 100644 --- a/source/helper/TestDefinitions.csv +++ b/source/helper/TestDefinitions.csv @@ -17,7 +17,7 @@ 16,Test-AuditLogSearch.ps1,3.1.1,Ensure Microsoft 365 audit log search is Enabled,E3,L1,8.2,Collect Audit Logs,TRUE,TRUE,TRUE,TRUE,EXO 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 +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,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,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,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 From 39ba3c3ad77201ab5115f18ef8d292274d92c799 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 11:39:14 -0500 Subject: [PATCH 08/42] add: New process for collecting MgGraph output to make pester testing easier --- CHANGELOG.md | 4 + .../Get-AdminRoleUserAndAssignment.ps1 | 38 +++++++++ source/Private/Get-MgOutput.ps1 | 85 +++++++++++++++++++ .../Test-AdministrativeAccountCompliance.ps1 | 83 +++++++----------- source/tests/Test-GlobalAdminsCount.ps1 | 6 +- source/tests/Test-MailboxAuditingE3.ps1 | 9 +- source/tests/Test-MailboxAuditingE5.ps1 | 7 +- .../Test-ManagedApprovedPublicGroups.ps1 | 2 +- source/tests/Test-PasswordHashSync.ps1 | 2 +- source/tests/Test-RestrictTenantCreation.ps1 | 2 +- .../Get-AdminRoleUserAndAssignment.tests.ps1 | 27 ++++++ tests/Unit/Private/Get-MgOutput.tests.ps1 | 27 ++++++ 12 files changed, 224 insertions(+), 68 deletions(-) create mode 100644 source/Private/Get-AdminRoleUserAndAssignment.ps1 create mode 100644 source/Private/Get-MgOutput.ps1 create mode 100644 tests/Unit/Private/Get-AdminRoleUserAndAssignment.tests.ps1 create mode 100644 tests/Unit/Private/Get-MgOutput.tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e5f9e..54fe1b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ The format is based on and uses the types of changes according to [Keep a Change ### Fixed - Fixed test 1.3.1 to include notification window for password expiration. +- Fixed 6.1.1 test definition to include the correct connection. ### Added - Added export to excel to `Export-M365SecurityAuditTable` function. +- `Get-AdminRoleUserLicense` function to get the license of a user with admin roles for 1.1.1. +- Skip MSOL connection confirmation to `Get-MFAStatus` function. +- Get-MgOutput function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. ## [0.1.13] - 2024-06-18 diff --git a/source/Private/Get-AdminRoleUserAndAssignment.ps1 b/source/Private/Get-AdminRoleUserAndAssignment.ps1 new file mode 100644 index 0000000..2ed74ec --- /dev/null +++ b/source/Private/Get-AdminRoleUserAndAssignment.ps1 @@ -0,0 +1,38 @@ +function Get-AdminRoleUserAndAssignment { + [CmdletBinding()] + param () + + $result = @{} + + # Get the DisplayNames of all admin roles + $adminRoleNames = (Get-MgDirectoryRole | Where-Object { $null -ne $_.RoleTemplateId }).DisplayName + + # Get Admin Roles + $adminRoles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { ($adminRoleNames -contains $_.DisplayName) -and ($_.DisplayName -ne "Directory Synchronization Accounts") } + + foreach ($role in $adminRoles) { + Write-Verbose "Processing role: $($role.DisplayName)" + $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$($role.Id)'" + + foreach ($assignment in $roleAssignments) { + Write-Verbose "Processing role assignment for principal ID: $($assignment.PrincipalId)" + $userDetails = Get-MgUser -UserId $assignment.PrincipalId -Property "DisplayName, UserPrincipalName, Id, OnPremisesSyncEnabled" -ErrorAction SilentlyContinue + + if ($userDetails) { + Write-Verbose "Retrieved user details for: $($userDetails.UserPrincipalName)" + $licenses = Get-MgUserLicenseDetail -UserId $assignment.PrincipalId -ErrorAction SilentlyContinue + + if (-not $result[$role.DisplayName]) { + $result[$role.DisplayName] = @() + } + $result[$role.DisplayName] += [PSCustomObject]@{ + AssignmentId = $assignment.Id + UserDetails = $userDetails + Licenses = $licenses + } + } + } + } + + return $result +} diff --git a/source/Private/Get-MgOutput.ps1 b/source/Private/Get-MgOutput.ps1 new file mode 100644 index 0000000..b578883 --- /dev/null +++ b/source/Private/Get-MgOutput.ps1 @@ -0,0 +1,85 @@ +function Get-MgOutput { + <# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-MgOutput -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. + +#> + [cmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [String] + $Rec + ) + + begin { + # Begin Block # + } + process { + switch ($rec) { + '1.1.3' { + # Step: Retrieve global admin role + $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" + # Step: Retrieve global admin members + $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id + return $globalAdmins + } + '1.2.1' { + $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility + return $allGroups + } + '5.1.2.3' { + # Retrieve the tenant creation policy + $tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants + return $tenantCreationPolicy + } + '5.1.8.1' { + # Retrieve password hash sync status (Condition A and C) + $passwordHashSync = Get-MgOrganization | Select-Object -ExpandProperty OnPremisesSyncEnabled + return $passwordHashSync + } + '6.1.2' { + $tenantSkus = Get-MgSubscribedSku -All + $e3SkuPartNumber = "SPE_E3" + $founde3Sku = $tenantSkus | Where-Object { $_.SkuPartNumber -eq $e3SkuPartNumber } + if ($founde3Sku.Count -ne 0) { + $allE3Users = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde3Sku.SkuId) )" -All + return $allE3Users + } + else { + return $null + } + } + '6.1.3' { + $tenantSkus = Get-MgSubscribedSku -All + $e5SkuPartNumber = "SPE_E5" + $founde5Sku = $tenantSkus | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber } + if ($founde5Sku.Count -ne 0) { + $allE5Users = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde5Sku.SkuId) )" -All + return $allE5Users + } + else { + return $null + } + } + Default { + # 1.1.1 + $AdminRoleAssignmentsAndUsers = Get-AdminRoleUserAndAssignment + return $AdminRoleAssignmentsAndUsers + } + } + } + end { + Write-Verbose "Retuning data for Rec: $Rec" + } +} # end function Get-MgOutput + diff --git a/source/tests/Test-AdministrativeAccountCompliance.ps1 b/source/tests/Test-AdministrativeAccountCompliance.ps1 index e4644ad..cf0d390 100644 --- a/source/tests/Test-AdministrativeAccountCompliance.ps1 +++ b/source/tests/Test-AdministrativeAccountCompliance.ps1 @@ -1,76 +1,59 @@ function Test-AdministrativeAccountCompliance { [CmdletBinding()] - param ( - # Aligned - # Parameters can be added if needed - ) + param () begin { # The following conditions are checked: # Condition A: The administrative account is cloud-only (not synced). # Condition B: The account is assigned a valid license (e.g., Microsoft Entra ID P1 or P2). # Condition C: The administrative account does not have any other application assignments (only valid licenses). - $validLicenses = @('AAD_PREMIUM', 'AAD_PREMIUM_P2') $recnum = "1.1.1" Write-Verbose "Starting Test-AdministrativeAccountCompliance with Rec: $recnum" } process { - try { - # Retrieve all admin roles - Write-Verbose "Retrieving all admin roles" - # Get the DisplayNames of all admin roles - $adminRoleNames = (Get-MgDirectoryRole | Where-Object { $null -ne $_.RoleTemplateId }).DisplayName - # Use the DisplayNames to filter the roles in Get-MgRoleManagementDirectoryRoleDefinition - $adminRoles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { ($adminRoleNames -contains $_.DisplayName) -and ($_.DisplayName -ne "Directory Synchronization Accounts")} + try { + # Retrieve admin roles, assignments, and user details including licenses + Write-Verbose "Retrieving admin roles, assignments, and user details including licenses" + $adminRoleAssignments = Get-MgOutput -Rec $recnum $adminRoleUsers = @() - # Loop through each admin role to get role assignments and user details - foreach ($role in $adminRoles) { - Write-Verbose "Processing role: $($role.DisplayName)" - $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$($role.Id)'" + foreach ($roleName in $adminRoleAssignments.Keys) { + $assignments = $adminRoleAssignments[$roleName] + foreach ($assignment in $assignments) { + $userDetails = $assignment.UserDetails + $userId = $userDetails.Id + $userPrincipalName = $userDetails.UserPrincipalName + $licenses = $assignment.Licenses + $licenseString = if ($licenses) { ($licenses.SkuPartNumber -join '|') } else { "No Licenses Found" } - foreach ($assignment in $roleAssignments) { - Write-Verbose "Processing role assignment for principal ID: $($assignment.PrincipalId)" - # Get user details for each principal ID - $userDetails = Get-MgUser -UserId $assignment.PrincipalId -Property "DisplayName, UserPrincipalName, Id, OnPremisesSyncEnabled" -ErrorAction SilentlyContinue - if ($userDetails) { - Write-Verbose "Retrieved user details for: $($userDetails.UserPrincipalName)" - # Get user license details - $licenses = Get-MgUserLicenseDetail -UserId $assignment.PrincipalId -ErrorAction SilentlyContinue - $licenseString = if ($licenses) { ($licenses.SkuPartNumber -join '|') } else { "No Licenses Found" } + # Condition A: Check if the account is cloud-only + $cloudOnlyStatus = if ($userDetails.OnPremisesSyncEnabled) { "Fail" } else { "Pass" } - # Condition A: Check if the account is cloud-only - $cloudOnlyStatus = if ($userDetails.OnPremisesSyncEnabled) { "Fail" } else { "Pass" } + # Condition B: Check if the account has valid licenses + $hasValidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -contains $_ } + $validLicensesStatus = if ($hasValidLicense) { "Pass" } else { "Fail" } - # Condition B: Check if the account has valid licenses - $hasValidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -contains $_ } - $validLicensesStatus = if ($hasValidLicense) { "Pass" } else { "Fail" } + # Condition C: Check if the account has no other licenses + $hasInvalidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -notcontains $_ } + $invalidLicenses = $licenses.SkuPartNumber | Where-Object { $validLicenses -notcontains $_ } + $applicationAssignmentStatus = if ($hasInvalidLicense) { "Fail" } else { "Pass" } - # Condition C: Check if the account has no other licenses - $hasInvalidLicense = $licenses.SkuPartNumber | ForEach-Object { $validLicenses -notcontains $_ } - $invalidLicenses = $licenses.SkuPartNumber | Where-Object { $validLicenses -notcontains $_ } - $applicationAssignmentStatus = if ($hasInvalidLicense) { "Fail" } else { "Pass" } + Write-Verbose "User: $userPrincipalName, Cloud-Only: $cloudOnlyStatus, Valid Licenses: $validLicensesStatus, Invalid Licenses: $($invalidLicenses -join ', ')" - Write-Verbose "User: $($userDetails.UserPrincipalName), Cloud-Only: $cloudOnlyStatus, Valid Licenses: $validLicensesStatus, Invalid Licenses: $($invalidLicenses -join ', ')" - - # Collect user information - $adminRoleUsers += [PSCustomObject]@{ - UserName = $userDetails.UserPrincipalName - RoleName = $role.DisplayName - UserId = $userDetails.Id - HybridUser = $userDetails.OnPremisesSyncEnabled - Licenses = $licenseString - CloudOnlyStatus = $cloudOnlyStatus - ValidLicensesStatus = $validLicensesStatus - ApplicationAssignmentStatus = $applicationAssignmentStatus - } - } - else { - Write-Verbose "No user details found for principal ID: $($assignment.PrincipalId)" + # Collect user information + $adminRoleUsers += [PSCustomObject]@{ + UserName = $userPrincipalName + RoleName = $roleName + UserId = $userId + HybridUser = $userDetails.OnPremisesSyncEnabled + Licenses = $licenseString + CloudOnlyStatus = $cloudOnlyStatus + ValidLicensesStatus = $validLicensesStatus + ApplicationAssignmentStatus = $applicationAssignmentStatus } } } diff --git a/source/tests/Test-GlobalAdminsCount.ps1 b/source/tests/Test-GlobalAdminsCount.ps1 index 865d02e..e52117a 100644 --- a/source/tests/Test-GlobalAdminsCount.ps1 +++ b/source/tests/Test-GlobalAdminsCount.ps1 @@ -30,11 +30,7 @@ function Test-GlobalAdminsCount { process { try { - # Step: Retrieve global admin role - $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" - - # Step: Retrieve global admin members - $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id + $globalAdmins = Get-MgOutput -Rec $recnum # Step: Count the number of global admins $globalAdminCount = $globalAdmins.Count diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 4f30450..33be83f 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -29,7 +29,6 @@ function Test-MailboxAuditingE3 { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - $e3SkuPartNumber = "SPE_E3" $actionDictionaries = Get-Action -Dictionaries # E3 specific actions @@ -38,14 +37,14 @@ function Test-MailboxAuditingE3 { $OwnerActions = $actionDictionaries.OwnerActions.Keys | Where-Object { $_ -notin @("MailItemsAccessed", "Send") } $allFailures = @() - $founde3Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq $e3SkuPartNumber } - $processedUsers = @{} # Dictionary to track processed users $recnum = "6.1.2" + $allUsers = Get-MgOutput -Rec $recnum + $processedUsers = @{} # Dictionary to track processed users + } process { - if ($founde3Sku.Count -ne 0) { - $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde3Sku.SkuId) )" -All + if ($null -ne $allUsers) { $mailboxes = Get-EXOMailbox -PropertySets Audit try { foreach ($user in $allUsers) { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index adca387..24adda4 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -27,9 +27,6 @@ function Test-MailboxAuditingE5 { # - Condition C: AuditDelegate actions do not include all of the following: ApplyRecord, Create, HardDelete, MailItemsAccessed, MoveToDeletedItems, SendAs, SendOnBehalf, SoftDelete, Update, UpdateFolderPermissions, UpdateInboxRules. # - Condition D: AuditOwner actions do not include all of the following: ApplyRecord, HardDelete, MailItemsAccessed, MoveToDeletedItems, Send, SoftDelete, Update, UpdateCalendarDelegation, UpdateFolderPermissions, UpdateInboxRules. - $e5SkuPartNumber = "SPE_E5" - $founde5Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber } - $actionDictionaries = Get-Action -Dictionaries $AdminActions = $actionDictionaries.AdminActions.Keys $DelegateActions = $actionDictionaries.DelegateActions.Keys @@ -38,11 +35,11 @@ function Test-MailboxAuditingE5 { $allFailures = @() $processedUsers = @{} $recnum = "6.1.3" + $allUsers = Get-MgOutput -Rec $recnum } process { - if (($founde5Sku.count) -ne 0) { - $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde5Sku.SkuId) )" -All + if ($null -ne $allUsers) { $mailboxes = Get-EXOMailbox -PropertySets Audit try { foreach ($user in $allUsers) { diff --git a/source/tests/Test-ManagedApprovedPublicGroups.ps1 b/source/tests/Test-ManagedApprovedPublicGroups.ps1 index 829f202..0563ed5 100644 --- a/source/tests/Test-ManagedApprovedPublicGroups.ps1 +++ b/source/tests/Test-ManagedApprovedPublicGroups.ps1 @@ -30,7 +30,7 @@ function Test-ManagedApprovedPublicGroups { process { try { # Step: Retrieve all groups with visibility set to 'Public' - $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility + $allGroups = Get-MgOutput -Rec $recnum # Step: Determine failure reasons based on the presence of public groups $failureReasons = if ($null -ne $allGroups -and $allGroups.Count -gt 0) { diff --git a/source/tests/Test-PasswordHashSync.ps1 b/source/tests/Test-PasswordHashSync.ps1 index 1d8362e..8c1e1b1 100644 --- a/source/tests/Test-PasswordHashSync.ps1 +++ b/source/tests/Test-PasswordHashSync.ps1 @@ -34,7 +34,7 @@ function Test-PasswordHashSync { # 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments # Retrieve password hash sync status (Condition A and C) - $passwordHashSync = Get-MgOrganization | Select-Object -ExpandProperty OnPremisesSyncEnabled + $passwordHashSync = Get-MgOutput -Rec $recnum $hashSyncResult = $passwordHashSync # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-RestrictTenantCreation.ps1 b/source/tests/Test-RestrictTenantCreation.ps1 index 6d3c314..c1a7c4e 100644 --- a/source/tests/Test-RestrictTenantCreation.ps1 +++ b/source/tests/Test-RestrictTenantCreation.ps1 @@ -35,7 +35,7 @@ function Test-RestrictTenantCreation { # 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 + $tenantCreationPolicy = Get-MgOutput -Rec $recnum $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants # Prepare failure reasons and details based on compliance diff --git a/tests/Unit/Private/Get-AdminRoleUserAndAssignment.tests.ps1 b/tests/Unit/Private/Get-AdminRoleUserAndAssignment.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-AdminRoleUserAndAssignment.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-MgOutput.tests.ps1 b/tests/Unit/Private/Get-MgOutput.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-MgOutput.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' + } + } + } +} + From 5ff23962182cd06aed497989e590870974e991da Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 11:46:00 -0500 Subject: [PATCH 09/42] fix: Removed banner from EXO connection step --- CHANGELOG.md | 1 + source/Private/Connect-M365Suite.ps1 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fe1b8..220f468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed test 1.3.1 to include notification window for password expiration. - Fixed 6.1.1 test definition to include the correct connection. +- Removed banner from EXO connection step. ### Added diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index ee7b7c4..741444c 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -59,7 +59,7 @@ function Connect-M365Suite { if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "EXO | Microsoft Graph") { Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan - Connect-ExchangeOnline | Out-Null + Connect-ExchangeOnline -ShowBanner $false | Out-Null $exoTenant = (Get-OrganizationConfig).Identity $tenantInfo += [PSCustomObject]@{ Service = "Exchange Online" From b564458ed1ca4020b9ec572cb6bf10e19e3e531a Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:31:35 -0500 Subject: [PATCH 10/42] fix: Removed banner fix and included AzureAD --- source/Private/Connect-M365Suite.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index 741444c..61f372c 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -18,9 +18,9 @@ function Connect-M365Suite { try { 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 - $tenantDetails = Get-AzureADTenantDetail + Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Yellow + Connect-AzureAD -WarningAction SilentlyContinue | Out-Null + $tenantDetails = Get-AzureADTenantDetail -WarningAction SilentlyContinue $tenantInfo += [PSCustomObject]@{ Service = "Azure Active Directory" TenantName = $tenantDetails.DisplayName @@ -31,7 +31,7 @@ function Connect-M365Suite { } if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "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 + Write-Host "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Yellow try { Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null $graphOrgDetails = Get-MgOrganization @@ -58,8 +58,8 @@ function Connect-M365Suite { } if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "EXO | Microsoft Graph") { - Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan - Connect-ExchangeOnline -ShowBanner $false | Out-Null + Write-Host "Connecting to Exchange Online..." -ForegroundColor Yellow + Connect-ExchangeOnline -ShowBanner:$false | Out-Null $exoTenant = (Get-OrganizationConfig).Identity $tenantInfo += [PSCustomObject]@{ Service = "Exchange Online" @@ -71,7 +71,7 @@ function Connect-M365Suite { } if ($RequiredConnections -contains "SPO") { - Write-Host "Connecting to SharePoint Online..." -ForegroundColor Cyan + Write-Host "Connecting to SharePoint Online..." -ForegroundColor Yellow Connect-SPOService -Url $TenantAdminUrl | Out-Null $spoContext = Get-SPOSite -Limit 1 $tenantInfo += [PSCustomObject]@{ @@ -84,7 +84,7 @@ function Connect-M365Suite { } if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") { - Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Cyan + Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Yellow Connect-MicrosoftTeams | Out-Null $teamsTenantDetails = Get-CsTenant $tenantInfo += [PSCustomObject]@{ From 736997fbbe44a47bfea51149030de0524633f733 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:33:57 -0500 Subject: [PATCH 11/42] add: Get-ExoOutput function and modified tests: '1.2.2', '1.3.3', '1.3.6', '2.1.1' --- source/Private/Get-ExoOutput.ps1 | 103 ++++++++++++++++++ .../tests/Test-BlockSharedMailboxSignIn.ps1 | 2 +- source/tests/Test-CustomerLockbox.ps1 | 3 +- .../tests/Test-ExternalSharingCalendars.ps1 | 2 +- source/tests/Test-SafeLinksOfficeApps.ps1 | 31 +----- tests/Unit/Private/Get-ExoOutput.tests.ps1 | 27 +++++ 6 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 source/Private/Get-ExoOutput.ps1 create mode 100644 tests/Unit/Private/Get-ExoOutput.tests.ps1 diff --git a/source/Private/Get-ExoOutput.ps1 b/source/Private/Get-ExoOutput.ps1 new file mode 100644 index 0000000..f5b4878 --- /dev/null +++ b/source/Private/Get-ExoOutput.ps1 @@ -0,0 +1,103 @@ +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-ExoOutput -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. + +#> +function Get-ExoOutput { + [cmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [String] + $Rec + ) + + begin { + # Begin Block # + } + process { + switch ($Rec) { + '1.2.2' { + $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox + return $MBX + } + '1.3.3' { + # Step: Retrieve sharing policies related to calendar sharing + $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } + return $sharingPolicies + } + '1.3.6' { + # Step: Retrieve the organization configuration (Condition C: Pass/Fail) + $orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled + $customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled + return $customerLockboxEnabled + } + '2.1.1' { + if (Get-Command Get-SafeLinksPolicy -ErrorAction SilentlyContinue) { + # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled + # Retrieve all Safe Links policies + $policies = Get-SafeLinksPolicy + # Initialize the details collection + $misconfiguredDetails = @() + + foreach ($policy in $policies) { + # Get the detailed configuration of each policy + $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name + + # Check each required property and record failures + # Condition A: Checking policy settings + $failures = @() + if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } # Email: On + if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } # Teams: On + if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } # Office 365 Apps: On + if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } # Click protection settings: On + if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } # Do not track when users click safe links: Off + + # Only add details for policies that have misconfigurations + if ($failures.Count -gt 0) { + $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" + } + } + return $misconfiguredDetails + } + else { + return 1 + } + + } + '2.1.2' { Write-Output "Matched 2.1.2" } + '2.1.3' { Write-Output "Matched 2.1.3" } + '2.1.4' { Write-Output "Matched 2.1.4" } + '2.1.5' { Write-Output "Matched 2.1.5" } + '2.1.6' { Write-Output "Matched 2.1.6" } + '2.1.7' { Write-Output "Matched 2.1.7" } + '2.1.9' { Write-Output "Matched 2.1.9" } + '3.1.1' { Write-Output "Matched 3.1.1" } + '6.1.1' { Write-Output "Matched 6.1.1" } + '6.1.2' { Write-Output "Matched 6.1.2" } + '6.1.3' { Write-Output "Matched 6.1.3" } + '6.2.1' { Write-Output "Matched 6.2.1" } + '6.2.2' { Write-Output "Matched 6.2.2" } + '6.2.3' { Write-Output "Matched 6.2.3" } + '6.3.1' { Write-Output "Matched 6.3.1" } + '6.5.1' { Write-Output "Matched 6.5.1" } + '6.5.2' { Write-Output "Matched 6.5.2" } + '6.5.3' { Write-Output "Matched 6.5.3" } + '8.6.1' { Write-Output "Matched 8.6.1" } + default { Write-Output "No match found" } + } + } + end { + Write-Verbose "Retuning data for Rec: $Rec" + } +} # end function Get-MgOutput + diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index db217e1..a304952 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -30,7 +30,7 @@ function Test-BlockSharedMailboxSignIn { process { try { # Step: Retrieve shared mailbox details - $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox + $MBX = Get-ExoOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index cc67246..3796fbd 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -33,8 +33,7 @@ function Test-CustomerLockbox { process { try { # Step: Retrieve the organization configuration (Condition C: Pass/Fail) - $orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled - $customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled + $customerLockboxEnabled = Get-ExoOutput -Rec $recnum # Step: Prepare failure reasons and details based on compliance (Condition A, B, & C: Fail) $failureReasons = if (-not $customerLockboxEnabled) { diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index 2b1946e..df487f0 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -31,7 +31,7 @@ function Test-ExternalSharingCalendars { process { try { # Step: Retrieve sharing policies related to calendar sharing - $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } + $sharingPolicies = Get-ExoOutput -Rec $recnum # Step (Condition A & B: Pass/Fail): Check if calendar sharing is disabled in all applicable policies $isExternalSharingDisabled = $true diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 8bd329b..2388f31 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -40,33 +40,12 @@ function Test-SafeLinksOfficeApps { } process { - if (Get-Command Get-SafeLinksPolicy -ErrorAction SilentlyContinue) { + # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled + # Retrieve all Safe Links policies + $misconfiguredDetails = Get-ExoOutput -Rec $recnum + # Misconfigured details returns 1 if EXO Commands needed for the test are not available + if ($misconfiguredDetails -ne 1) { try { - # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled - # Retrieve all Safe Links policies - $policies = Get-SafeLinksPolicy - # Initialize the details collection - $misconfiguredDetails = @() - - foreach ($policy in $policies) { - # Get the detailed configuration of each policy - $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name - - # Check each required property and record failures - # Condition A: Checking policy settings - $failures = @() - if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } # Email: On - if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } # Teams: On - if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } # Office 365 Apps: On - if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } # Click protection settings: On - if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } # Do not track when users click safe links: Off - - # Only add details for policies that have misconfigurations - if ($failures.Count -gt 0) { - $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" - } - } - # Prepare the final result # Condition B: Ensuring no misconfigurations $result = $misconfiguredDetails.Count -eq 0 diff --git a/tests/Unit/Private/Get-ExoOutput.tests.ps1 b/tests/Unit/Private/Get-ExoOutput.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-ExoOutput.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' + } + } + } +} + From c5780450e751adda5f70735ff029fdb28fd067ce Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:34:15 -0500 Subject: [PATCH 12/42] docs: Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 220f468..67328ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed test 1.3.1 to include notification window for password expiration. - Fixed 6.1.1 test definition to include the correct connection. -- Removed banner from EXO connection step. +- Removed banner and warning from EXO and AzureAD connection step. ### Added @@ -16,6 +16,7 @@ The format is based on and uses the types of changes according to [Keep a Change - `Get-AdminRoleUserLicense` function to get the license of a user with admin roles for 1.1.1. - Skip MSOL connection confirmation to `Get-MFAStatus` function. - Get-MgOutput function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. +- Added `Get-ExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. ## [0.1.13] - 2024-06-18 From 90c5b95f35d6a08c61358ae9045265d09492ed0f Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:43:52 -0500 Subject: [PATCH 13/42] fix: missing output type comments for switches --- source/Private/Get-ExoOutput.ps1 | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/Private/Get-ExoOutput.ps1 b/source/Private/Get-ExoOutput.ps1 index f5b4878..6300f3b 100644 --- a/source/Private/Get-ExoOutput.ps1 +++ b/source/Private/Get-ExoOutput.ps1 @@ -27,21 +27,28 @@ function Get-ExoOutput { process { switch ($Rec) { '1.2.2' { + # Test-BlockSharedMailboxSignIn.ps1 $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox + # [object[]] return $MBX } '1.3.3' { + # Test-ExternalSharingCalendars.ps1 # Step: Retrieve sharing policies related to calendar sharing $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } + # [psobject[]] return $sharingPolicies } '1.3.6' { + # Test-CustomerLockbox.ps1 # Step: Retrieve the organization configuration (Condition C: Pass/Fail) $orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled $customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled + # [bool] return $customerLockboxEnabled } '2.1.1' { + # Test-SafeLinksOfficeApps.ps1 if (Get-Command Get-SafeLinksPolicy -ErrorAction SilentlyContinue) { # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled # Retrieve all Safe Links policies @@ -67,6 +74,7 @@ function Get-ExoOutput { $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" } } + # [object[]] return $misconfiguredDetails } else { @@ -74,7 +82,18 @@ function Get-ExoOutput { } } - '2.1.2' { Write-Output "Matched 2.1.2" } + '2.1.2' { + # Test-CommonAttachmentFilter.ps1 + # 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled + # Condition A: The Common Attachment Types Filter is enabled in the Microsoft 365 Security & Compliance Center. + # Condition B: Using Exchange Online PowerShell, verify that the `EnableFileFilter` property of the default malware filter policy is set to `True`. + + # Retrieve the attachment filter policy + $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter + $result = $attachmentFilter.EnableFileFilter + # [bool] + return $result + } '2.1.3' { Write-Output "Matched 2.1.3" } '2.1.4' { Write-Output "Matched 2.1.4" } '2.1.5' { Write-Output "Matched 2.1.5" } From 381b8ebeb8a6d7ae80aaa155db016f2ce6eaa78e Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:28:51 -0500 Subject: [PATCH 14/42] add: Get-ExoOutput function and updated tests --- source/Private/Get-ExoOutput.ps1 | 235 ++++++++++++++++-- source/tests/Test-AntiPhishingPolicy.ps1 | 2 +- source/tests/Test-AuditDisabledFalse.ps1 | 3 +- source/tests/Test-AuditLogSearch.ps1 | 4 +- source/tests/Test-BlockMailForwarding.ps1 | 6 +- source/tests/Test-CommonAttachmentFilter.ps1 | 3 +- source/tests/Test-EnableDKIM.ps1 | 2 +- source/tests/Test-IdentifyExternalEmail.ps1 | 2 +- source/tests/Test-MailTipsEnabled.ps1 | 2 +- source/tests/Test-MailboxAuditingE3.ps1 | 2 +- source/tests/Test-MailboxAuditingE5.ps1 | 2 +- .../tests/Test-ModernAuthExchangeOnline.ps1 | 4 +- source/tests/Test-NoWhitelistDomains.ps1 | 3 +- source/tests/Test-NotifyMalwareInternal.ps1 | 2 +- source/tests/Test-ReportSecurityInTeams.ps1 | 5 +- source/tests/Test-RestrictOutlookAddins.ps1 | 19 +- .../Test-RestrictStorageProvidersOutlook.ps1 | 2 +- source/tests/Test-SafeAttachmentsPolicy.ps1 | 5 +- source/tests/Test-SafeAttachmentsTeams.ps1 | 13 +- source/tests/Test-SpamPolicyAdminNotify.ps1 | 2 +- 20 files changed, 239 insertions(+), 79 deletions(-) diff --git a/source/Private/Get-ExoOutput.ps1 b/source/Private/Get-ExoOutput.ps1 index 6300f3b..224e5eb 100644 --- a/source/Private/Get-ExoOutput.ps1 +++ b/source/Private/Get-ExoOutput.ps1 @@ -23,6 +23,33 @@ function Get-ExoOutput { begin { # Begin Block # + <# + 1.2.2 + 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 + 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 + 8.6.1 + + $testNumbers = @('1.2.2', '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', '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', '8.6.1') + #> } process { switch ($Rec) { @@ -94,25 +121,195 @@ function Get-ExoOutput { # [bool] return $result } - '2.1.3' { Write-Output "Matched 2.1.3" } - '2.1.4' { Write-Output "Matched 2.1.4" } - '2.1.5' { Write-Output "Matched 2.1.5" } - '2.1.6' { Write-Output "Matched 2.1.6" } - '2.1.7' { Write-Output "Matched 2.1.7" } - '2.1.9' { Write-Output "Matched 2.1.9" } - '3.1.1' { Write-Output "Matched 3.1.1" } - '6.1.1' { Write-Output "Matched 6.1.1" } - '6.1.2' { Write-Output "Matched 6.1.2" } - '6.1.3' { Write-Output "Matched 6.1.3" } - '6.2.1' { Write-Output "Matched 6.2.1" } - '6.2.2' { Write-Output "Matched 6.2.2" } - '6.2.3' { Write-Output "Matched 6.2.3" } - '6.3.1' { Write-Output "Matched 6.3.1" } - '6.5.1' { Write-Output "Matched 6.5.1" } - '6.5.2' { Write-Output "Matched 6.5.2" } - '6.5.3' { Write-Output "Matched 6.5.3" } - '8.6.1' { Write-Output "Matched 8.6.1" } - default { Write-Output "No match found" } + '2.1.3' { + # Test-NotifyMalwareInternal.ps1 + # 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' } + # [object[]] + return $malwareNotifications + } + '2.1.4' { + # Test-SafeAttachmentsPolicy.ps1 + if (Get-Command Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue) { + # Retrieve all Safe Attachment policies where Enable is set to True + # Check if ErrorAction needed below + $safeAttachmentPolicies = Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue | Where-Object { $_.Enable -eq $true } + # [object[]] + return $safeAttachmentPolicies + else { + return 1 + } + } + } + '2.1.5' { + # Test-SafeAttachmentsTeams.ps1 + if (Get-Command Get-AtpPolicyForO365 -ErrorAction SilentlyContinue) { + # 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 + # Check if the required ATP policies are enabled + $atpPolicyResult = $atpPolicies | Where-Object { + $_.EnableATPForSPOTeamsODB -eq $true -and + $_.EnableSafeDocs -eq $true -and + $_.AllowSafeDocsOpen -eq $false + } + # [psobject[]] + return $atpPolicyResult + } + else { + return 1 + } + } + '2.1.6' { + # Test-SpamPolicyAdminNotify.ps1 + # Retrieve the default hosted outbound spam filter policy + $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } + return $hostedOutboundSpamFilterPolicy + + } + '2.1.7' { + # Test-AntiPhishingPolicy.ps1 + # Condition A: Ensure that an anti-phishing policy has been created + $antiPhishPolicies = Get-AntiPhishPolicy + return $antiPhishPolicies + } + '2.1.9' { + # Test-EnableDKIM.ps1 + # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains + + # Retrieve DKIM configuration for all domains + $dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled + # [object[]] + return $dkimConfig + } + '3.1.1' { + # Test-AuditLogSearch.ps1 + # 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 + # [bool] + return $auditLogResult + } + '6.1.1' { + # Test-AuditDisabledFalse.ps1 + # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' + + # Retrieve the AuditDisabled configuration (Condition B) + $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled + # [bool] + $auditNotDisabled = -not $auditDisabledConfig.AuditDisabled + return $auditNotDisabled + } + '6.1.2' { + # Test-MailboxAuditingE3.ps1 + $mailboxes = Get-EXOMailbox -PropertySets Audit + # [object[]] + return $mailboxes + } + '6.1.3' { + # Test-MailboxAuditingE5.ps1 + $mailboxes = Get-EXOMailbox -PropertySets Audit + # [object[]] + return $mailboxes + } + '6.2.1' { + # Test-BlockMailForwarding.ps1 + # 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled + # Step 1: Retrieve the transport rules that redirect messages + $transportRules = Get-TransportRule | Where-Object { $null -ne $_.RedirectMessageTo } + if ($null -eq $transportRules) { + $transportRules = 1 + } + # Step 2: Check all anti-spam outbound policies + $outboundSpamPolicies = Get-HostedOutboundSpamFilterPolicy + $nonCompliantSpamPolicies = $outboundSpamPolicies | Where-Object { $_.AutoForwardingMode -ne 'Off' } + return $transportRules, $nonCompliantSpamPolicies + + } + '6.2.2' { + # Test-NoWhitelistDomains.ps1 + # 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains + + # Retrieve transport rules that whitelist specific domains + # Condition A: Checking for transport rules that whitelist specific domains + # [object[]] + $whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $null -ne $_.SenderDomainIs } + return $whitelistedRules + } + '6.2.3' { + # Test-IdentifyExternalEmail.ps1 + # 6.2.3 (L1) Ensure email from external senders is identified + + # Retrieve external sender tagging configuration + # [object[]] + $externalInOutlook = Get-ExternalInOutlook + return $externalInOutlook + } + '6.3.1' { + # Test-RestrictOutlookAddins.ps1 + # 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed + $customPolicyFailures = @() + # 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 } + + # Condition B: Using PowerShell, verify that MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are not assigned to users. + if ($foundRoles) { + $customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')" + } + } + } + } + # Check Default Role Assignment Policy + $defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy" + return $customPolicyFailures, $defaultPolicy + } + '6.5.1' { + # Test-ModernAuthExchangeOnline.ps1 + # Ensuring the ExchangeOnlineManagement module is available + # 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled + + # Check modern authentication setting in Exchange Online configuration (Condition A and B) + $orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled + return $orgConfig + } + '6.5.2' { + # Test-MailTipsEnabled.ps1 + # 6.5.2 (L2) Ensure MailTips are enabled for end users + # Retrieve organization configuration for MailTips settings + # [object] + $orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold + return $orgConfig + } + '6.5.3' { + # Test-RestrictStorageProvidersOutlook.ps1 + # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web + # Retrieve all OwaMailbox policies + # [object[]] + $owaPolicies = Get-OwaMailboxPolicy + return $owaPolicies + } + '8.6.1' { + # Test-ReportSecurityInTeams.ps1 + # 8.6.1 (L1) Ensure users can report security concerns in Teams + + # Retrieve the necessary settings for Teams and Exchange Online + # Condition B: Verify that 'Monitor reported messages in Microsoft Teams' is checked in the Microsoft 365 Defender portal. + # Condition C: Ensure the 'Send reported messages to' setting in the Microsoft 365 Defender portal is set to 'My reporting mailbox only' with the correct report email addresses. + $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled + return $ReportSubmissionPolicy + } + default { throw "No match found for test: $Rec" } } } end { diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 24cb320..843bc12 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -34,7 +34,7 @@ function Test-AntiPhishingPolicy { try { # Condition A: Ensure that an anti-phishing policy has been created - $antiPhishPolicies = Get-AntiPhishPolicy + $antiPhishPolicies = Get-ExoOutput -Rec $recnum # Condition B: Verify the anti-phishing policy settings using PowerShell $validatedPolicies = $antiPhishPolicies | Where-Object { diff --git a/source/tests/Test-AuditDisabledFalse.ps1 b/source/tests/Test-AuditDisabledFalse.ps1 index 263e929..edd7f11 100644 --- a/source/tests/Test-AuditDisabledFalse.ps1 +++ b/source/tests/Test-AuditDisabledFalse.ps1 @@ -35,8 +35,7 @@ function Test-AuditDisabledFalse { # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' # Retrieve the AuditDisabled configuration (Condition B) - $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled - $auditNotDisabled = -not $auditDisabledConfig.AuditDisabled + $auditNotDisabled = Get-ExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditNotDisabled) { diff --git a/source/tests/Test-AuditLogSearch.ps1 b/source/tests/Test-AuditLogSearch.ps1 index 90e828e..578138b 100644 --- a/source/tests/Test-AuditLogSearch.ps1 +++ b/source/tests/Test-AuditLogSearch.ps1 @@ -36,9 +36,7 @@ function Test-AuditLogSearch { try { # 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 + $auditLogResult = Get-ExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditLogResult) { diff --git a/source/tests/Test-BlockMailForwarding.ps1 b/source/tests/Test-BlockMailForwarding.ps1 index 4dab7a9..d9de8f6 100644 --- a/source/tests/Test-BlockMailForwarding.ps1 +++ b/source/tests/Test-BlockMailForwarding.ps1 @@ -35,12 +35,10 @@ function Test-BlockMailForwarding { # 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled # Step 1: Retrieve the transport rules that redirect messages - $transportRules = Get-TransportRule | Where-Object { $null -ne $_.RedirectMessageTo } + $transportRules,$nonCompliantSpamPolicies = Get-ExoOutput -Rec $recnum $transportForwardingBlocked = $transportRules.Count -eq 0 # 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 @@ -51,7 +49,7 @@ function Test-BlockMailForwarding { $failureReasons = @() $details = @() - if ($transportRules.Count -gt 0) { + if ($transportRules -ne 1) { # Fail Condition A $failureReasons += "Mail forwarding rules found: $($transportRules.Name -join ', ')" $details += "Transport Rules Details:`nRule Name|Redirects To" diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 0384880..0840121 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -38,8 +38,7 @@ function Test-CommonAttachmentFilter { # Condition B: Using Exchange Online PowerShell, verify that the `EnableFileFilter` property of the default malware filter policy is set to `True`. # Retrieve the attachment filter policy - $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter - $result = $attachmentFilter.EnableFileFilter + $result = Get-ExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $result) { diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index e686dd7..7c55c4a 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -36,7 +36,7 @@ function Test-EnableDKIM { # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains # Retrieve DKIM configuration for all domains - $dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled + $dkimConfig = Get-ExoOutput -Rec $recnum $dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false $dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain } diff --git a/source/tests/Test-IdentifyExternalEmail.ps1 b/source/tests/Test-IdentifyExternalEmail.ps1 index 21f96ca..b8920ec 100644 --- a/source/tests/Test-IdentifyExternalEmail.ps1 +++ b/source/tests/Test-IdentifyExternalEmail.ps1 @@ -36,7 +36,7 @@ function Test-IdentifyExternalEmail { # 6.2.3 (L1) Ensure email from external senders is identified # Retrieve external sender tagging configuration - $externalInOutlook = Get-ExternalInOutlook + $externalInOutlook = Get-ExoOutput -Rec $recnum $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-MailTipsEnabled.ps1 b/source/tests/Test-MailTipsEnabled.ps1 index 4869fe8..94197c0 100644 --- a/source/tests/Test-MailTipsEnabled.ps1 +++ b/source/tests/Test-MailTipsEnabled.ps1 @@ -38,7 +38,7 @@ function Test-MailTipsEnabled { # 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 + $orgConfig = Get-ExoOutput -Rec $recnum # Check the MailTips settings (Conditions A, B, C, D) $allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25 diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 33be83f..ba8198c 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -45,7 +45,7 @@ function Test-MailboxAuditingE3 { process { if ($null -ne $allUsers) { - $mailboxes = Get-EXOMailbox -PropertySets Audit + $mailboxes = Get-ExoOutput -Rec $recnum try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index 24adda4..6c218e0 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -40,7 +40,7 @@ function Test-MailboxAuditingE5 { process { if ($null -ne $allUsers) { - $mailboxes = Get-EXOMailbox -PropertySets Audit + $mailboxes = Get-ExoOutput -Rec $recnum try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { diff --git a/source/tests/Test-ModernAuthExchangeOnline.ps1 b/source/tests/Test-ModernAuthExchangeOnline.ps1 index e856abf..f9bac64 100644 --- a/source/tests/Test-ModernAuthExchangeOnline.ps1 +++ b/source/tests/Test-ModernAuthExchangeOnline.ps1 @@ -31,12 +31,10 @@ function Test-ModernAuthExchangeOnline { process { try { - # Ensuring the ExchangeOnlineManagement module is available - # 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled # Check modern authentication setting in Exchange Online configuration (Condition A and B) - $orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled + $orgConfig = Get-ExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $orgConfig.OAuth2ClientProfileEnabled) { diff --git a/source/tests/Test-NoWhitelistDomains.ps1 b/source/tests/Test-NoWhitelistDomains.ps1 index b9c6d0b..37441bb 100644 --- a/source/tests/Test-NoWhitelistDomains.ps1 +++ b/source/tests/Test-NoWhitelistDomains.ps1 @@ -38,8 +38,7 @@ function Test-NoWhitelistDomains { # Retrieve transport rules that whitelist specific domains # Condition A: Checking for transport rules that whitelist specific domains - $whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $null -ne $_.SenderDomainIs } - + $whitelistedRules = Get-ExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance # Condition B: Prepare failure reasons based on the presence of whitelisted rules $failureReasons = if ($whitelistedRules) { diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index 6c15c9a..f2a17f3 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -34,7 +34,7 @@ function Test-NotifyMalwareInternal { # 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' } + $malwareNotifications = Get-ExoOutput -Rec $recnum # Condition B: Using PowerShell, the `NotifyInternal` property in the anti-malware policy is set to `True` and includes at least one valid email address for notifications. $policiesToReport = @() diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index 23b1c47..1b27dd7 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -16,16 +16,15 @@ function Test-ReportSecurityInTeams { process { try { + # Test-ReportSecurityInTeams.ps1 # 8.6.1 (L1) Ensure users can report security concerns in Teams # Retrieve the necessary settings for Teams and Exchange Online # Condition A: Ensure the 'Report a security concern' setting in the Teams admin center is set to 'On'. $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting - # Condition B: Verify that 'Monitor reported messages in Microsoft Teams' is checked in the Microsoft 365 Defender portal. # Condition C: Ensure the 'Send reported messages to' setting in the Microsoft 365 Defender portal is set to 'My reporting mailbox only' with the correct report email addresses. - $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled - + $ReportSubmissionPolicy = Get-ExoOutput -Rec $recnum # Check if all the required settings are enabled $securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and $ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and diff --git a/source/tests/Test-RestrictOutlookAddins.ps1 b/source/tests/Test-RestrictOutlookAddins.ps1 index 6b6eb20..a4537ca 100644 --- a/source/tests/Test-RestrictOutlookAddins.ps1 +++ b/source/tests/Test-RestrictOutlookAddins.ps1 @@ -11,7 +11,6 @@ function Test-RestrictOutlookAddins { #. .\source\Classes\CISAuditResult.ps1 # Initialization code - $customPolicyFailures = @() $defaultPolicyFailureDetails = @() $relevantRoles = @('My Custom Apps', 'My Marketplace Apps', 'My ReadWriteMailbox Apps') $recnum = "6.3.1" @@ -36,24 +35,8 @@ function Test-RestrictOutlookAddins { # 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 - - if ($roleAssignmentPolicies.RoleAssignmentPolicy) { - foreach ($policy in $roleAssignmentPolicies) { - if ($policy.RoleAssignmentPolicy) { - $rolePolicyDetails = Get-RoleAssignmentPolicy -Identity $policy.RoleAssignmentPolicy - $foundRoles = $rolePolicyDetails.AssignedRoles | Where-Object { $_ -in $relevantRoles } - - # Condition B: Using PowerShell, verify that MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are not assigned to users. - if ($foundRoles) { - $customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')" - } - } - } - } - # Check Default Role Assignment Policy - $defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy" + $customPolicyFailures, $defaultPolicy = Get-ExoOutput -Rec $recnum $defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles } # Condition A: Verify that the roles MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are unchecked under Other roles. diff --git a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 index 820796d..a5f310a 100644 --- a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 +++ b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 @@ -34,7 +34,7 @@ function Test-RestrictStorageProvidersOutlook { # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web # Retrieve all OwaMailbox policies - $owaPolicies = Get-OwaMailboxPolicy + $owaPolicies = Get-ExoOutput -Rec $recnum # Condition A: Check if AdditionalStorageProvidersAvailable is set to False $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index be2faf4..fa22622 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -28,10 +28,9 @@ function Test-SafeAttachmentsPolicy { } process { - if (Get-Command Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue) { + $safeAttachmentPolicies = Get-ExoOutput -Rec $recnum + if ($safeAttachmentPolicies -ne 1) { try { - # Retrieve all Safe Attachment policies where Enable is set to True - $safeAttachmentPolicies = Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue | Where-Object { $_.Enable -eq $true } # Check if any Safe Attachments policy is enabled (Condition A) $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index ceb7547..123cdbb 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -31,18 +31,9 @@ function Test-SafeAttachmentsTeams { } process { - if (Get-Command Get-AtpPolicyForO365 -ErrorAction SilentlyContinue) { + $atpPolicyResult = Get-ExoOutput -Rec $recnum + if ($atpPolicyResult -ne 1) { 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 - # Check if the required ATP policies are enabled - $atpPolicyResult = $atpPolicies | Where-Object { - $_.EnableATPForSPOTeamsODB -eq $true -and - $_.EnableSafeDocs -eq $true -and - $_.AllowSafeDocsOpen -eq $false - } - # Condition A: Check Safe Attachments for SharePoint # Condition B: Check Safe Attachments for OneDrive # Condition C: Check Safe Attachments for Microsoft Teams diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index 314d6fd..81116fa 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -38,7 +38,7 @@ function Test-SpamPolicyAdminNotify { # 2.1.6 Ensure Exchange Online Spam Policies are set to notify administrators # Retrieve the default hosted outbound spam filter policy - $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } + $hostedOutboundSpamFilterPolicy = Get-ExoOutput -Rec $recnum # Check if both settings are enabled (Condition A and Condition B for pass) $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail From 9a299d4bac3b3527342feaac0d1e51f20070a867 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:30:27 -0500 Subject: [PATCH 15/42] docs: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67328ff..55e083c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Skip MSOL connection confirmation to `Get-MFAStatus` function. - Get-MgOutput function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. - Added `Get-ExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. +- Updated EXO tests to utilize the new output functions ('1.2.2', '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', '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', '8.6.1'). ## [0.1.13] - 2024-06-18 From a90df5bef4bc8606ecea82590b5ac12f9827024a Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:34:26 -0500 Subject: [PATCH 16/42] docs: Update Formatting --- source/Private/Get-ExoOutput.ps1 | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/source/Private/Get-ExoOutput.ps1 b/source/Private/Get-ExoOutput.ps1 index 224e5eb..98af176 100644 --- a/source/Private/Get-ExoOutput.ps1 +++ b/source/Private/Get-ExoOutput.ps1 @@ -1,16 +1,12 @@ <# .SYNOPSIS This is a sample Private function only visible within the module. - .DESCRIPTION This sample function is not exported to the module and only return the data passed as parameter. - .EXAMPLE $null = Get-ExoOutput -PrivateData 'NOTHING TO SEE HERE' - .PARAMETER PrivateData The PrivateData parameter is what will be returned without transformation. - #> function Get-ExoOutput { [cmdletBinding()] @@ -20,7 +16,6 @@ function Get-ExoOutput { [String] $Rec ) - begin { # Begin Block # <# @@ -82,11 +77,9 @@ function Get-ExoOutput { $policies = Get-SafeLinksPolicy # Initialize the details collection $misconfiguredDetails = @() - foreach ($policy in $policies) { # Get the detailed configuration of each policy $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name - # Check each required property and record failures # Condition A: Checking policy settings $failures = @() @@ -95,7 +88,6 @@ function Get-ExoOutput { if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } # Office 365 Apps: On if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } # Click protection settings: On if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } # Do not track when users click safe links: Off - # Only add details for policies that have misconfigurations if ($failures.Count -gt 0) { $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" @@ -107,14 +99,12 @@ function Get-ExoOutput { else { return 1 } - } '2.1.2' { # Test-CommonAttachmentFilter.ps1 # 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled # Condition A: The Common Attachment Types Filter is enabled in the Microsoft 365 Security & Compliance Center. # Condition B: Using Exchange Online PowerShell, verify that the `EnableFileFilter` property of the default malware filter policy is set to `True`. - # Retrieve the attachment filter policy $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter $result = $attachmentFilter.EnableFileFilter @@ -124,7 +114,6 @@ function Get-ExoOutput { '2.1.3' { # Test-NotifyMalwareInternal.ps1 # 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' } # [object[]] @@ -167,7 +156,6 @@ function Get-ExoOutput { # Retrieve the default hosted outbound spam filter policy $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } return $hostedOutboundSpamFilterPolicy - } '2.1.7' { # Test-AntiPhishingPolicy.ps1 @@ -178,7 +166,6 @@ function Get-ExoOutput { '2.1.9' { # Test-EnableDKIM.ps1 # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains - # Retrieve DKIM configuration for all domains $dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled # [object[]] @@ -187,7 +174,6 @@ function Get-ExoOutput { '3.1.1' { # Test-AuditLogSearch.ps1 # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - # Retrieve the audit log configuration $auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled # @@ -198,7 +184,6 @@ function Get-ExoOutput { '6.1.1' { # Test-AuditDisabledFalse.ps1 # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' - # Retrieve the AuditDisabled configuration (Condition B) $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled # [bool] @@ -229,12 +214,10 @@ function Get-ExoOutput { $outboundSpamPolicies = Get-HostedOutboundSpamFilterPolicy $nonCompliantSpamPolicies = $outboundSpamPolicies | Where-Object { $_.AutoForwardingMode -ne 'Off' } return $transportRules, $nonCompliantSpamPolicies - } '6.2.2' { # Test-NoWhitelistDomains.ps1 # 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains - # Retrieve transport rules that whitelist specific domains # Condition A: Checking for transport rules that whitelist specific domains # [object[]] @@ -244,7 +227,6 @@ function Get-ExoOutput { '6.2.3' { # Test-IdentifyExternalEmail.ps1 # 6.2.3 (L1) Ensure email from external senders is identified - # Retrieve external sender tagging configuration # [object[]] $externalInOutlook = Get-ExternalInOutlook @@ -256,13 +238,11 @@ function Get-ExoOutput { $customPolicyFailures = @() # 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 } - # Condition B: Using PowerShell, verify that MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are not assigned to users. if ($foundRoles) { $customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')" @@ -278,7 +258,6 @@ function Get-ExoOutput { # Test-ModernAuthExchangeOnline.ps1 # Ensuring the ExchangeOnlineManagement module is available # 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled - # Check modern authentication setting in Exchange Online configuration (Condition A and B) $orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled return $orgConfig @@ -302,7 +281,6 @@ function Get-ExoOutput { '8.6.1' { # Test-ReportSecurityInTeams.ps1 # 8.6.1 (L1) Ensure users can report security concerns in Teams - # Retrieve the necessary settings for Teams and Exchange Online # Condition B: Verify that 'Monitor reported messages in Microsoft Teams' is checked in the Microsoft 365 Defender portal. # Condition C: Ensure the 'Send reported messages to' setting in the Microsoft 365 Defender portal is set to 'My reporting mailbox only' with the correct report email addresses. From e9bac2fe1cc5aba16317422dd984c415ba89737d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:44:25 -0500 Subject: [PATCH 17/42] changed name of output functions with prefix 'CIS' --- ...Get-ExoOutput.ps1 => Get-CISExoOutput.ps1} | 6 ++-- source/Private/Get-CISMSTeamsOutput.ps1 | 32 +++++++++++++++++++ .../{Get-MgOutput.ps1 => Get-CISMgOutput.ps1} | 6 ++-- .../Test-AdministrativeAccountCompliance.ps1 | 2 +- source/tests/Test-AntiPhishingPolicy.ps1 | 2 +- source/tests/Test-AuditDisabledFalse.ps1 | 2 +- source/tests/Test-AuditLogSearch.ps1 | 2 +- source/tests/Test-BlockMailForwarding.ps1 | 2 +- .../tests/Test-BlockSharedMailboxSignIn.ps1 | 2 +- source/tests/Test-CommonAttachmentFilter.ps1 | 2 +- source/tests/Test-CustomerLockbox.ps1 | 2 +- source/tests/Test-EnableDKIM.ps1 | 2 +- .../tests/Test-ExternalSharingCalendars.ps1 | 2 +- source/tests/Test-GlobalAdminsCount.ps1 | 2 +- source/tests/Test-IdentifyExternalEmail.ps1 | 2 +- source/tests/Test-MailTipsEnabled.ps1 | 2 +- source/tests/Test-MailboxAuditingE3.ps1 | 4 +-- source/tests/Test-MailboxAuditingE5.ps1 | 4 +-- .../Test-ManagedApprovedPublicGroups.ps1 | 2 +- .../tests/Test-ModernAuthExchangeOnline.ps1 | 2 +- source/tests/Test-NoWhitelistDomains.ps1 | 2 +- source/tests/Test-NotifyMalwareInternal.ps1 | 2 +- source/tests/Test-PasswordHashSync.ps1 | 2 +- source/tests/Test-ReportSecurityInTeams.ps1 | 2 +- source/tests/Test-RestrictOutlookAddins.ps1 | 2 +- .../Test-RestrictStorageProvidersOutlook.ps1 | 2 +- source/tests/Test-RestrictTenantCreation.ps1 | 2 +- source/tests/Test-SafeAttachmentsPolicy.ps1 | 2 +- source/tests/Test-SafeAttachmentsTeams.ps1 | 2 +- source/tests/Test-SafeLinksOfficeApps.ps1 | 2 +- source/tests/Test-SpamPolicyAdminNotify.ps1 | 2 +- ...t.tests.ps1 => Get-CISExoOutput.tests.ps1} | 0 ...sts.ps1 => Get-CISMSTeamsOutput.tests.ps1} | 0 tests/Unit/Private/Get-CISMgOutput.tests.ps1 | 27 ++++++++++++++++ 34 files changed, 95 insertions(+), 36 deletions(-) rename source/Private/{Get-ExoOutput.ps1 => Get-CISExoOutput.ps1} (99%) create mode 100644 source/Private/Get-CISMSTeamsOutput.ps1 rename source/Private/{Get-MgOutput.ps1 => Get-CISMgOutput.ps1} (96%) rename tests/Unit/Private/{Get-ExoOutput.tests.ps1 => Get-CISExoOutput.tests.ps1} (100%) rename tests/Unit/Private/{Get-MgOutput.tests.ps1 => Get-CISMSTeamsOutput.tests.ps1} (100%) create mode 100644 tests/Unit/Private/Get-CISMgOutput.tests.ps1 diff --git a/source/Private/Get-ExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 similarity index 99% rename from source/Private/Get-ExoOutput.ps1 rename to source/Private/Get-CISExoOutput.ps1 index 98af176..e932c47 100644 --- a/source/Private/Get-ExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -4,11 +4,11 @@ .DESCRIPTION This sample function is not exported to the module and only return the data passed as parameter. .EXAMPLE - $null = Get-ExoOutput -PrivateData 'NOTHING TO SEE HERE' + $null = Get-CISExoOutput -PrivateData 'NOTHING TO SEE HERE' .PARAMETER PrivateData The PrivateData parameter is what will be returned without transformation. #> -function Get-ExoOutput { +function Get-CISExoOutput { [cmdletBinding()] [OutputType([string])] param( @@ -293,5 +293,5 @@ function Get-ExoOutput { end { Write-Verbose "Retuning data for Rec: $Rec" } -} # end function Get-MgOutput +} # end function Get-CISExoOutput diff --git a/source/Private/Get-CISMSTeamsOutput.ps1 b/source/Private/Get-CISMSTeamsOutput.ps1 new file mode 100644 index 0000000..70c28a6 --- /dev/null +++ b/source/Private/Get-CISMSTeamsOutput.ps1 @@ -0,0 +1,32 @@ +function Get-CISMSTeamsOutput +{ + <# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-MSTeamsOutput -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. + + #> + [cmdletBinding()] + [OutputType([string])] + param + ( + [Parameter()] + [String] + $PrivateData + ) + + process + { + Write-Output $PrivateData + } + +} + diff --git a/source/Private/Get-MgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 similarity index 96% rename from source/Private/Get-MgOutput.ps1 rename to source/Private/Get-CISMgOutput.ps1 index b578883..46e987e 100644 --- a/source/Private/Get-MgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -1,4 +1,4 @@ -function Get-MgOutput { +function Get-CISMgOutput { <# .SYNOPSIS This is a sample Private function only visible within the module. @@ -7,7 +7,7 @@ function Get-MgOutput { This sample function is not exported to the module and only return the data passed as parameter. .EXAMPLE - $null = Get-MgOutput -PrivateData 'NOTHING TO SEE HERE' + $null = Get-CISMgOutput -PrivateData 'NOTHING TO SEE HERE' .PARAMETER PrivateData The PrivateData parameter is what will be returned without transformation. @@ -81,5 +81,5 @@ function Get-MgOutput { end { Write-Verbose "Retuning data for Rec: $Rec" } -} # end function Get-MgOutput +} # end function Get-CISMgOutput diff --git a/source/tests/Test-AdministrativeAccountCompliance.ps1 b/source/tests/Test-AdministrativeAccountCompliance.ps1 index cf0d390..2f205f8 100644 --- a/source/tests/Test-AdministrativeAccountCompliance.ps1 +++ b/source/tests/Test-AdministrativeAccountCompliance.ps1 @@ -17,7 +17,7 @@ function Test-AdministrativeAccountCompliance { try { # Retrieve admin roles, assignments, and user details including licenses Write-Verbose "Retrieving admin roles, assignments, and user details including licenses" - $adminRoleAssignments = Get-MgOutput -Rec $recnum + $adminRoleAssignments = Get-CISMgOutput -Rec $recnum $adminRoleUsers = @() diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 843bc12..41bcb26 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -34,7 +34,7 @@ function Test-AntiPhishingPolicy { try { # Condition A: Ensure that an anti-phishing policy has been created - $antiPhishPolicies = Get-ExoOutput -Rec $recnum + $antiPhishPolicies = Get-CISExoOutput -Rec $recnum # Condition B: Verify the anti-phishing policy settings using PowerShell $validatedPolicies = $antiPhishPolicies | Where-Object { diff --git a/source/tests/Test-AuditDisabledFalse.ps1 b/source/tests/Test-AuditDisabledFalse.ps1 index edd7f11..0aa3de6 100644 --- a/source/tests/Test-AuditDisabledFalse.ps1 +++ b/source/tests/Test-AuditDisabledFalse.ps1 @@ -35,7 +35,7 @@ function Test-AuditDisabledFalse { # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' # Retrieve the AuditDisabled configuration (Condition B) - $auditNotDisabled = Get-ExoOutput -Rec $recnum + $auditNotDisabled = Get-CISExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditNotDisabled) { diff --git a/source/tests/Test-AuditLogSearch.ps1 b/source/tests/Test-AuditLogSearch.ps1 index 578138b..5180b6c 100644 --- a/source/tests/Test-AuditLogSearch.ps1 +++ b/source/tests/Test-AuditLogSearch.ps1 @@ -36,7 +36,7 @@ function Test-AuditLogSearch { try { # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - $auditLogResult = Get-ExoOutput -Rec $recnum + $auditLogResult = Get-CISExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditLogResult) { diff --git a/source/tests/Test-BlockMailForwarding.ps1 b/source/tests/Test-BlockMailForwarding.ps1 index d9de8f6..a46b641 100644 --- a/source/tests/Test-BlockMailForwarding.ps1 +++ b/source/tests/Test-BlockMailForwarding.ps1 @@ -35,7 +35,7 @@ function Test-BlockMailForwarding { # 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled # Step 1: Retrieve the transport rules that redirect messages - $transportRules,$nonCompliantSpamPolicies = Get-ExoOutput -Rec $recnum + $transportRules,$nonCompliantSpamPolicies = Get-CISExoOutput -Rec $recnum $transportForwardingBlocked = $transportRules.Count -eq 0 # Step 2: Check all anti-spam outbound policies diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index a304952..9025358 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -30,7 +30,7 @@ function Test-BlockSharedMailboxSignIn { process { try { # Step: Retrieve shared mailbox details - $MBX = Get-ExoOutput -Rec $recnum + $MBX = Get-CISExoOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 0840121..978f3ca 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -38,7 +38,7 @@ function Test-CommonAttachmentFilter { # Condition B: Using Exchange Online PowerShell, verify that the `EnableFileFilter` property of the default malware filter policy is set to `True`. # Retrieve the attachment filter policy - $result = Get-ExoOutput -Rec $recnum + $result = Get-CISExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $result) { diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index 3796fbd..559caaf 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -33,7 +33,7 @@ function Test-CustomerLockbox { process { try { # Step: Retrieve the organization configuration (Condition C: Pass/Fail) - $customerLockboxEnabled = Get-ExoOutput -Rec $recnum + $customerLockboxEnabled = Get-CISExoOutput -Rec $recnum # Step: Prepare failure reasons and details based on compliance (Condition A, B, & C: Fail) $failureReasons = if (-not $customerLockboxEnabled) { diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index 7c55c4a..8b8a758 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -36,7 +36,7 @@ function Test-EnableDKIM { # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains # Retrieve DKIM configuration for all domains - $dkimConfig = Get-ExoOutput -Rec $recnum + $dkimConfig = Get-CISExoOutput -Rec $recnum $dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false $dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain } diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index df487f0..d2ae686 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -31,7 +31,7 @@ function Test-ExternalSharingCalendars { process { try { # Step: Retrieve sharing policies related to calendar sharing - $sharingPolicies = Get-ExoOutput -Rec $recnum + $sharingPolicies = Get-CISExoOutput -Rec $recnum # Step (Condition A & B: Pass/Fail): Check if calendar sharing is disabled in all applicable policies $isExternalSharingDisabled = $true diff --git a/source/tests/Test-GlobalAdminsCount.ps1 b/source/tests/Test-GlobalAdminsCount.ps1 index e52117a..8b5580c 100644 --- a/source/tests/Test-GlobalAdminsCount.ps1 +++ b/source/tests/Test-GlobalAdminsCount.ps1 @@ -30,7 +30,7 @@ function Test-GlobalAdminsCount { process { try { - $globalAdmins = Get-MgOutput -Rec $recnum + $globalAdmins = Get-CISMgOutput -Rec $recnum # Step: Count the number of global admins $globalAdminCount = $globalAdmins.Count diff --git a/source/tests/Test-IdentifyExternalEmail.ps1 b/source/tests/Test-IdentifyExternalEmail.ps1 index b8920ec..ebca9fd 100644 --- a/source/tests/Test-IdentifyExternalEmail.ps1 +++ b/source/tests/Test-IdentifyExternalEmail.ps1 @@ -36,7 +36,7 @@ function Test-IdentifyExternalEmail { # 6.2.3 (L1) Ensure email from external senders is identified # Retrieve external sender tagging configuration - $externalInOutlook = Get-ExoOutput -Rec $recnum + $externalInOutlook = Get-CISExoOutput -Rec $recnum $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-MailTipsEnabled.ps1 b/source/tests/Test-MailTipsEnabled.ps1 index 94197c0..c51f7ba 100644 --- a/source/tests/Test-MailTipsEnabled.ps1 +++ b/source/tests/Test-MailTipsEnabled.ps1 @@ -38,7 +38,7 @@ function Test-MailTipsEnabled { # 6.5.2 (L2) Ensure MailTips are enabled for end users # Retrieve organization configuration for MailTips settings - $orgConfig = Get-ExoOutput -Rec $recnum + $orgConfig = Get-CISExoOutput -Rec $recnum # Check the MailTips settings (Conditions A, B, C, D) $allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25 diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index ba8198c..d05c18c 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -38,14 +38,14 @@ function Test-MailboxAuditingE3 { $allFailures = @() $recnum = "6.1.2" - $allUsers = Get-MgOutput -Rec $recnum + $allUsers = Get-CISMgOutput -Rec $recnum $processedUsers = @{} # Dictionary to track processed users } process { if ($null -ne $allUsers) { - $mailboxes = Get-ExoOutput -Rec $recnum + $mailboxes = Get-CISExoOutput -Rec $recnum try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index 6c218e0..f0ee6be 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -35,12 +35,12 @@ function Test-MailboxAuditingE5 { $allFailures = @() $processedUsers = @{} $recnum = "6.1.3" - $allUsers = Get-MgOutput -Rec $recnum + $allUsers = Get-CISMgOutput -Rec $recnum } process { if ($null -ne $allUsers) { - $mailboxes = Get-ExoOutput -Rec $recnum + $mailboxes = Get-CISExoOutput -Rec $recnum try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { diff --git a/source/tests/Test-ManagedApprovedPublicGroups.ps1 b/source/tests/Test-ManagedApprovedPublicGroups.ps1 index 0563ed5..2c2140f 100644 --- a/source/tests/Test-ManagedApprovedPublicGroups.ps1 +++ b/source/tests/Test-ManagedApprovedPublicGroups.ps1 @@ -30,7 +30,7 @@ function Test-ManagedApprovedPublicGroups { process { try { # Step: Retrieve all groups with visibility set to 'Public' - $allGroups = Get-MgOutput -Rec $recnum + $allGroups = Get-CISMgOutput -Rec $recnum # Step: Determine failure reasons based on the presence of public groups $failureReasons = if ($null -ne $allGroups -and $allGroups.Count -gt 0) { diff --git a/source/tests/Test-ModernAuthExchangeOnline.ps1 b/source/tests/Test-ModernAuthExchangeOnline.ps1 index f9bac64..d4f4738 100644 --- a/source/tests/Test-ModernAuthExchangeOnline.ps1 +++ b/source/tests/Test-ModernAuthExchangeOnline.ps1 @@ -34,7 +34,7 @@ function Test-ModernAuthExchangeOnline { # 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled # Check modern authentication setting in Exchange Online configuration (Condition A and B) - $orgConfig = Get-ExoOutput -Rec $recnum + $orgConfig = Get-CISExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance $failureReasons = if (-not $orgConfig.OAuth2ClientProfileEnabled) { diff --git a/source/tests/Test-NoWhitelistDomains.ps1 b/source/tests/Test-NoWhitelistDomains.ps1 index 37441bb..076b2c1 100644 --- a/source/tests/Test-NoWhitelistDomains.ps1 +++ b/source/tests/Test-NoWhitelistDomains.ps1 @@ -38,7 +38,7 @@ function Test-NoWhitelistDomains { # Retrieve transport rules that whitelist specific domains # Condition A: Checking for transport rules that whitelist specific domains - $whitelistedRules = Get-ExoOutput -Rec $recnum + $whitelistedRules = Get-CISExoOutput -Rec $recnum # Prepare failure reasons and details based on compliance # Condition B: Prepare failure reasons based on the presence of whitelisted rules $failureReasons = if ($whitelistedRules) { diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index f2a17f3..d08da4f 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -34,7 +34,7 @@ function Test-NotifyMalwareInternal { # 2.1.3 Ensure notifications for internal users sending malware is Enabled # Retrieve all 'Custom' malware filter policies and check notification settings - $malwareNotifications = Get-ExoOutput -Rec $recnum + $malwareNotifications = Get-CISExoOutput -Rec $recnum # Condition B: Using PowerShell, the `NotifyInternal` property in the anti-malware policy is set to `True` and includes at least one valid email address for notifications. $policiesToReport = @() diff --git a/source/tests/Test-PasswordHashSync.ps1 b/source/tests/Test-PasswordHashSync.ps1 index 8c1e1b1..c054cfb 100644 --- a/source/tests/Test-PasswordHashSync.ps1 +++ b/source/tests/Test-PasswordHashSync.ps1 @@ -34,7 +34,7 @@ function Test-PasswordHashSync { # 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments # Retrieve password hash sync status (Condition A and C) - $passwordHashSync = Get-MgOutput -Rec $recnum + $passwordHashSync = Get-CISMgOutput -Rec $recnum $hashSyncResult = $passwordHashSync # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index 1b27dd7..94de4c6 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -24,7 +24,7 @@ function Test-ReportSecurityInTeams { $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting # Condition B: Verify that 'Monitor reported messages in Microsoft Teams' is checked in the Microsoft 365 Defender portal. # Condition C: Ensure the 'Send reported messages to' setting in the Microsoft 365 Defender portal is set to 'My reporting mailbox only' with the correct report email addresses. - $ReportSubmissionPolicy = Get-ExoOutput -Rec $recnum + $ReportSubmissionPolicy = Get-CISExoOutput -Rec $recnum # Check if all the required settings are enabled $securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and $ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and diff --git a/source/tests/Test-RestrictOutlookAddins.ps1 b/source/tests/Test-RestrictOutlookAddins.ps1 index a4537ca..8c5408e 100644 --- a/source/tests/Test-RestrictOutlookAddins.ps1 +++ b/source/tests/Test-RestrictOutlookAddins.ps1 @@ -36,7 +36,7 @@ function Test-RestrictOutlookAddins { # Check all mailboxes for custom policies with unallowed add-ins # Check Default Role Assignment Policy - $customPolicyFailures, $defaultPolicy = Get-ExoOutput -Rec $recnum + $customPolicyFailures, $defaultPolicy = Get-CISExoOutput -Rec $recnum $defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles } # Condition A: Verify that the roles MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are unchecked under Other roles. diff --git a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 index a5f310a..a466f72 100644 --- a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 +++ b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 @@ -34,7 +34,7 @@ function Test-RestrictStorageProvidersOutlook { # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web # Retrieve all OwaMailbox policies - $owaPolicies = Get-ExoOutput -Rec $recnum + $owaPolicies = Get-CISExoOutput -Rec $recnum # Condition A: Check if AdditionalStorageProvidersAvailable is set to False $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } diff --git a/source/tests/Test-RestrictTenantCreation.ps1 b/source/tests/Test-RestrictTenantCreation.ps1 index c1a7c4e..476eb99 100644 --- a/source/tests/Test-RestrictTenantCreation.ps1 +++ b/source/tests/Test-RestrictTenantCreation.ps1 @@ -35,7 +35,7 @@ function Test-RestrictTenantCreation { # 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' # Retrieve the tenant creation policy - $tenantCreationPolicy = Get-MgOutput -Rec $recnum + $tenantCreationPolicy = Get-CISMgOutput -Rec $recnum $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index fa22622..6011aa8 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -28,7 +28,7 @@ function Test-SafeAttachmentsPolicy { } process { - $safeAttachmentPolicies = Get-ExoOutput -Rec $recnum + $safeAttachmentPolicies = Get-CISExoOutput -Rec $recnum if ($safeAttachmentPolicies -ne 1) { try { # Check if any Safe Attachments policy is enabled (Condition A) diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index 123cdbb..5ae5f14 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -31,7 +31,7 @@ function Test-SafeAttachmentsTeams { } process { - $atpPolicyResult = Get-ExoOutput -Rec $recnum + $atpPolicyResult = Get-CISExoOutput -Rec $recnum if ($atpPolicyResult -ne 1) { try { # Condition A: Check Safe Attachments for SharePoint diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 2388f31..da66d07 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -42,7 +42,7 @@ function Test-SafeLinksOfficeApps { process { # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled # Retrieve all Safe Links policies - $misconfiguredDetails = Get-ExoOutput -Rec $recnum + $misconfiguredDetails = Get-CISExoOutput -Rec $recnum # Misconfigured details returns 1 if EXO Commands needed for the test are not available if ($misconfiguredDetails -ne 1) { try { diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index 81116fa..9d6779c 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -38,7 +38,7 @@ function Test-SpamPolicyAdminNotify { # 2.1.6 Ensure Exchange Online Spam Policies are set to notify administrators # Retrieve the default hosted outbound spam filter policy - $hostedOutboundSpamFilterPolicy = Get-ExoOutput -Rec $recnum + $hostedOutboundSpamFilterPolicy = Get-CISExoOutput -Rec $recnum # Check if both settings are enabled (Condition A and Condition B for pass) $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail diff --git a/tests/Unit/Private/Get-ExoOutput.tests.ps1 b/tests/Unit/Private/Get-CISExoOutput.tests.ps1 similarity index 100% rename from tests/Unit/Private/Get-ExoOutput.tests.ps1 rename to tests/Unit/Private/Get-CISExoOutput.tests.ps1 diff --git a/tests/Unit/Private/Get-MgOutput.tests.ps1 b/tests/Unit/Private/Get-CISMSTeamsOutput.tests.ps1 similarity index 100% rename from tests/Unit/Private/Get-MgOutput.tests.ps1 rename to tests/Unit/Private/Get-CISMSTeamsOutput.tests.ps1 diff --git a/tests/Unit/Private/Get-CISMgOutput.tests.ps1 b/tests/Unit/Private/Get-CISMgOutput.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-CISMgOutput.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' + } + } + } +} + From 7e98f774246665c657d7d5ef12f3ca035ba9f6a9 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:46:02 -0500 Subject: [PATCH 18/42] docs: Update CHANGELOG --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e083c..6cbc1b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,9 @@ The format is based on and uses the types of changes according to [Keep a Change - Added export to excel to `Export-M365SecurityAuditTable` function. - `Get-AdminRoleUserLicense` function to get the license of a user with admin roles for 1.1.1. - Skip MSOL connection confirmation to `Get-MFAStatus` function. -- Get-MgOutput function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. -- Added `Get-ExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. +- Added `Get-CISMgOutput` function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. +- Added `Get-CISExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. +- Added `Get-CISMSTeamsOutput` function to get the output of the Microsoft Teams API per test and adjusted tests to utilize. - Updated EXO tests to utilize the new output functions ('1.2.2', '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', '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', '8.6.1'). ## [0.1.13] - 2024-06-18 From a874836b8bb33767ac07aee7aa89c79a98a0eafb Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:26:45 -0500 Subject: [PATCH 19/42] fix: Update formatting --- source/Private/Get-CISExoOutput.ps1 | 5 +++-- source/Private/Get-CISMgOutput.ps1 | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index e932c47..b275df5 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -18,7 +18,8 @@ function Get-CISExoOutput { ) begin { # Begin Block # - <# + <# + # Tests 1.2.2 1.3.3 1.3.6 @@ -42,7 +43,7 @@ function Get-CISExoOutput { 6.5.2 6.5.3 8.6.1 - + # Test number array $testNumbers = @('1.2.2', '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', '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', '8.6.1') #> } diff --git a/source/Private/Get-CISMgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 index 46e987e..3b43c30 100644 --- a/source/Private/Get-CISMgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -23,6 +23,19 @@ function Get-CISMgOutput { begin { # Begin Block # + # Tests + <# + 1.1.1 + 1.1.3 + 1.2.1 + 1.3.1 + 5.1.2.3 + 5.1.8.1 + 6.1.2 + 6.1.3 + # Test number array + $testNumbers = @('1.1.1', '1.1.3', '1.2.1', '1.3.1', '5.1.2.3', '5.1.8.1', '6.1.2', '6.1.3') + #> } process { switch ($rec) { From 917833b186c52066b681772406a85328c61c11f7 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:27:25 -0500 Subject: [PATCH 20/42] add: Get-CISMSTeamsOutput function and updated respective tests --- source/Private/Get-CISMSTeamsOutput.ps1 | 296 ++++++++++++++++-- source/tests/Test-BlockChannelEmails.ps1 | 2 +- source/tests/Test-DialInBypassLobby.ps1 | 2 +- source/tests/Test-ExternalNoControl.ps1 | 3 +- source/tests/Test-MeetingChatNoAnonymous.ps1 | 2 +- source/tests/Test-NoAnonymousMeetingJoin.ps1 | 2 +- source/tests/Test-NoAnonymousMeetingStart.ps1 | 2 +- source/tests/Test-OrgOnlyBypassLobby.ps1 | 2 +- source/tests/Test-OrganizersPresent.ps1 | 2 +- source/tests/Test-ReportSecurityInTeams.ps1 | 2 +- source/tests/Test-TeamsExternalAccess.ps1 | 4 +- .../tests/Test-TeamsExternalFileSharing.ps1 | 3 +- 12 files changed, 283 insertions(+), 39 deletions(-) diff --git a/source/Private/Get-CISMSTeamsOutput.ps1 b/source/Private/Get-CISMSTeamsOutput.ps1 index 70c28a6..79768fe 100644 --- a/source/Private/Get-CISMSTeamsOutput.ps1 +++ b/source/Private/Get-CISMSTeamsOutput.ps1 @@ -1,32 +1,278 @@ -function Get-CISMSTeamsOutput -{ - <# - .SYNOPSIS - This is a sample Private function only visible within the module. - - .DESCRIPTION - This sample function is not exported to the module and only return the data passed as parameter. - - .EXAMPLE - $null = Get-MSTeamsOutput -PrivateData 'NOTHING TO SEE HERE' - - .PARAMETER PrivateData - The PrivateData parameter is what will be returned without transformation. - - #> +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + .EXAMPLE + $null = Get-CISMSTeamsOutput -PrivateData 'NOTHING TO SEE HERE' + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. +#> +function Get-CISMSTeamsOutput { [cmdletBinding()] [OutputType([string])] - param - ( - [Parameter()] + param( + [Parameter(Mandatory = $true)] [String] - $PrivateData + $Rec ) - - process - { - Write-Output $PrivateData + begin { + # Begin Block # + <# + # Tests + 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 + # Test number array + $testNumbers = @('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') + #> } + process { + switch ($Rec) { + '8.1.1' { + # Test-TeamsExternalFileSharing.ps1 + # 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 -} + # Condition A: The `AllowDropbox` setting is set to `False`. + # Condition B: The `AllowBox` setting is set to `False`. + # Condition C: The `AllowGoogleDrive` setting is set to `False`. + # Condition D: The `AllowShareFile` setting is set to `False`. + # Condition E: The `AllowEgnyte` setting is set to `False`. + + # 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 + $clientConfig = Get-CsTeamsClientConfiguration + return $clientConfig + } + '8.1.2' { + # Test-BlockChannelEmails.ps1 + # 8.1.2 (L1) Ensure users can't send emails to a channel email address + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `AllowEmailIntoChannel` setting in Teams is set to `False`. + # - Condition B: The setting `Users can send emails to a channel email address` is set to `Off` in the Teams admin center. + # - Condition C: Verification using PowerShell confirms that the `AllowEmailIntoChannel` setting is disabled. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AllowEmailIntoChannel` setting in Teams is not set to `False`. + # - Condition B: The setting `Users can send emails to a channel email address` is not set to `Off` in the Teams admin center. + # - Condition C: Verification using PowerShell indicates that the `AllowEmailIntoChannel` setting is enabled. + + # Retrieve Teams client configuration + $teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global + return $teamsClientConfig + } + '8.2.1' { + # Test-TeamsExternalAccess.ps1 + # 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `AllowTeamsConsumer` setting is `False`. + # - Condition B: The `AllowPublicUsers` setting is `False`. + # - Condition C: The `AllowFederatedUsers` setting is `False` or, if `True`, the `AllowedDomains` contains only authorized domain names. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AllowTeamsConsumer` setting is not `False`. + # - Condition B: The `AllowPublicUsers` setting is not `False`. + # - Condition C: The `AllowFederatedUsers` setting is `True` and the `AllowedDomains` contains unauthorized domain names or is not configured correctly. + + # Connect to Teams PowerShell using Connect-MicrosoftTeams + + $externalAccessConfig = Get-CsTenantFederationConfiguration + return $externalAccessConfig + } + '8.5.1' { + # Test-NoAnonymousMeetingJoin.ps1 + # 8.5.1 (L2) Ensure anonymous users can't join a meeting + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: `AllowAnonymousUsersToJoinMeeting` is set to `False`. + # - Condition B: Verification using the UI confirms that `Anonymous users can join a meeting` is set to `Off` in the Global meeting policy. + # - Condition C: PowerShell command output indicates that anonymous users are not allowed to join meetings. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: `AllowAnonymousUsersToJoinMeeting` is not set to `False`. + # - Condition B: Verification using the UI shows that `Anonymous users can join a meeting` is not set to `Off` in the Global meeting policy. + # - Condition C: PowerShell command output indicates that anonymous users are allowed to join meetings. + + # Connect to Teams PowerShell using Connect-MicrosoftTeams + + $teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global + return $teamsMeetingPolicy + } + '8.5.2' { + # Test-NoAnonymousMeetingStart.ps1 + # 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `AllowAnonymousUsersToStartMeeting` setting in the Teams admin center is set to `False`. + # - Condition B: The setting for anonymous users and dial-in callers starting a meeting is configured to ensure they must wait in the lobby. + # - Condition C: Verification using the UI confirms that the setting `Anonymous users and dial-in callers can start a meeting` is set to `Off`. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AllowAnonymousUsersToStartMeeting` setting in the Teams admin center is not set to `False`. + # - Condition B: The setting for anonymous users and dial-in callers starting a meeting allows them to bypass the lobby. + # - Condition C: Verification using the UI indicates that the setting `Anonymous users and dial-in callers can start a meeting` is not set to `Off`. + + # Connect to Teams PowerShell using Connect-MicrosoftTeams + + # Retrieve the Teams meeting policy for the global scope and check if anonymous users can start meetings + $CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting + return $CsTeamsMeetingPolicyAnonymous + } + '8.5.3' { + # Test-OrgOnlyBypassLobby.ps1 + # 8.5.3 (L1) Ensure only people in my org can bypass the lobby + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `AutoAdmittedUsers` setting in the Teams meeting policy is set to `EveryoneInCompanyExcludingGuests`. + # - Condition B: The setting for "Who can bypass the lobby" is configured to "People in my org" using the UI. + # - Condition C: Verification using the Microsoft Teams admin center confirms that the meeting join & lobby settings are configured as recommended. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AutoAdmittedUsers` setting in the Teams meeting policy is not set to `EveryoneInCompanyExcludingGuests`. + # - Condition B: The setting for "Who can bypass the lobby" is not configured to "People in my org" using the UI. + # - Condition C: Verification using the Microsoft Teams admin center indicates that the meeting join & lobby settings are not configured as recommended. + + # 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 + return $CsTeamsMeetingPolicyLobby + } + '8.5.4' { + # Test-DialInBypassLobby.ps1 + # 8.5.4 (L1) Ensure users dialing in can't bypass the lobby + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `AllowPSTNUsersToBypassLobby` setting in the Global Teams meeting policy is set to `False`. + # - Condition B: Verification using the UI in the Microsoft Teams admin center confirms that "People dialing in can't bypass the lobby" is set to `Off`. + # - Condition C: Ensure that individuals who dial in by phone must wait in the lobby until admitted by a meeting organizer, co-organizer, or presenter. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AllowPSTNUsersToBypassLobby` setting in the Global Teams meeting policy is not set to `False`. + # - Condition B: Verification using the UI in the Microsoft Teams admin center shows that "People dialing in can't bypass the lobby" is not set to `Off`. + # - Condition C: Individuals who dial in by phone are able to join the meeting directly without waiting in the lobby. + + # Retrieve Teams meeting policy for PSTN users + $CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby + return $CsTeamsMeetingPolicyPSTN + } + '8.5.5' { + # Test-MeetingChatNoAnonymous.ps1 + # 8.5.5 (L2) Ensure meeting chat does not allow anonymous users + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `MeetingChatEnabledType` setting in Teams is set to `EnabledExceptAnonymous`. + # - Condition B: The setting for meeting chat is configured to allow chat for everyone except anonymous users. + # - Condition C: Verification using the Teams Admin Center confirms that the meeting chat settings are configured as recommended. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `MeetingChatEnabledType` setting in Teams is not set to `EnabledExceptAnonymous`. + # - Condition B: The setting for meeting chat allows chat for anonymous users. + # - Condition C: Verification using the Teams Admin Center indicates that the meeting chat settings are not configured as recommended. + + # Retrieve the Teams meeting policy for meeting chat + $CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType + return $CsTeamsMeetingPolicyChat + } + '8.5.6' { + # Test-OrganizersPresent.ps1 + # 8.5.6 (L2) Ensure only organizers and co-organizers can present + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: The `DesignatedPresenterRoleMode` setting in the Teams meeting policy is set to `OrganizerOnlyUserOverride`. + # - Condition B: Verification using the Teams admin center confirms that the setting "Who can present" is configured to "Only organizers and co-organizers". + # - Condition C: Verification using PowerShell confirms that the `DesignatedPresenterRoleMode` is set to `OrganizerOnlyUserOverride`. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `DesignatedPresenterRoleMode` setting in the Teams meeting policy is not set to `OrganizerOnlyUserOverride`. + # - Condition B: Verification using the Teams admin center indicates that the setting "Who can present" is not configured to "Only organizers and co-organizers". + # - Condition C: Verification using PowerShell indicates that the `DesignatedPresenterRoleMode` is not set to `OrganizerOnlyUserOverride`. + + # Retrieve the Teams meeting policy for presenters + $CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode + return $CsTeamsMeetingPolicyPresenters + } + '8.5.7' { + # Test-ExternalNoControl.ps1 + # 8.5.7 (L1) Ensure external participants can't give or request control + # + # Validate test for a pass: + # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. + # - Specific conditions to check: + # - Condition A: Ensure the `AllowExternalParticipantGiveRequestControl` setting in Teams is set to `False`. + # - Condition B: The setting is verified through the Microsoft Teams admin center or via PowerShell command. + # - Condition C: Verification using the UI confirms that external participants are unable to give or request control. + # + # Validate test for a fail: + # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. + # - Specific conditions to check: + # - Condition A: The `AllowExternalParticipantGiveRequestControl` setting in Teams is not set to `False`. + # - Condition B: The setting is verified through the Microsoft Teams admin center or via PowerShell command. + # - Condition C: Verification using the UI indicates that external participants can give or request control. + + # Retrieve Teams meeting policy for external participant control + $CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl + return $CsTeamsMeetingPolicyControl + } + '8.6.1' { + # Test-ReportSecurityInTeams.ps1 + # 8.6.1 (L1) Ensure users can report security concerns in Teams + + # Retrieve the necessary settings for Teams and Exchange Online + # Condition A: Ensure the 'Report a security concern' setting in the Teams admin center is set to 'On'. + $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting + return $CsTeamsMessagingPolicy + } + default { + Write-Output "No matching action found" + } + } + } + end { + Write-Verbose "Retuning data for Rec: $Rec" + } +} # end function Get-CISMSTeamsOutput diff --git a/source/tests/Test-BlockChannelEmails.ps1 b/source/tests/Test-BlockChannelEmails.ps1 index a6f2827..26f13dd 100644 --- a/source/tests/Test-BlockChannelEmails.ps1 +++ b/source/tests/Test-BlockChannelEmails.ps1 @@ -33,7 +33,7 @@ function Test-BlockChannelEmails { # - Condition C: Verification using PowerShell indicates that the `AllowEmailIntoChannel` setting is enabled. # Retrieve Teams client configuration - $teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global + $teamsClientConfig = Get-CISMSTeamsOutput -Rec $recnum $allowEmailIntoChannel = $teamsClientConfig.AllowEmailIntoChannel # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-DialInBypassLobby.ps1 b/source/tests/Test-DialInBypassLobby.ps1 index 52f7afd..89bce50 100644 --- a/source/tests/Test-DialInBypassLobby.ps1 +++ b/source/tests/Test-DialInBypassLobby.ps1 @@ -33,7 +33,7 @@ function Test-DialInBypassLobby { # - Condition C: Individuals who dial in by phone are able to join the meeting directly without waiting in the lobby. # Retrieve Teams meeting policy for PSTN users - $CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby + $CsTeamsMeetingPolicyPSTN = Get-CISMSTeamsOutput -Rec $recnum $PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-ExternalNoControl.ps1 b/source/tests/Test-ExternalNoControl.ps1 index 462bb43..70a69fa 100644 --- a/source/tests/Test-ExternalNoControl.ps1 +++ b/source/tests/Test-ExternalNoControl.ps1 @@ -34,7 +34,8 @@ function Test-ExternalNoControl { # - Condition C: Verification using the UI indicates that external participants can give or request control. # Retrieve Teams meeting policy for external participant control - $CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl + $CsTeamsMeetingPolicyControl = Get-CISMSTeamsOutput -Rec $recnum + # Check if external participants can give or request control $externalControlRestricted = -not $CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-MeetingChatNoAnonymous.ps1 b/source/tests/Test-MeetingChatNoAnonymous.ps1 index 25b9367..4420efc 100644 --- a/source/tests/Test-MeetingChatNoAnonymous.ps1 +++ b/source/tests/Test-MeetingChatNoAnonymous.ps1 @@ -32,7 +32,7 @@ function Test-MeetingChatNoAnonymous { # - Condition C: Verification using the Teams Admin Center indicates that the meeting chat settings are not configured as recommended. # Retrieve the Teams meeting policy for meeting chat - $CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType + $CsTeamsMeetingPolicyChat = Get-CISMSTeamsOutput -Rec $recnum # Condition A: Check if the MeetingChatEnabledType is set to 'EnabledExceptAnonymous' $chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous' diff --git a/source/tests/Test-NoAnonymousMeetingJoin.ps1 b/source/tests/Test-NoAnonymousMeetingJoin.ps1 index e859a53..bad4009 100644 --- a/source/tests/Test-NoAnonymousMeetingJoin.ps1 +++ b/source/tests/Test-NoAnonymousMeetingJoin.ps1 @@ -33,7 +33,7 @@ function Test-NoAnonymousMeetingJoin { # Connect to Teams PowerShell using Connect-MicrosoftTeams - $teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global + $teamsMeetingPolicy = Get-CISMSTeamsOutput -Rec $recnum $allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-NoAnonymousMeetingStart.ps1 b/source/tests/Test-NoAnonymousMeetingStart.ps1 index 0a9b2dc..04498e0 100644 --- a/source/tests/Test-NoAnonymousMeetingStart.ps1 +++ b/source/tests/Test-NoAnonymousMeetingStart.ps1 @@ -34,7 +34,7 @@ function Test-NoAnonymousMeetingStart { # Connect to Teams PowerShell using Connect-MicrosoftTeams # Retrieve the Teams meeting policy for the global scope and check if anonymous users can start meetings - $CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting + $CsTeamsMeetingPolicyAnonymous = Get-CISMSTeamsOutput -Rec $recnum $anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-OrgOnlyBypassLobby.ps1 b/source/tests/Test-OrgOnlyBypassLobby.ps1 index b039c4d..104181e 100644 --- a/source/tests/Test-OrgOnlyBypassLobby.ps1 +++ b/source/tests/Test-OrgOnlyBypassLobby.ps1 @@ -34,7 +34,7 @@ function Test-OrgOnlyBypassLobby { # 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 + $CsTeamsMeetingPolicyLobby = Get-CISMSTeamsOutput -Rec $recnum $lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests' # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-OrganizersPresent.ps1 b/source/tests/Test-OrganizersPresent.ps1 index c57ad39..4a3be9f 100644 --- a/source/tests/Test-OrganizersPresent.ps1 +++ b/source/tests/Test-OrganizersPresent.ps1 @@ -32,7 +32,7 @@ function Test-OrganizersPresent { # - Condition C: Verification using PowerShell indicates that the `DesignatedPresenterRoleMode` is not set to `OrganizerOnlyUserOverride`. # Retrieve the Teams meeting policy for presenters - $CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode + $CsTeamsMeetingPolicyPresenters = Get-CISMSTeamsOutput -Rec $recnum $presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride' # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index 94de4c6..c5c822a 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -21,7 +21,7 @@ function Test-ReportSecurityInTeams { # Retrieve the necessary settings for Teams and Exchange Online # Condition A: Ensure the 'Report a security concern' setting in the Teams admin center is set to 'On'. - $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting + $CsTeamsMessagingPolicy = Get-CISMSTeamsOutput -Rec $recnum # Condition B: Verify that 'Monitor reported messages in Microsoft Teams' is checked in the Microsoft 365 Defender portal. # Condition C: Ensure the 'Send reported messages to' setting in the Microsoft 365 Defender portal is set to 'My reporting mailbox only' with the correct report email addresses. $ReportSubmissionPolicy = Get-CISExoOutput -Rec $recnum diff --git a/source/tests/Test-TeamsExternalAccess.ps1 b/source/tests/Test-TeamsExternalAccess.ps1 index a6d4bbb..787720b 100644 --- a/source/tests/Test-TeamsExternalAccess.ps1 +++ b/source/tests/Test-TeamsExternalAccess.ps1 @@ -10,8 +10,6 @@ function Test-TeamsExternalAccess { # Dot source the class script if necessary # . .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed - - $auditResult = [CISAuditResult]::new() $recnum = "8.2.1" } @@ -35,7 +33,7 @@ function Test-TeamsExternalAccess { # Connect to Teams PowerShell using Connect-MicrosoftTeams - $externalAccessConfig = Get-CsTenantFederationConfiguration + $externalAccessConfig = Get-CISMSTeamsOutput -Rec $recnum $allowedDomainsLimited = $false if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) { diff --git a/source/tests/Test-TeamsExternalFileSharing.ps1 b/source/tests/Test-TeamsExternalFileSharing.ps1 index 6502fa0..7f61ec1 100644 --- a/source/tests/Test-TeamsExternalFileSharing.ps1 +++ b/source/tests/Test-TeamsExternalFileSharing.ps1 @@ -26,9 +26,8 @@ function Test-TeamsExternalFileSharing { # 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 + $clientConfig = Get-CISMSTeamsOutput -Rec $recnum $approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte") - $clientConfig = Get-CsTeamsClientConfiguration - $isCompliant = $true $nonCompliantProviders = @() From 6b135c2e31003a592713e020a6945fa04e5e5e1f Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:29:51 -0500 Subject: [PATCH 21/42] docs: Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cbc1b9..fa622d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,9 @@ The format is based on and uses the types of changes according to [Keep a Change - Added `Get-CISMgOutput` function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. - Added `Get-CISExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. - Added `Get-CISMSTeamsOutput` function to get the output of the Microsoft Teams API per test and adjusted tests to utilize. +- Updated Microsoft Graph tests to utilize the new output functions ('1.1.1', '1.1.3', '1.2.1', '1.3.1', '5.1.2.3', '5.1.8.1', '6.1.2', '6.1.3') - Updated EXO tests to utilize the new output functions ('1.2.2', '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', '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', '8.6.1'). +- Updated MSTeams tests to utilize the new output functions ('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') ## [0.1.13] - 2024-06-18 From b6423c8a7dec13bdc574e87d0c4de28e9e3b0d12 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:51:00 -0500 Subject: [PATCH 22/42] Update output for Connect-SPOService --- source/Private/Connect-M365Suite.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index 61f372c..0f9587d 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -73,11 +73,11 @@ function Connect-M365Suite { if ($RequiredConnections -contains "SPO") { Write-Host "Connecting to SharePoint Online..." -ForegroundColor Yellow Connect-SPOService -Url $TenantAdminUrl | Out-Null - $spoContext = Get-SPOSite -Limit 1 + $spoContext = Get-SPOCrossTenantHostUrl + $tenantName = Get-UrlLine -Output $spoContext $tenantInfo += [PSCustomObject]@{ Service = "SharePoint Online" - TenantName = $spoContext.Url - TenantID = $spoContext.GroupId + TenantName = $tenantName } $connectedServices += "SPO" Write-Host "Successfully connected to SharePoint Online." -ForegroundColor Green From 4dd65a0140b2d28b6e78cad349f4a1fbcfe6ff38 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:06:02 -0500 Subject: [PATCH 23/42] add: Error handling to output functions --- source/Private/Get-CISMSTeamsOutput.ps1 | 4 +--- source/Private/Get-CISMgOutput.ps1 | 11 ++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/source/Private/Get-CISMSTeamsOutput.ps1 b/source/Private/Get-CISMSTeamsOutput.ps1 index 79768fe..14b108f 100644 --- a/source/Private/Get-CISMSTeamsOutput.ps1 +++ b/source/Private/Get-CISMSTeamsOutput.ps1 @@ -266,9 +266,7 @@ function Get-CISMSTeamsOutput { $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting return $CsTeamsMessagingPolicy } - default { - Write-Output "No matching action found" - } + default { throw "No match found for test: $Rec" } } } end { diff --git a/source/Private/Get-CISMgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 index 3b43c30..55344e8 100644 --- a/source/Private/Get-CISMgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -39,6 +39,11 @@ function Get-CISMgOutput { } process { switch ($rec) { + '1.1.1' { + # 1.1.1 + $AdminRoleAssignmentsAndUsers = Get-AdminRoleUserAndAssignment + return $AdminRoleAssignmentsAndUsers + } '1.1.3' { # Step: Retrieve global admin role $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" @@ -84,11 +89,7 @@ function Get-CISMgOutput { return $null } } - Default { - # 1.1.1 - $AdminRoleAssignmentsAndUsers = Get-AdminRoleUserAndAssignment - return $AdminRoleAssignmentsAndUsers - } + default { throw "No match found for test: $Rec" } } } end { From bad103f0cfaaec4117ff6ef2e49af6beaf5ba281 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:06:31 -0500 Subject: [PATCH 24/42] add: Get-CISSpoOutput function and updated respective tests --- source/Private/Get-CISSpoOutput.ps1 | 120 ++++++++++++++++++ source/Private/Get-UrlLine.ps1 | 32 +++++ .../Test-DisallowInfectedFilesDownload.ps1 | 2 +- source/tests/Test-GuestAccessExpiration.ps1 | 2 +- source/tests/Test-LinkSharingRestrictions.ps1 | 2 +- source/tests/Test-ModernAuthSharePoint.ps1 | 2 +- .../Test-OneDriveContentRestrictions.ps1 | 2 +- .../tests/Test-OneDriveSyncRestrictions.ps1 | 2 +- source/tests/Test-ReauthWithCode.ps1 | 2 +- source/tests/Test-RestrictCustomScripts.ps1 | 2 +- source/tests/Test-RestrictExternalSharing.ps1 | 2 +- source/tests/Test-SharePointAADB2B.ps1 | 2 +- .../Test-SharePointExternalSharingDomains.ps1 | 2 +- .../Test-SharePointGuestsItemSharing.ps1 | 2 +- tests/Unit/Private/Get-CISSpoOutput.tests.ps1 | 27 ++++ tests/Unit/Private/Get-UrlLine.tests.ps1 | 27 ++++ 16 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 source/Private/Get-CISSpoOutput.ps1 create mode 100644 source/Private/Get-UrlLine.ps1 create mode 100644 tests/Unit/Private/Get-CISSpoOutput.tests.ps1 create mode 100644 tests/Unit/Private/Get-UrlLine.tests.ps1 diff --git a/source/Private/Get-CISSpoOutput.ps1 b/source/Private/Get-CISSpoOutput.ps1 new file mode 100644 index 0000000..7ed1088 --- /dev/null +++ b/source/Private/Get-CISSpoOutput.ps1 @@ -0,0 +1,120 @@ +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + .EXAMPLE + $null = Get-CISSpoOutput -PrivateData 'NOTHING TO SEE HERE' + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. +#> +function Get-CISSpoOutput { + [cmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [String] + $Rec + ) + begin { + # Begin Block # + <# + # Tests + 7.2.1 + 7.2.2 + 7.2.3 + 7.2.4 + 7.2.5 + 7.2.6 + 7.2.7 + 7.2.9 + 7.2.10 + 7.3.1 + 7.3.2 + 7.3.4 + + # Test number array + $testNumbers = @('7.2.1', '7.2.2', '7.2.3', '7.2.4', '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.2.10', '7.3.1', '7.3.2', '7.3.4') + #> + } + process { + switch ($Rec) { + '7.2.1' { + # Test-ModernAuthSharePoint.ps1 + $SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled + return $SPOTenant + } + '7.2.2' { + # Test-SharePointAADB2B.ps1 + # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + $SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration + return $SPOTenantAzureADB2B + } + '7.2.3' { + # Test-RestrictExternalSharing.ps1 + # 7.2.3 (L1) Ensure external content sharing is restricted + # Retrieve the SharingCapability setting for the SharePoint tenant + $SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability + return $SPOTenantSharingCapability + } + '7.2.4' { + # Test-OneDriveContentRestrictions.ps1 + $SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability + return $SPOTenant + } + '7.2.5' { + # Test-SharePointGuestsItemSharing.ps1 + # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own + $SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing + return $SPOTenant + } + '7.2.6' { + # Test-SharePointExternalSharingDomains.ps1 + # 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists + $SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList + return $SPOTenant + } + '7.2.7' { + # Test-LinkSharingRestrictions.ps1 + # Retrieve link sharing configuration for SharePoint and OneDrive + $SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType + return $SPOTenantLinkSharing + } + '7.2.9' { + # Test-GuestAccessExpiration.ps1 + # Retrieve SharePoint tenant settings related to guest access expiration + $SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays + return $SPOTenantGuestAccess + } + '7.2.10' { + # Test-ReauthWithCode.ps1 + # 7.2.10 (L1) Ensure reauthentication with verification code is restricted + # Retrieve reauthentication settings for SharePoint Online + $SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays + return $SPOTenantReauthentication + } + '7.3.1' { + # Test-DisallowInfectedFilesDownload.ps1 + # Retrieve the SharePoint tenant configuration + $SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload + return $SPOTenantDisallowInfectedFileDownload + } + '7.3.2' { + # Test-OneDriveSyncRestrictions.ps1 + # Retrieve OneDrive sync client restriction settings + $SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList + return $SPOTenantSyncClientRestriction + } + '7.3.4' { + # Test-RestrictCustomScripts.ps1 + # Retrieve all site collections and select necessary properties + $SPOSitesCustomScript = Get-SPOSite -Limit All | Select-Object Title, Url, DenyAddAndCustomizePages + return $SPOSitesCustomScript + } + default { throw "No match found for test: $Rec" } + } + } + end { + Write-Verbose "Retuning data for Rec: $Rec" + } +} # end function Get-CISMSTeamsOutput \ No newline at end of file diff --git a/source/Private/Get-UrlLine.ps1 b/source/Private/Get-UrlLine.ps1 new file mode 100644 index 0000000..37a34a2 --- /dev/null +++ b/source/Private/Get-UrlLine.ps1 @@ -0,0 +1,32 @@ +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-UrlLine -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. +#> +function Get-UrlLine { + [cmdletBinding()] + [OutputType([string])] + param ( + [Parameter(Mandatory=$true)] + [string]$Output + ) + # Split the output into lines + $Lines = $Output -split "`n" + # Iterate over each line + foreach ($Line in $Lines) { + # If the line starts with 'https', return it + if ($Line.StartsWith('https')) { + return $Line.Trim() + } + } + # If no line starts with 'https', return an empty string + return $null + } \ No newline at end of file diff --git a/source/tests/Test-DisallowInfectedFilesDownload.ps1 b/source/tests/Test-DisallowInfectedFilesDownload.ps1 index b9e72af..2da1c17 100644 --- a/source/tests/Test-DisallowInfectedFilesDownload.ps1 +++ b/source/tests/Test-DisallowInfectedFilesDownload.ps1 @@ -34,7 +34,7 @@ function Test-DisallowInfectedFilesDownload { # - Condition C: Verification using the PowerShell command indicates that the setting is incorrectly configured. # Retrieve the SharePoint tenant configuration - $SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload + $SPOTenantDisallowInfectedFileDownload = Get-CISSpoOutput -Rec $recnum # Condition A: The `DisallowInfectedFileDownload` setting is set to `True` $isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload diff --git a/source/tests/Test-GuestAccessExpiration.ps1 b/source/tests/Test-GuestAccessExpiration.ps1 index 782b9d7..8c0fcbc 100644 --- a/source/tests/Test-GuestAccessExpiration.ps1 +++ b/source/tests/Test-GuestAccessExpiration.ps1 @@ -34,7 +34,7 @@ function Test-GuestAccessExpiration { # - Condition C: Verification using the SharePoint Admin Center indicates that guest access is not set to expire automatically after the specified number of days. # Retrieve SharePoint tenant settings related to guest access expiration - $SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays + $SPOTenantGuestAccess = Get-CISSpoOutput -Rec $recnum $isGuestAccessExpirationConfiguredCorrectly = $SPOTenantGuestAccess.ExternalUserExpirationRequired -and $SPOTenantGuestAccess.ExternalUserExpireInDays -le 30 # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-LinkSharingRestrictions.ps1 b/source/tests/Test-LinkSharingRestrictions.ps1 index 5f41fe8..2144646 100644 --- a/source/tests/Test-LinkSharingRestrictions.ps1 +++ b/source/tests/Test-LinkSharingRestrictions.ps1 @@ -33,7 +33,7 @@ function Test-LinkSharingRestrictions { # - Condition C: Verification using the UI indicates that the link sharing settings are not configured as recommended. # Retrieve link sharing configuration for SharePoint and OneDrive - $SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType + $SPOTenantLinkSharing = Get-CISSpoOutput -Rec $recnum $isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-ModernAuthSharePoint.ps1 b/source/tests/Test-ModernAuthSharePoint.ps1 index 66d6614..85336c9 100644 --- a/source/tests/Test-ModernAuthSharePoint.ps1 +++ b/source/tests/Test-ModernAuthSharePoint.ps1 @@ -33,7 +33,7 @@ function Test-ModernAuthSharePoint { process { try { # 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required - $SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled + $SPOTenant = Get-CISSpoOutput -Rec $recnum $modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-OneDriveContentRestrictions.ps1 b/source/tests/Test-OneDriveContentRestrictions.ps1 index a87ffd1..bed9d6e 100644 --- a/source/tests/Test-OneDriveContentRestrictions.ps1 +++ b/source/tests/Test-OneDriveContentRestrictions.ps1 @@ -34,7 +34,7 @@ function Test-OneDriveContentRestrictions { # 7.2.4 (L2) Ensure OneDrive content sharing is restricted # Retrieve OneDrive sharing capability settings - $SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability + $SPOTenant = Get-CISSpoOutput -Rec $recnum $isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled' # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-OneDriveSyncRestrictions.ps1 b/source/tests/Test-OneDriveSyncRestrictions.ps1 index 4a6d0fb..373a815 100644 --- a/source/tests/Test-OneDriveSyncRestrictions.ps1 +++ b/source/tests/Test-OneDriveSyncRestrictions.ps1 @@ -32,7 +32,7 @@ function Test-OneDriveSyncRestrictions { # - Condition C: "AllowedDomainList" does not contain the trusted domain GUIDs from the on-premises environment. # Retrieve OneDrive sync client restriction settings - $SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList + $SPOTenantSyncClientRestriction = Get-CISSpoOutput -Rec $recnum $isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList # Condition A: Check if TenantRestrictionEnabled is True diff --git a/source/tests/Test-ReauthWithCode.ps1 b/source/tests/Test-ReauthWithCode.ps1 index feccb93..e5d0f3a 100644 --- a/source/tests/Test-ReauthWithCode.ps1 +++ b/source/tests/Test-ReauthWithCode.ps1 @@ -34,7 +34,7 @@ function Test-ReauthWithCode { # 7.2.10 (L1) Ensure reauthentication with verification code is restricted # Retrieve reauthentication settings for SharePoint Online - $SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays + $SPOTenantReauthentication = Get-CISSpoOutput -Rec $recnum $isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15 # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-RestrictCustomScripts.ps1 b/source/tests/Test-RestrictCustomScripts.ps1 index f492085..7705721 100644 --- a/source/tests/Test-RestrictCustomScripts.ps1 +++ b/source/tests/Test-RestrictCustomScripts.ps1 @@ -32,7 +32,7 @@ function Test-RestrictCustomScripts { # - Condition C: Verification using the SharePoint Admin Center indicates that the `DenyAddAndCustomizePages` setting is not enforced. # Retrieve all site collections and select necessary properties - $SPOSitesCustomScript = Get-SPOSite -Limit All | Select-Object Title, Url, DenyAddAndCustomizePages + $SPOSitesCustomScript = Get-CISSpoOutput -Rec $recnum # Process URLs to replace 'sharepoint.com' with '' $processedUrls = $SPOSitesCustomScript | ForEach-Object { diff --git a/source/tests/Test-RestrictExternalSharing.ps1 b/source/tests/Test-RestrictExternalSharing.ps1 index dc9c016..3c99f67 100644 --- a/source/tests/Test-RestrictExternalSharing.ps1 +++ b/source/tests/Test-RestrictExternalSharing.ps1 @@ -36,7 +36,7 @@ function Test-RestrictExternalSharing { # 7.2.3 (L1) Ensure external content sharing is restricted # Retrieve the SharingCapability setting for the SharePoint tenant - $SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability + $SPOTenantSharingCapability = Get-CISSpoOutput -Rec $recnum $isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled') # Prepare failure reasons and details based on compliance diff --git a/source/tests/Test-SharePointAADB2B.ps1 b/source/tests/Test-SharePointAADB2B.ps1 index fa1f733..d0bf748 100644 --- a/source/tests/Test-SharePointAADB2B.ps1 +++ b/source/tests/Test-SharePointAADB2B.ps1 @@ -33,7 +33,7 @@ function Test-SharePointAADB2B { process { try { # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled - $SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration + $SPOTenantAzureADB2B = Get-CISSpoOutput -Rec $recnum # Populate the auditResult object with the required properties $params = @{ diff --git a/source/tests/Test-SharePointExternalSharingDomains.ps1 b/source/tests/Test-SharePointExternalSharingDomains.ps1 index 035dc29..64c53c6 100644 --- a/source/tests/Test-SharePointExternalSharingDomains.ps1 +++ b/source/tests/Test-SharePointExternalSharingDomains.ps1 @@ -33,7 +33,7 @@ function Test-SharePointExternalSharingDomains { process { try { # 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists - $SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList + $SPOTenant = Get-CISSpoOutput -Rec $recnum $isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList' # Populate the auditResult object with the required properties diff --git a/source/tests/Test-SharePointGuestsItemSharing.ps1 b/source/tests/Test-SharePointGuestsItemSharing.ps1 index 0ac33e2..b09a66a 100644 --- a/source/tests/Test-SharePointGuestsItemSharing.ps1 +++ b/source/tests/Test-SharePointGuestsItemSharing.ps1 @@ -33,7 +33,7 @@ function Test-SharePointGuestsItemSharing { process { try { # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own - $SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing + $SPOTenant = Get-CISSpoOutput -Rec $recnum $isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing # Populate the auditResult object with the required properties diff --git a/tests/Unit/Private/Get-CISSpoOutput.tests.ps1 b/tests/Unit/Private/Get-CISSpoOutput.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-CISSpoOutput.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-UrlLine.tests.ps1 b/tests/Unit/Private/Get-UrlLine.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-UrlLine.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' + } + } + } +} + From 0601996a68ed2f18977550e9038664e5fe65cdb1 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:08:49 -0500 Subject: [PATCH 25/42] docs: Update CHANGELOG --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa622d2..291bdc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ The format is based on and uses the types of changes according to [Keep a Change - Added export to excel to `Export-M365SecurityAuditTable` function. - `Get-AdminRoleUserLicense` function to get the license of a user with admin roles for 1.1.1. - Skip MSOL connection confirmation to `Get-MFAStatus` function. -- Added `Get-CISMgOutput` function to get the output of the Microsoft Graph API per test and adjusted tests to utilize. -- Added `Get-CISExoOutput` function to get the output of the Exchange Online API per test and adjusted tests to utilize. -- Added `Get-CISMSTeamsOutput` function to get the output of the Microsoft Teams API per test and adjusted tests to utilize. +- Added `Get-CISMgOutput` function to get the output of the Microsoft Graph API per test. +- Added `Get-CISExoOutput` function to get the output of the Exchange Online API per test. +- Added `Get-CISMSTeamsOutput` function to get the output of the Microsoft Teams API per test. +- Added `Get-CISSPOOutput` function to get the output of the SharePoint Online API per test. - Updated Microsoft Graph tests to utilize the new output functions ('1.1.1', '1.1.3', '1.2.1', '1.3.1', '5.1.2.3', '5.1.8.1', '6.1.2', '6.1.3') - Updated EXO tests to utilize the new output functions ('1.2.2', '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', '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', '8.6.1'). - Updated MSTeams tests to utilize the new output functions ('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') +- Updated SPO tests to utilize the new output functions ('7.2.1', '7.2.2', '7.2.3', '7.2.4', '7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.2.10', '7.3.1', '7.3.2', '7.3.4') ## [0.1.13] - 2024-06-18 From 6b94ee72a5190a953f451870f1649e4d0071b181 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:42:59 -0500 Subject: [PATCH 26/42] add: Get-CISAadOutput function and updated respective tests --- source/Private/Get-CISAadOutput.ps1 | 41 +++++++++++++++++++ .../tests/Test-BlockSharedMailboxSignIn.ps1 | 5 ++- tests/Unit/Private/Get-CISAadOutput.tests.ps1 | 27 ++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 source/Private/Get-CISAadOutput.ps1 create mode 100644 tests/Unit/Private/Get-CISAadOutput.tests.ps1 diff --git a/source/Private/Get-CISAadOutput.ps1 b/source/Private/Get-CISAadOutput.ps1 new file mode 100644 index 0000000..4acf52f --- /dev/null +++ b/source/Private/Get-CISAadOutput.ps1 @@ -0,0 +1,41 @@ +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + .EXAMPLE + $null = Get-Get-CISAadOutput -PrivateData 'NOTHING TO SEE HERE' + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. +#> +function Get-CISAadOutput { + [cmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [String] + $Rec + ) + begin { + # Begin Block # + <# + # Tests + 1.2.2 + # Test number + $testNumbers ="1.2.2" + #> + } + process { + switch ($Rec) { + '1.2.2' { + # Test-BlockSharedMailboxSignIn.ps1 + $users = Get-AzureADUser + } + default { throw "No match found for test: $Rec" } + } + } + end { + Write-Verbose "Retuning data for Rec: $Rec" + return $users + } +} # end function Get-CISAadOutput diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index 9025358..cfda390 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -31,9 +31,10 @@ function Test-BlockSharedMailboxSignIn { try { # Step: Retrieve shared mailbox details $MBX = Get-CISExoOutput -Rec $recnum - + $objectids = $MBX.ExternalDirectoryObjectId + $users = Get-CISAadOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) - $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } + $sharedMailboxDetails = $users | Where-Object {$_.objectid -in $objectids} # Step: Identify enabled mailboxes (Condition B: Pass/Fail) $enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName } diff --git a/tests/Unit/Private/Get-CISAadOutput.tests.ps1 b/tests/Unit/Private/Get-CISAadOutput.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-CISAadOutput.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' + } + } + } +} + From e60c9855e35f53200d259914d64eb1c21b7b4e0e Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:51:57 -0500 Subject: [PATCH 27/42] fix: output functions verbosity --- source/Private/Get-CISAadOutput.ps1 | 2 +- source/Private/Get-CISExoOutput.ps1 | 3 ++- source/Private/Get-CISMSTeamsOutput.ps1 | 1 + source/Private/Get-CISMgOutput.ps1 | 1 + source/Private/Get-CISSpoOutput.ps1 | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/Private/Get-CISAadOutput.ps1 b/source/Private/Get-CISAadOutput.ps1 index 4acf52f..ebfefde 100644 --- a/source/Private/Get-CISAadOutput.ps1 +++ b/source/Private/Get-CISAadOutput.ps1 @@ -35,7 +35,7 @@ function Get-CISAadOutput { } } end { - Write-Verbose "Retuning data for Rec: $Rec" + Write-Verbose "Get-CISAadOutput: Retuning data for Rec: $Rec" return $users } } # end function Get-CISAadOutput diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index b275df5..affea47 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -18,7 +18,7 @@ function Get-CISExoOutput { ) begin { # Begin Block # - <# + <# # Tests 1.2.2 1.3.3 @@ -48,6 +48,7 @@ function Get-CISExoOutput { #> } process { + Write-Verbose "Get-CISExoOutput: Retuning data for Rec: $Rec" switch ($Rec) { '1.2.2' { # Test-BlockSharedMailboxSignIn.ps1 diff --git a/source/Private/Get-CISMSTeamsOutput.ps1 b/source/Private/Get-CISMSTeamsOutput.ps1 index 14b108f..9eff6e4 100644 --- a/source/Private/Get-CISMSTeamsOutput.ps1 +++ b/source/Private/Get-CISMSTeamsOutput.ps1 @@ -36,6 +36,7 @@ function Get-CISMSTeamsOutput { #> } process { + Write-Verbose "Get-CISMSTeamsOutput: Retuning data for Rec: $Rec" switch ($Rec) { '8.1.1' { # Test-TeamsExternalFileSharing.ps1 diff --git a/source/Private/Get-CISMgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 index 55344e8..9a02b9f 100644 --- a/source/Private/Get-CISMgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -38,6 +38,7 @@ function Get-CISMgOutput { #> } process { + Write-Verbose "Get-CISMgOutput: Retuning data for Rec: $Rec" switch ($rec) { '1.1.1' { # 1.1.1 diff --git a/source/Private/Get-CISSpoOutput.ps1 b/source/Private/Get-CISSpoOutput.ps1 index 7ed1088..6821656 100644 --- a/source/Private/Get-CISSpoOutput.ps1 +++ b/source/Private/Get-CISSpoOutput.ps1 @@ -38,6 +38,7 @@ function Get-CISSpoOutput { #> } process { + Write-Verbose "Retuning data for Rec: $Rec" switch ($Rec) { '7.2.1' { # Test-ModernAuthSharePoint.ps1 From be68c1d0d774966941edba598d23e7ff3b9ee0a5 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:59:26 -0500 Subject: [PATCH 28/42] fix: Remove output type for output functions. Add Later --- source/Private/Get-CISAadOutput.ps1 | 4 +--- source/Private/Get-CISExoOutput.ps1 | 4 +--- source/Private/Get-CISMSTeamsOutput.ps1 | 4 +--- source/Private/Get-CISSpoOutput.ps1 | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/source/Private/Get-CISAadOutput.ps1 b/source/Private/Get-CISAadOutput.ps1 index ebfefde..fef1944 100644 --- a/source/Private/Get-CISAadOutput.ps1 +++ b/source/Private/Get-CISAadOutput.ps1 @@ -10,11 +10,9 @@ #> function Get-CISAadOutput { [cmdletBinding()] - [OutputType([string])] param( [Parameter(Mandatory = $true)] - [String] - $Rec + [String]$Rec ) begin { # Begin Block # diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index affea47..ac8280e 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -10,11 +10,9 @@ #> function Get-CISExoOutput { [cmdletBinding()] - [OutputType([string])] param( [Parameter(Mandatory = $true)] - [String] - $Rec + [String]$Rec ) begin { # Begin Block # diff --git a/source/Private/Get-CISMSTeamsOutput.ps1 b/source/Private/Get-CISMSTeamsOutput.ps1 index 9eff6e4..6c157cb 100644 --- a/source/Private/Get-CISMSTeamsOutput.ps1 +++ b/source/Private/Get-CISMSTeamsOutput.ps1 @@ -10,11 +10,9 @@ #> function Get-CISMSTeamsOutput { [cmdletBinding()] - [OutputType([string])] param( [Parameter(Mandatory = $true)] - [String] - $Rec + [String]$Rec ) begin { # Begin Block # diff --git a/source/Private/Get-CISSpoOutput.ps1 b/source/Private/Get-CISSpoOutput.ps1 index 6821656..06c56a4 100644 --- a/source/Private/Get-CISSpoOutput.ps1 +++ b/source/Private/Get-CISSpoOutput.ps1 @@ -10,11 +10,9 @@ #> function Get-CISSpoOutput { [cmdletBinding()] - [OutputType([string])] param( [Parameter(Mandatory = $true)] - [String] - $Rec + [String]$Rec ) begin { # Begin Block # From defcf56c82e981e2a761ebc1c8b8ed2b8b482abf Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:00:12 -0500 Subject: [PATCH 29/42] add: DomainName paramter to get-mggraph and test 1.3.1 --- source/Private/Get-CISMgOutput.ps1 | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/Private/Get-CISMgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 index 9a02b9f..46f9ff7 100644 --- a/source/Private/Get-CISMgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -14,11 +14,11 @@ function Get-CISMgOutput { #> [cmdletBinding()] - [OutputType([string])] param( [Parameter(Mandatory = $true)] - [String] - $Rec + [String]$Rec, + [Parameter(Mandatory = $false)] + [String]$DomainName ) begin { @@ -56,6 +56,15 @@ function Get-CISMgOutput { $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility return $allGroups } + '1.3.1' { + # Test-PasswordNeverExpirePolicy.ps1 + $domains = if ($DomainName) { + Get-MgDomain -DomainId $DomainName + } else { + Get-MgDomain + } + return $domains + } '5.1.2.3' { # Retrieve the tenant creation policy $tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants From 1e75fbd33519317957d5193032b85b7035076821 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:11:04 -0500 Subject: [PATCH 30/42] add: DomainName paramter to get-mggraph and test 1.3.1 --- .../tests/Test-PasswordNeverExpirePolicy.ps1 | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/source/tests/Test-PasswordNeverExpirePolicy.ps1 b/source/tests/Test-PasswordNeverExpirePolicy.ps1 index c6a41c0..ecd0989 100644 --- a/source/tests/Test-PasswordNeverExpirePolicy.ps1 +++ b/source/tests/Test-PasswordNeverExpirePolicy.ps1 @@ -39,12 +39,7 @@ function Test-PasswordNeverExpirePolicy { process { try { # Step: Retrieve all domains or a specific domain - $domains = if ($DomainName) { - Get-MgDomain -DomainId $DomainName - } else { - Get-MgDomain - } - + $domains = Get-CISMgOutput -Rec $recnum -DomainId $DomainName foreach ($domain in $domains) { $domainName = $domain.Id $isDefault = $domain.IsDefault @@ -60,7 +55,8 @@ function Test-PasswordNeverExpirePolicy { # Step (Condition A & B): Prepare failure reasons and details based on compliance $failureReasons = if ($notificationPolIsCompliant -and $pwPolIsCompliant) { "N/A" - } else { + } + else { "Password expiration is not set to never expire or notification window is not set to 30 days for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30`n" } @@ -86,16 +82,8 @@ function Test-PasswordNeverExpirePolicy { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } From a141380f3fbf0107d4e6226508d8a4a0af54e083 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:14:37 -0500 Subject: [PATCH 31/42] add: simplified error handling with Get-TestError --- source/Private/Get-TestError.ps1 | 33 +++++++++++++++++++ .../Test-AdministrativeAccountCompliance.ps1 | 11 ++----- source/tests/Test-AntiPhishingPolicy.ps1 | 12 ++----- source/tests/Test-AuditDisabledFalse.ps1 | 12 ++----- source/tests/Test-AuditLogSearch.ps1 | 12 ++----- source/tests/Test-BlockChannelEmails.ps1 | 12 ++----- source/tests/Test-BlockMailForwarding.ps1 | 12 ++----- .../tests/Test-BlockSharedMailboxSignIn.ps1 | 12 ++----- source/tests/Test-CommonAttachmentFilter.ps1 | 12 ++----- source/tests/Test-CustomerLockbox.ps1 | 12 ++----- source/tests/Test-DialInBypassLobby.ps1 | 12 ++----- .../Test-DisallowInfectedFilesDownload.ps1 | 12 ++----- source/tests/Test-EnableDKIM.ps1 | 12 ++----- source/tests/Test-ExternalNoControl.ps1 | 12 ++----- .../tests/Test-ExternalSharingCalendars.ps1 | 12 ++----- source/tests/Test-GlobalAdminsCount.ps1 | 12 ++----- source/tests/Test-GuestAccessExpiration.ps1 | 12 ++----- .../tests/Test-GuestUsersBiweeklyReview.ps1 | 12 ++----- source/tests/Test-IdentifyExternalEmail.ps1 | 12 ++----- source/tests/Test-LinkSharingRestrictions.ps1 | 12 ++----- source/tests/Test-MailTipsEnabled.ps1 | 12 ++----- .../Test-ManagedApprovedPublicGroups.ps1 | 12 ++----- source/tests/Test-MeetingChatNoAnonymous.ps1 | 12 ++----- .../tests/Test-ModernAuthExchangeOnline.ps1 | 12 ++----- source/tests/Test-ModernAuthSharePoint.ps1 | 12 ++----- source/tests/Test-NoAnonymousMeetingJoin.ps1 | 12 ++----- source/tests/Test-NoAnonymousMeetingStart.ps1 | 12 ++----- source/tests/Test-NoWhitelistDomains.ps1 | 12 ++----- source/tests/Test-NotifyMalwareInternal.ps1 | 12 ++----- .../Test-OneDriveContentRestrictions.ps1 | 12 ++----- .../tests/Test-OneDriveSyncRestrictions.ps1 | 12 ++----- source/tests/Test-OrgOnlyBypassLobby.ps1 | 12 ++----- source/tests/Test-OrganizersPresent.ps1 | 12 ++----- source/tests/Test-PasswordHashSync.ps1 | 12 ++----- source/tests/Test-ReauthWithCode.ps1 | 12 ++----- source/tests/Test-ReportSecurityInTeams.ps1 | 12 ++----- source/tests/Test-RestrictCustomScripts.ps1 | 12 ++----- source/tests/Test-RestrictExternalSharing.ps1 | 12 ++----- source/tests/Test-RestrictOutlookAddins.ps1 | 12 ++----- .../Test-RestrictStorageProvidersOutlook.ps1 | 12 ++----- source/tests/Test-RestrictTenantCreation.ps1 | 12 ++----- source/tests/Test-SharePointAADB2B.ps1 | 12 ++----- .../Test-SharePointExternalSharingDomains.ps1 | 12 ++----- .../Test-SharePointGuestsItemSharing.ps1 | 12 ++----- source/tests/Test-SpamPolicyAdminNotify.ps1 | 12 ++----- source/tests/Test-TeamsExternalAccess.ps1 | 12 ++----- .../tests/Test-TeamsExternalFileSharing.ps1 | 12 ++----- tests/Unit/Private/Get-TestError.tests.ps1 | 27 +++++++++++++++ 48 files changed, 152 insertions(+), 459 deletions(-) create mode 100644 source/Private/Get-TestError.ps1 create mode 100644 tests/Unit/Private/Get-TestError.tests.ps1 diff --git a/source/Private/Get-TestError.ps1 b/source/Private/Get-TestError.ps1 new file mode 100644 index 0000000..48cb531 --- /dev/null +++ b/source/Private/Get-TestError.ps1 @@ -0,0 +1,33 @@ + + +<# + .SYNOPSIS + This is a sample Private function only visible within the module. + + .DESCRIPTION + This sample function is not exported to the module and only return the data passed as parameter. + + .EXAMPLE + $null = Get-TestError -PrivateData 'NOTHING TO SEE HERE' + + .PARAMETER PrivateData + The PrivateData parameter is what will be returned without transformation. + +#> + +function Get-TestError { + [cmdletBinding()] + param ( + $LastError, + $recnum + ) + # 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 = $LastError }) + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + Write-Verbose "An error occurred during the test: `n$LastError" -VerboseAction Continue + return $auditResult +} + diff --git a/source/tests/Test-AdministrativeAccountCompliance.ps1 b/source/tests/Test-AdministrativeAccountCompliance.ps1 index 2f205f8..50a2605 100644 --- a/source/tests/Test-AdministrativeAccountCompliance.ps1 +++ b/source/tests/Test-AdministrativeAccountCompliance.ps1 @@ -107,15 +107,8 @@ function Test-AdministrativeAccountCompliance { $auditResult = Initialize-CISAuditResult @params } catch { - Write-Error "An error occurred during the test: $_" - - # Handle the error and create a failure result - $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 41bcb26..f2027ca 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -92,16 +92,8 @@ function Test-AntiPhishingPolicy { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-AuditDisabledFalse.ps1 b/source/tests/Test-AuditDisabledFalse.ps1 index 0aa3de6..fd8436e 100644 --- a/source/tests/Test-AuditDisabledFalse.ps1 +++ b/source/tests/Test-AuditDisabledFalse.ps1 @@ -63,16 +63,8 @@ function Test-AuditDisabledFalse { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-AuditLogSearch.ps1 b/source/tests/Test-AuditLogSearch.ps1 index 5180b6c..2d44b69 100644 --- a/source/tests/Test-AuditLogSearch.ps1 +++ b/source/tests/Test-AuditLogSearch.ps1 @@ -66,16 +66,8 @@ function Test-AuditLogSearch { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-BlockChannelEmails.ps1 b/source/tests/Test-BlockChannelEmails.ps1 index 26f13dd..0468ed0 100644 --- a/source/tests/Test-BlockChannelEmails.ps1 +++ b/source/tests/Test-BlockChannelEmails.ps1 @@ -62,16 +62,8 @@ function Test-BlockChannelEmails { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-BlockMailForwarding.ps1 b/source/tests/Test-BlockMailForwarding.ps1 index a46b641..81177ff 100644 --- a/source/tests/Test-BlockMailForwarding.ps1 +++ b/source/tests/Test-BlockMailForwarding.ps1 @@ -88,16 +88,8 @@ function Test-BlockMailForwarding { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index cfda390..974e8f4 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -67,16 +67,8 @@ function Test-BlockSharedMailboxSignIn { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 978f3ca..7c29bcd 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -68,16 +68,8 @@ function Test-CommonAttachmentFilter { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index 559caaf..9a003d9 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -62,16 +62,8 @@ function Test-CustomerLockbox { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-DialInBypassLobby.ps1 b/source/tests/Test-DialInBypassLobby.ps1 index 89bce50..f2bf4e6 100644 --- a/source/tests/Test-DialInBypassLobby.ps1 +++ b/source/tests/Test-DialInBypassLobby.ps1 @@ -62,16 +62,8 @@ function Test-DialInBypassLobby { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-DisallowInfectedFilesDownload.ps1 b/source/tests/Test-DisallowInfectedFilesDownload.ps1 index 2da1c17..851c90b 100644 --- a/source/tests/Test-DisallowInfectedFilesDownload.ps1 +++ b/source/tests/Test-DisallowInfectedFilesDownload.ps1 @@ -65,16 +65,8 @@ function Test-DisallowInfectedFilesDownload { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index 8b8a758..fa86282 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -66,16 +66,8 @@ function Test-EnableDKIM { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ExternalNoControl.ps1 b/source/tests/Test-ExternalNoControl.ps1 index 70a69fa..3b377ab 100644 --- a/source/tests/Test-ExternalNoControl.ps1 +++ b/source/tests/Test-ExternalNoControl.ps1 @@ -64,16 +64,8 @@ function Test-ExternalNoControl { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index d2ae686..ba0d8a3 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -70,16 +70,8 @@ function Test-ExternalSharingCalendars { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-GlobalAdminsCount.ps1 b/source/tests/Test-GlobalAdminsCount.ps1 index 8b5580c..d2804f5 100644 --- a/source/tests/Test-GlobalAdminsCount.ps1 +++ b/source/tests/Test-GlobalAdminsCount.ps1 @@ -65,16 +65,8 @@ function Test-GlobalAdminsCount { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-GuestAccessExpiration.ps1 b/source/tests/Test-GuestAccessExpiration.ps1 index 8c0fcbc..92d40a0 100644 --- a/source/tests/Test-GuestAccessExpiration.ps1 +++ b/source/tests/Test-GuestAccessExpiration.ps1 @@ -58,16 +58,8 @@ function Test-GuestAccessExpiration { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-GuestUsersBiweeklyReview.ps1 b/source/tests/Test-GuestUsersBiweeklyReview.ps1 index 757c4d3..1f4c633 100644 --- a/source/tests/Test-GuestUsersBiweeklyReview.ps1 +++ b/source/tests/Test-GuestUsersBiweeklyReview.ps1 @@ -50,16 +50,8 @@ function Test-GuestUsersBiweeklyReview { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-IdentifyExternalEmail.ps1 b/source/tests/Test-IdentifyExternalEmail.ps1 index ebca9fd..571b41d 100644 --- a/source/tests/Test-IdentifyExternalEmail.ps1 +++ b/source/tests/Test-IdentifyExternalEmail.ps1 @@ -62,16 +62,8 @@ function Test-IdentifyExternalEmail { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-LinkSharingRestrictions.ps1 b/source/tests/Test-LinkSharingRestrictions.ps1 index 2144646..9816d51 100644 --- a/source/tests/Test-LinkSharingRestrictions.ps1 +++ b/source/tests/Test-LinkSharingRestrictions.ps1 @@ -58,16 +58,8 @@ function Test-LinkSharingRestrictions { } 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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-MailTipsEnabled.ps1 b/source/tests/Test-MailTipsEnabled.ps1 index c51f7ba..aa6d6bc 100644 --- a/source/tests/Test-MailTipsEnabled.ps1 +++ b/source/tests/Test-MailTipsEnabled.ps1 @@ -70,16 +70,8 @@ function Test-MailTipsEnabled { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ManagedApprovedPublicGroups.ps1 b/source/tests/Test-ManagedApprovedPublicGroups.ps1 index 2c2140f..944b47e 100644 --- a/source/tests/Test-ManagedApprovedPublicGroups.ps1 +++ b/source/tests/Test-ManagedApprovedPublicGroups.ps1 @@ -60,16 +60,8 @@ function Test-ManagedApprovedPublicGroups { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-MeetingChatNoAnonymous.ps1 b/source/tests/Test-MeetingChatNoAnonymous.ps1 index 4420efc..64e0e2e 100644 --- a/source/tests/Test-MeetingChatNoAnonymous.ps1 +++ b/source/tests/Test-MeetingChatNoAnonymous.ps1 @@ -57,16 +57,8 @@ function Test-MeetingChatNoAnonymous { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ModernAuthExchangeOnline.ps1 b/source/tests/Test-ModernAuthExchangeOnline.ps1 index d4f4738..d4cd0bc 100644 --- a/source/tests/Test-ModernAuthExchangeOnline.ps1 +++ b/source/tests/Test-ModernAuthExchangeOnline.ps1 @@ -59,16 +59,8 @@ function Test-ModernAuthExchangeOnline { } 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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ModernAuthSharePoint.ps1 b/source/tests/Test-ModernAuthSharePoint.ps1 index 85336c9..64e2598 100644 --- a/source/tests/Test-ModernAuthSharePoint.ps1 +++ b/source/tests/Test-ModernAuthSharePoint.ps1 @@ -57,16 +57,8 @@ function Test-ModernAuthSharePoint { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-NoAnonymousMeetingJoin.ps1 b/source/tests/Test-NoAnonymousMeetingJoin.ps1 index bad4009..a0ae10f 100644 --- a/source/tests/Test-NoAnonymousMeetingJoin.ps1 +++ b/source/tests/Test-NoAnonymousMeetingJoin.ps1 @@ -57,16 +57,8 @@ function Test-NoAnonymousMeetingJoin { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-NoAnonymousMeetingStart.ps1 b/source/tests/Test-NoAnonymousMeetingStart.ps1 index 04498e0..424e756 100644 --- a/source/tests/Test-NoAnonymousMeetingStart.ps1 +++ b/source/tests/Test-NoAnonymousMeetingStart.ps1 @@ -58,16 +58,8 @@ function Test-NoAnonymousMeetingStart { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-NoWhitelistDomains.ps1 b/source/tests/Test-NoWhitelistDomains.ps1 index 076b2c1..da28030 100644 --- a/source/tests/Test-NoWhitelistDomains.ps1 +++ b/source/tests/Test-NoWhitelistDomains.ps1 @@ -68,16 +68,8 @@ function Test-NoWhitelistDomains { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index d08da4f..b87c381 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -74,16 +74,8 @@ function Test-NotifyMalwareInternal { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-OneDriveContentRestrictions.ps1 b/source/tests/Test-OneDriveContentRestrictions.ps1 index bed9d6e..44b9d7f 100644 --- a/source/tests/Test-OneDriveContentRestrictions.ps1 +++ b/source/tests/Test-OneDriveContentRestrictions.ps1 @@ -63,16 +63,8 @@ function Test-OneDriveContentRestrictions { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-OneDriveSyncRestrictions.ps1 b/source/tests/Test-OneDriveSyncRestrictions.ps1 index 373a815..f689d37 100644 --- a/source/tests/Test-OneDriveSyncRestrictions.ps1 +++ b/source/tests/Test-OneDriveSyncRestrictions.ps1 @@ -63,16 +63,8 @@ function Test-OneDriveSyncRestrictions { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-OrgOnlyBypassLobby.ps1 b/source/tests/Test-OrgOnlyBypassLobby.ps1 index 104181e..87b7d24 100644 --- a/source/tests/Test-OrgOnlyBypassLobby.ps1 +++ b/source/tests/Test-OrgOnlyBypassLobby.ps1 @@ -66,16 +66,8 @@ function Test-OrgOnlyBypassLobby { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-OrganizersPresent.ps1 b/source/tests/Test-OrganizersPresent.ps1 index 4a3be9f..d85ff32 100644 --- a/source/tests/Test-OrganizersPresent.ps1 +++ b/source/tests/Test-OrganizersPresent.ps1 @@ -61,16 +61,8 @@ function Test-OrganizersPresent { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-PasswordHashSync.ps1 b/source/tests/Test-PasswordHashSync.ps1 index c054cfb..d131a3e 100644 --- a/source/tests/Test-PasswordHashSync.ps1 +++ b/source/tests/Test-PasswordHashSync.ps1 @@ -58,16 +58,8 @@ function Test-PasswordHashSync { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ReauthWithCode.ps1 b/source/tests/Test-ReauthWithCode.ps1 index e5d0f3a..9e68202 100644 --- a/source/tests/Test-ReauthWithCode.ps1 +++ b/source/tests/Test-ReauthWithCode.ps1 @@ -58,16 +58,8 @@ function Test-ReauthWithCode { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index c5c822a..0247956 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -57,16 +57,8 @@ function Test-ReportSecurityInTeams { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-RestrictCustomScripts.ps1 b/source/tests/Test-RestrictCustomScripts.ps1 index 7705721..88f1c75 100644 --- a/source/tests/Test-RestrictCustomScripts.ps1 +++ b/source/tests/Test-RestrictCustomScripts.ps1 @@ -111,16 +111,8 @@ function Test-RestrictCustomScripts { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-RestrictExternalSharing.ps1 b/source/tests/Test-RestrictExternalSharing.ps1 index 3c99f67..d508445 100644 --- a/source/tests/Test-RestrictExternalSharing.ps1 +++ b/source/tests/Test-RestrictExternalSharing.ps1 @@ -63,16 +63,8 @@ function Test-RestrictExternalSharing { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-RestrictOutlookAddins.ps1 b/source/tests/Test-RestrictOutlookAddins.ps1 index 8c5408e..4952b24 100644 --- a/source/tests/Test-RestrictOutlookAddins.ps1 +++ b/source/tests/Test-RestrictOutlookAddins.ps1 @@ -76,16 +76,8 @@ function Test-RestrictOutlookAddins { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 index a466f72..1db39e5 100644 --- a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 +++ b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 @@ -67,16 +67,8 @@ function Test-RestrictStorageProvidersOutlook { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-RestrictTenantCreation.ps1 b/source/tests/Test-RestrictTenantCreation.ps1 index 476eb99..b6f5382 100644 --- a/source/tests/Test-RestrictTenantCreation.ps1 +++ b/source/tests/Test-RestrictTenantCreation.ps1 @@ -59,16 +59,8 @@ function Test-RestrictTenantCreation { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-SharePointAADB2B.ps1 b/source/tests/Test-SharePointAADB2B.ps1 index d0bf748..afbc0d9 100644 --- a/source/tests/Test-SharePointAADB2B.ps1 +++ b/source/tests/Test-SharePointAADB2B.ps1 @@ -46,16 +46,8 @@ function Test-SharePointAADB2B { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-SharePointExternalSharingDomains.ps1 b/source/tests/Test-SharePointExternalSharingDomains.ps1 index 64c53c6..246c41c 100644 --- a/source/tests/Test-SharePointExternalSharingDomains.ps1 +++ b/source/tests/Test-SharePointExternalSharingDomains.ps1 @@ -47,16 +47,8 @@ function Test-SharePointExternalSharingDomains { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-SharePointGuestsItemSharing.ps1 b/source/tests/Test-SharePointGuestsItemSharing.ps1 index b09a66a..58c834f 100644 --- a/source/tests/Test-SharePointGuestsItemSharing.ps1 +++ b/source/tests/Test-SharePointGuestsItemSharing.ps1 @@ -47,16 +47,8 @@ function Test-SharePointGuestsItemSharing { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index 9d6779c..a574612 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -65,16 +65,8 @@ function Test-SpamPolicyAdminNotify { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-TeamsExternalAccess.ps1 b/source/tests/Test-TeamsExternalAccess.ps1 index 787720b..9c7d320 100644 --- a/source/tests/Test-TeamsExternalAccess.ps1 +++ b/source/tests/Test-TeamsExternalAccess.ps1 @@ -54,16 +54,8 @@ function Test-TeamsExternalAccess { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/source/tests/Test-TeamsExternalFileSharing.ps1 b/source/tests/Test-TeamsExternalFileSharing.ps1 index 7f61ec1..9fe0374 100644 --- a/source/tests/Test-TeamsExternalFileSharing.ps1 +++ b/source/tests/Test-TeamsExternalFileSharing.ps1 @@ -49,16 +49,8 @@ function Test-TeamsExternalFileSharing { $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 + $LastError = $_ + $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } diff --git a/tests/Unit/Private/Get-TestError.tests.ps1 b/tests/Unit/Private/Get-TestError.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-TestError.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' + } + } + } +} + From 011f91cdde08cb483aabb71bc1f195ae9661ae64 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:15:29 -0500 Subject: [PATCH 32/42] docs: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 291bdc7..fa8712d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Added `Get-CISExoOutput` function to get the output of the Exchange Online API per test. - Added `Get-CISMSTeamsOutput` function to get the output of the Microsoft Teams API per test. - Added `Get-CISSPOOutput` function to get the output of the SharePoint Online API per test. +- Added `Get-TestError` function to get the error output of a test. - Updated Microsoft Graph tests to utilize the new output functions ('1.1.1', '1.1.3', '1.2.1', '1.3.1', '5.1.2.3', '5.1.8.1', '6.1.2', '6.1.3') - Updated EXO tests to utilize the new output functions ('1.2.2', '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', '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', '8.6.1'). - Updated MSTeams tests to utilize the new output functions ('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') From 82df16623e92a1c5cfd6b1a8d5af2d5347ac2378 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:22:08 -0500 Subject: [PATCH 33/42] docs: Update CommentBlock for Remove-RowsWithEmptyCSVStatus --- source/Public/Remove-RowsWithEmptyCSVStatus.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/Public/Remove-RowsWithEmptyCSVStatus.ps1 b/source/Public/Remove-RowsWithEmptyCSVStatus.ps1 index 7258500..0e4f38e 100644 --- a/source/Public/Remove-RowsWithEmptyCSVStatus.ps1 +++ b/source/Public/Remove-RowsWithEmptyCSVStatus.ps1 @@ -1,3 +1,18 @@ +<# + .SYNOPSIS + Removes rows from an Excel worksheet where the 'CSV_Status' column is empty and saves the result to a new file. + .DESCRIPTION + The Remove-RowsWithEmptyCSVStatus function imports data from a specified worksheet in an Excel file, checks for the presence of the 'CSV_Status' column, and filters out rows where the 'CSV_Status' column is empty. The filtered data is then exported to a new Excel file with a '-Filtered' suffix added to the original file name. + .PARAMETER FilePath + The path to the Excel file to be processed. + .PARAMETER WorksheetName + The name of the worksheet within the Excel file to be processed. + .EXAMPLE + PS C:\> Remove-RowsWithEmptyCSVStatus -FilePath "C:\Reports\Report.xlsx" -WorksheetName "Sheet1" + This command imports data from the "Sheet1" worksheet in the "Report.xlsx" file, removes rows where the 'CSV_Status' column is empty, and saves the filtered data to a new file named "Report-Filtered.xlsx" in the same directory. + .NOTES + This function requires the ImportExcel module to be installed. +#> function Remove-RowsWithEmptyCSVStatus { [CmdletBinding()] param ( From ca1734381f4afd6463cf7be89a99f8038aed106f Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:28:01 -0500 Subject: [PATCH 34/42] docs: Update CHANGELOG and Help Uri --- CHANGELOG.md | 1 + README.md | Bin 36208 -> 37784 bytes docs/index.html | Bin 94070 -> 99072 bytes 3 files changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8712d..890df99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed test 1.3.1 to include notification window for password expiration. - Fixed 6.1.1 test definition to include the correct connection. - Removed banner and warning from EXO and AzureAD connection step. +- Fixed missing CommentBlock for `Remove-RowsWithEmptyCSVStatus` function. ### Added diff --git a/README.md b/README.md index eb83ba81775750fdc48597384cae132c76f6fa26..daa40ca610e4ed6a3a624526661373f91bda7817 100644 GIT binary patch delta 978 zcmbVLO-~bH5S{|TmX>W7(kN(?*G96a^h+^OV+cZM{3044BqoidwoBZ&Z8vQdP@vj? zCvS5hUcBkmX75J-fj_~-gC6TY@OfXmfJ!f>+3tML^UTb)-}LOg@A;5R=?Pk-$7GR1 z^N{Y+9e#N_8l0s^WKsd18n}PO3665C3WnQ{3cxqR3 zuU;4p_72j3`*tSib019I=$9J*oc`{Xyr!g8 zC71Zy_W2bFu8K!cT>%wj&@A9DQ;t^fu2UY*6XY3)DWHZ8twd#Lma1k#a}cuuOCI(j z-Ze<7FUN+DpOm#L$}8YEfkMzKvTZWCb>%w0d%nz(7aBXkevWJkww2t+Uk8$ET>Nb7 z45#%ItTofRqrevwsT%%^?7L_1m#r)!G(NR6!J&q~*P=4(%|w^zw&rKC>u1{t9a{oz z5MljQlz&_tjowsrD(F(DIa&v0lWY!5rnox(5KylL5|s78j|`t(fZqW<8DGQD4(mze z+pw-voCFVU#&iK`Q4QKee1_uu`P_&h$6CVA8=rLugIrxvEg{dTg=qNi{LIc+Bi_BM zH)xtb9l7YEPhw4B`p-jX{BsGinMY>-7GU#k?DzKTfFoPVi|J5QF}LAXDycgObwj-rgPy{{odH**^dP delta 151 zcmbQSoaw_XrVTOrn^&~iFmAqJP{KHQLL|rLdqxi2oAaEx7$<*lVA*V7@4`0ufa9~x zac(S(K!(9)2Od7g%}YEs7*C#Hr7`(Qqs`A)g_SA(cUap@uR_`=!$&hVDeNyoV*_JzGDik+V?-koSna-Lm9-sf#i0*V zG5E5FjRRiN7(?Qde?kKHp^#`&OeE1n0x!IfEGGIOfxrt8-}Lw0wp%wal9(^w_uYHX zJ?H$+@BHq4H1GQ8WX0??{}yVc4r;^u5?RzvAqr!>&3xM~+!-3K+)nS%Bz4M;NVDu8 zUi-O=%k@m;-KwHNKRj~3t~in3KYdv;nH5rgv`)3Wz#j3`c%$FD(^_u5;XpC%%!jjT+`dODw%p52?73-?}mrWA6c}vg! z;bZaiUe%sEYgr$hOL&Wb^v_@Y{ERW>{SCHcCX$HdvuX! z;rUJSMZ!J!bfq}nMd*28X~3K``I9I&4SRtv`JuCR`st!RPEZTt>^CSaDu}Q^L_VKw z$Xl#9PjK3JIE33Y1*(%iLr#A4GG>NR99=5Sh>LS3UK`;^%^ zg4_!s<;I)->OzZPO@N&^*}A*&Hp|U*;guG>y%5$`>w~;etYC%mS!l|aLAEd}Kee$# z-`jk>TxI;6lZjV@GJDxA=eK&)d>aR=Ck&%e`QF97_v)T-r-ZhFD4fIG`Y7tl(}$ zw3wkG3Pzny9;X%@%2vvr_3~Bd=v*-SSAcV6#d}v}8@XGCBUS3&PIhO#O+3=~50bBd A82|tP delta 262 zcmZo@WBYcGb;3DW0|tEtBL)K?Hepa;Fk~=humX}MK+=f8bmQybgvoA+I_x$KNeo2{ zcAE%YG*@c~`I4WUf{vxQuqIC)0L6J4Pnf&Fgxim?t|JvQ3V2tC*C^x7nb8 zgK@LNlsuu$+m;<+-R!r)h;j3VwR_k$*R11W++4L)fN^roI+N`Uc8nKTri*Yf%4}Y@ zyCPw7K&!wuWkwlRRzHS(hP=s*Iogvq*s@IanV7PDNMP^1@<4Hmd8wv fi`ClRWX5=adAgSsqXW#XlOGlcZ=YkunBoBd@C8>{ From 4e0b20fc1486954cf8e494de3c2b05bc452c7d74 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:58:27 -0500 Subject: [PATCH 35/42] fix: Test-Error verbose call and Get-CISMgOutput Parameter --- source/Private/Get-CISMgOutput.ps1 | 3 ++- source/Private/Get-TestError.ps1 | 2 +- source/tests/Test-PasswordNeverExpirePolicy.ps1 | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/Private/Get-CISMgOutput.ps1 b/source/Private/Get-CISMgOutput.ps1 index 46f9ff7..c5bcca8 100644 --- a/source/Private/Get-CISMgOutput.ps1 +++ b/source/Private/Get-CISMgOutput.ps1 @@ -60,7 +60,8 @@ function Get-CISMgOutput { # Test-PasswordNeverExpirePolicy.ps1 $domains = if ($DomainName) { Get-MgDomain -DomainId $DomainName - } else { + } + else { Get-MgDomain } return $domains diff --git a/source/Private/Get-TestError.ps1 b/source/Private/Get-TestError.ps1 index 48cb531..83bb5f4 100644 --- a/source/Private/Get-TestError.ps1 +++ b/source/Private/Get-TestError.ps1 @@ -27,7 +27,7 @@ function Get-TestError { $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $LastError }) # Call Initialize-CISAuditResult with error parameters $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - Write-Verbose "An error occurred during the test: `n$LastError" -VerboseAction Continue + Write-Verbose "An error occurred during the test: `n$LastError" -Verbose return $auditResult } diff --git a/source/tests/Test-PasswordNeverExpirePolicy.ps1 b/source/tests/Test-PasswordNeverExpirePolicy.ps1 index ecd0989..d550657 100644 --- a/source/tests/Test-PasswordNeverExpirePolicy.ps1 +++ b/source/tests/Test-PasswordNeverExpirePolicy.ps1 @@ -39,7 +39,7 @@ function Test-PasswordNeverExpirePolicy { process { try { # Step: Retrieve all domains or a specific domain - $domains = Get-CISMgOutput -Rec $recnum -DomainId $DomainName + $domains = Get-CISMgOutput -Rec $recnum -DomainName $DomainName foreach ($domain in $domains) { $domainName = $domain.Id $isDefault = $domain.IsDefault From e81395bb4cb9ee7ff3b48934ec878b54396bb35d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:16:19 -0500 Subject: [PATCH 36/42] fix: Connect host output --- source/Private/Connect-M365Suite.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index 0f9587d..54ec3cf 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -101,9 +101,8 @@ function Connect-M365Suite { Write-Host "Connected to the following tenants:" -ForegroundColor Yellow foreach ($tenant in $tenantInfo) { Write-Host "Service: $($tenant.Service)" -ForegroundColor Cyan - Write-Host "Tenant Name: $($tenant.TenantName)" -ForegroundColor Green + Write-Host "Tenant Context: $($tenant.TenantName)`n" -ForegroundColor Green #Write-Host "Tenant ID: $($tenant.TenantID)" - Write-Host "" } $confirmation = Read-Host "Do you want to proceed with these connections? (Y/N)" if ($confirmation -notlike 'Y') { From 6dc52f5b89269842b04ba7092908469fc28290be Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:16:56 -0500 Subject: [PATCH 37/42] docs: Formatting Invoke-M365SecurityAudit --- source/Public/Invoke-M365SecurityAudit.ps1 | 167 +++++++++++---------- 1 file changed, 86 insertions(+), 81 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index fbd72f7..ad778ea 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -1,106 +1,111 @@ <# .SYNOPSIS - Invokes a security audit for Microsoft 365 environments. + Invokes a security audit for Microsoft 365 environments. .DESCRIPTION - The Invoke-M365SecurityAudit cmdlet performs a comprehensive security audit based on the specified parameters. It allows auditing of various configurations and settings within a Microsoft 365 environment, such as compliance with CIS benchmarks. + The Invoke-M365SecurityAudit cmdlet performs a comprehensive security audit based on the specified parameters. It allows auditing of various configurations and settings within a Microsoft 365 environment, such as compliance with CIS benchmarks. .PARAMETER TenantAdminUrl - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. .PARAMETER M365DomainForPWPolicyTest - The domain name of the Microsoft 365 environment to test. This parameter is not mandatory and by default it will pass/fail all found domains as a group if a specific domain is not specified. + The domain name of the Microsoft 365 environment to test. This parameter is not mandatory and by default it will pass/fail all found domains as a group if a specific domain is not specified. .PARAMETER ELevel - Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter. + Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter. .PARAMETER ProfileLevel - Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter. + Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter. .PARAMETER IncludeIG1 - If specified, includes tests where IG1 is true. + If specified, includes tests where IG1 is true. .PARAMETER IncludeIG2 - If specified, includes tests where IG2 is true. + If specified, includes tests where IG2 is true. .PARAMETER IncludeIG3 - If specified, includes tests where IG3 is true. + If specified, includes tests where IG3 is true. .PARAMETER IncludeRecommendation - Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers. + Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers. .PARAMETER SkipRecommendation - Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers. + Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers. .PARAMETER DoNotConnect - If specified, the cmdlet will not establish a connection to Microsoft 365 services. + If specified, the cmdlet will not establish a connection to Microsoft 365 services. .PARAMETER DoNotDisconnect - If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. + If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. .PARAMETER NoModuleCheck - If specified, the cmdlet will not check for the presence of required modules. + If specified, the cmdlet will not check for the presence of required modules. .PARAMETER DoNotConfirmConnections - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. .EXAMPLE - PS> Invoke-M365SecurityAudit - Performs a security audit using default parameters. - Output: - Status : Fail - ELevel : E3 - ProfileLevel: L1 - Connection : Microsoft Graph - Rec : 1.1.1 - Result : False - Details : Non-compliant accounts: - Username | Roles | HybridStatus | Missing Licence - user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM - user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 - FailureReason: Non-Compliant Accounts: 2 + PS> Invoke-M365SecurityAudit + + Performs a security audit using default parameters. + Output: + Status : Fail + ELevel : E3 + ProfileLevel: L1 + Connection : Microsoft Graph + Rec : 1.1.1 + Result : False + Details : Non-compliant accounts: + Username | Roles | HybridStatus | Missing Licence + user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM + user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 + FailureReason: Non-Compliant Accounts: 2 .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -ELevel "E5" -ProfileLevel "L1" - Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment. - Output: - Status : Fail - ELevel : E5 - ProfileLevel: L1 - Connection : Microsoft Graph - Rec : 1.1.1 - Result : False - Details : Non-compliant accounts: - Username | Roles | HybridStatus | Missing Licence - user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM - user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 - FailureReason: Non-Compliant Accounts: 2 + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -ELevel "E5" -ProfileLevel "L1" + + Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment. + Output: + Status : Fail + ELevel : E5 + ProfileLevel: L1 + Connection : Microsoft Graph + Rec : 1.1.1 + Result : False + Details : Non-compliant accounts: + Username | Roles | HybridStatus | Missing Licence + user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM + user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 + FailureReason: Non-Compliant Accounts: 2 .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -IncludeIG1 - Performs an audit including all tests where IG1 is true. - Output: - Status : Fail - ELevel : E3 - ProfileLevel: L1 - Connection : Microsoft Graph - Rec : 1.1.1 - Result : False - Details : Non-compliant accounts: - Username | Roles | HybridStatus | Missing Licence - user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM - user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 - FailureReason: Non-Compliant Accounts: 2 + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -IncludeIG1 + + Performs an audit including all tests where IG1 is true. + Output: + Status : Fail + ELevel : E3 + ProfileLevel: L1 + Connection : Microsoft Graph + Rec : 1.1.1 + Result : False + Details : Non-compliant accounts: + Username | Roles | HybridStatus | Missing Licence + user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM + user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 + FailureReason: Non-Compliant Accounts: 2 .EXAMPLE - PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -SkipRecommendation '1.1.3', '2.1.1' - Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1. - Output: - Status : Fail - ELevel : E3 - ProfileLevel: L1 - Connection : Microsoft Graph - Rec : 1.1.1 - Result : False - Details : Non-compliant accounts: - Username | Roles | HybridStatus | Missing Licence - user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM - user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 - FailureReason: Non-Compliant Accounts: 2 + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -SkipRecommendation '1.1.3', '2.1.1' + Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1. + Output: + Status : Fail + ELevel : E3 + ProfileLevel: L1 + Connection : Microsoft Graph + Rec : 1.1.1 + Result : False + Details : Non-compliant accounts: + Username | Roles | HybridStatus | Missing Licence + user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM + user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2 + FailureReason: Non-Compliant Accounts: 2 .EXAMPLE - PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" - PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation - Captures the audit results into a variable and exports them to a CSV file. - Output: - CISAuditResult[] - auditResults.csv + PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" + PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation + + Captures the audit results into a variable and exports them to a CSV file. + Output: + CISAuditResult[] + auditResults.csv .EXAMPLE - PS> Invoke-M365SecurityAudit -WhatIf - Displays what would happen if the cmdlet is run without actually performing the audit. - Output: - What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsoft 365 environment". + PS> Invoke-M365SecurityAudit -WhatIf + + Displays what would happen if the cmdlet is run without actually performing the audit. + Output: + What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsoft 365 environment". .INPUTS None. You cannot pipe objects to Invoke-M365SecurityAudit. .OUTPUTS From 968e5898600bff83dbf3f69edbe5f285cbd6b2f9 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:40:27 -0500 Subject: [PATCH 38/42] add: finally block to always disconnect --- source/Public/Invoke-M365SecurityAudit.ps1 | 80 +++++++++++----------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index ad778ea..f03adc2 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -248,55 +248,54 @@ function Invoke-M365SecurityAudit { $currentTestIndex = 0 # Establishing connections if required - try { - $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections - if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { + $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections + if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { + try { Write-Information "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -InformationAction Continue Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections -SkipConfirmation:$DoNotConfirmConnections + + Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue + # 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 + if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { + $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $M365DomainForPWPolicyTest + # Add the result to the collection + [void]$allAuditResults.Add($auditResult) + } + } } - } - catch { - Write-Host "Execution aborted: $_" -ForegroundColor Red - break - } - - - - Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue - # 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 { + Write-Error "An error occurred: $_" } - 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 - if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { - $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $M365DomainForPWPolicyTest - # Add the result to the collection - [void]$allAuditResults.Add($auditResult) + finally { + if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Disconnect")) { + # Clean up sessions + Disconnect-M365Suite -RequiredConnections $requiredConnections + } } } } End { - if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Disconnect")) { - # Clean up sessions - Disconnect-M365Suite -RequiredConnections $requiredConnections - } if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", "Measure")) { # Call the private function to calculate and display results Measure-AuditResult -AllAuditResults $allAuditResults -FailedTests $script:FailedTests @@ -316,3 +315,4 @@ function Invoke-M365SecurityAudit { } } + From 8b91a8c06ea61ecde54608099934479499837410 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:41:47 -0500 Subject: [PATCH 39/42] add: finally block to always disconnect --- source/Public/Invoke-M365SecurityAudit.ps1 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index f03adc2..46908bc 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -107,19 +107,19 @@ Output: What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsoft 365 environment". .INPUTS - None. You cannot pipe objects to Invoke-M365SecurityAudit. + None. You cannot pipe objects to Invoke-M365SecurityAudit. .OUTPUTS - CISAuditResult[] - The cmdlet returns an array of CISAuditResult objects representing the results of the security audit. + CISAuditResult[] + The cmdlet returns an array of CISAuditResult objects representing the results of the security audit. .NOTES - - This module is based on CIS benchmarks. - - Governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. - - Commercial use is not permitted. This module cannot be sold or used for commercial purposes. - - Modifications and sharing are allowed under the same license. - - For full license details, visit: https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en - - Register for CIS Benchmarks at: https://www.cisecurity.org/cis-benchmarks + - This module is based on CIS benchmarks. + - Governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + - Commercial use is not permitted. This module cannot be sold or used for commercial purposes. + - Modifications and sharing are allowed under the same license. + - For full license details, visit: https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en + - Register for CIS Benchmarks at: https://www.cisecurity.org/cis-benchmarks .LINK - https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit + https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit #> function Invoke-M365SecurityAudit { [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')] From 5c852679d97b39208ca80b605e898e4598076dfd Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 19:06:59 -0500 Subject: [PATCH 40/42] add: finally block to always disconnect --- source/Public/Invoke-M365SecurityAudit.ps1 | 83 ++++++++++++---------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index 46908bc..49fd8bc 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -248,53 +248,61 @@ function Invoke-M365SecurityAudit { $currentTestIndex = 0 # Establishing connections if required - $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections - if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { - try { + try { + $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections + if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { Write-Information "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -InformationAction Continue Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections -SkipConfirmation:$DoNotConfirmConnections + } + } + catch { + Write-Host "Connection execution aborted: $_" -ForegroundColor Red + break + } - Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue - # 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 = $_ }) - } + + try { + Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue + # 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 } - - $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 - if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { - $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $M365DomainForPWPolicyTest - # Add the result to the collection - [void]$allAuditResults.Add($auditResult) - } + Catch { + # Log the error and add the test to the failed tests collection + Write-Verbose "Failed to load test function $($_.Name): $_" -Verbose + $script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) } } - catch { - Write-Error "An error occurred: $_" - } - finally { - if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Disconnect")) { - # Clean up sessions - Disconnect-M365Suite -RequiredConnections $requiredConnections + + $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 + if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) { + $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $M365DomainForPWPolicyTest + # Add the result to the collection + [void]$allAuditResults.Add($auditResult) } } } + catch { + # Log the error and add the test to the failed tests collection + Write-Verbose "Invoke-M365SecurityAudit: Failed to load test function $($_.Name): $_" -Verbose + $script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) + } + finally { + if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Disconnect")) { + # Clean up sessions + Disconnect-M365Suite -RequiredConnections $requiredConnections + } + } } - End { if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", "Measure")) { # Call the private function to calculate and display results @@ -316,3 +324,4 @@ function Invoke-M365SecurityAudit { } + From e0436686b863cc0e6f8446aabdfe879d98cbefff Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 19:31:09 -0500 Subject: [PATCH 41/42] docs: formatting --- source/Private/Assert-ModuleAvailability.ps1 | 12 ++++++------ source/Private/Measure-AuditResult.ps1 | 2 +- source/Public/Invoke-M365SecurityAudit.ps1 | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/source/Private/Assert-ModuleAvailability.ps1 b/source/Private/Assert-ModuleAvailability.ps1 index ea4be62..a0f2f33 100644 --- a/source/Private/Assert-ModuleAvailability.ps1 +++ b/source/Private/Assert-ModuleAvailability.ps1 @@ -10,25 +10,25 @@ function Assert-ModuleAvailability { $module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion } if ($null -eq $module) { - Write-Information "Installing $ModuleName module..." -InformationAction Continue + Write-Host "Installing $ModuleName module..." -ForegroundColor Yellow Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null } elseif ($module.Version -lt [version]$RequiredVersion) { - Write-Information "Updating $ModuleName module to required version..." -InformationAction Continue + Write-Host "Updating $ModuleName module to required version..." -ForegroundColor Yellow Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null } else { - Write-Information "$ModuleName module is already at required version or newer." -InformationAction Continue + Write-Host "$ModuleName module is already at required version or newer." -ForegroundColor Gray } if ($SubModules.Count -gt 0) { foreach ($subModule in $SubModules) { - Write-Information "Importing submodule $ModuleName.$subModule..." -InformationAction Continue + Write-Host "Importing submodule $ModuleName.$subModule..." -ForegroundColor DarkGray Import-Module -Name "$ModuleName.$subModule" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null } } else { - Write-Information "Importing module $ModuleName..." -InformationAction Continue - Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null + Write-Host "Importing module $ModuleName..." -ForegroundColor DarkGray + Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null } } catch { diff --git a/source/Private/Measure-AuditResult.ps1 b/source/Private/Measure-AuditResult.ps1 index 041fa25..46c787a 100644 --- a/source/Private/Measure-AuditResult.ps1 +++ b/source/Private/Measure-AuditResult.ps1 @@ -19,7 +19,7 @@ function Measure-AuditResult { # 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%." + Write-Host "Your passing percentage is $passPercentage%." -ForegroundColor Magenta # Display details of failed tests if ($FailedTests.Count -gt 0) { diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index 49fd8bc..6d63b5b 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -197,6 +197,7 @@ function Invoke-M365SecurityAudit { # Check and install required modules if necessary if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Check for required modules: $requiredModulesFormatted", "Check")) { + Write-Host "Checking for and installing required modules..." -ForegroundColor DarkMagenta foreach ($module in $requiredModules) { Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModules $module.SubModules } @@ -251,7 +252,7 @@ function Invoke-M365SecurityAudit { try { $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { - Write-Information "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -InformationAction Continue + Write-Host "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -ForegroundColor DarkMagenta Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections -SkipConfirmation:$DoNotConfirmConnections } } @@ -262,7 +263,7 @@ function Invoke-M365SecurityAudit { try { - Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue + Write-Host "A total of $($totalTests) tests were selected to run..." -ForegroundColor DarkMagenta # Import the test functions $testFiles | ForEach-Object { $currentTestIndex++ From e6cdae32a1a4b6f2ef22da4fd165db33d2b05203 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sun, 23 Jun 2024 19:34:53 -0500 Subject: [PATCH 42/42] docs: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 890df99..dc91822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on and uses the types of changes according to [Keep a Change - Fixed 6.1.1 test definition to include the correct connection. - Removed banner and warning from EXO and AzureAD connection step. - Fixed missing CommentBlock for `Remove-RowsWithEmptyCSVStatus` function. +- Fixed formatting and color for various Write-Host messages. ### Added