From 8ce1af7745e130867a84687ac351a5ce65fd1e42 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:31:26 -0500 Subject: [PATCH 01/24] fix: paramters for export table --- source/Public/Export-M365SecurityAuditTable.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Public/Export-M365SecurityAuditTable.ps1 b/source/Public/Export-M365SecurityAuditTable.ps1 index bb3d3b3..29bd75c 100644 --- a/source/Public/Export-M365SecurityAuditTable.ps1 +++ b/source/Public/Export-M365SecurityAuditTable.ps1 @@ -69,8 +69,8 @@ function Export-M365SecurityAuditTable { [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromCsv")] [string]$ExportPath, - [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] - [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")] + [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromAuditResults")] + [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromCsv")] [switch]$ExportOriginalTests, [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] From c652b3c88674f058a4f86a3818450759a40d8ea8 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:32:11 -0500 Subject: [PATCH 02/24] fix: 1.2.2 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 6 +++++- source/tests/Test-BlockSharedMailboxSignIn.ps1 | 7 +++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 9f95f1e..59bae99 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -52,7 +52,11 @@ function Get-CISExoOutput { # Test-BlockSharedMailboxSignIn.ps1 $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox # [object[]] - return $MBX + # Example output: + # 123e4567-e89b-12d3-a456-426614174000 + # 987e6543-21ba-12d3-a456-426614174000 + # abcddcba-98fe-76dc-a456-426614174000 + return $MBX.ExternalDirectoryObjectId } '1.3.3' { # Test-ExternalSharingCalendars.ps1 diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index 974e8f4..8d30fd1 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -30,8 +30,7 @@ function Test-BlockSharedMailboxSignIn { process { try { # Step: Retrieve shared mailbox details - $MBX = Get-CISExoOutput -Rec $recnum - $objectids = $MBX.ExternalDirectoryObjectId + $objectids = Get-CISExoOutput -Rec $recnum $users = Get-CISAadOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) $sharedMailboxDetails = $users | Where-Object {$_.objectid -in $objectids} @@ -42,7 +41,7 @@ function Test-BlockSharedMailboxSignIn { # Step: Determine failure reasons based on enabled mailboxes (Condition A & B: Fail) $failureReasons = if (-not $allBlocked) { - "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" + "Some mailboxes have sign-in enabled (AccountEnabled:True):`n$($enabledMailboxes -join ', ')" } else { "N/A" @@ -53,7 +52,7 @@ function Test-BlockSharedMailboxSignIn { "All shared mailboxes have sign-in blocked." } else { - "Enabled Mailboxes: $($enabledMailboxes -join ', ')" + "AccountEnabled set to True Mailboxes: $($enabledMailboxes -join ', ')" } # Step: Create and populate the CISAuditResult object From b0486c3f23fa2d0cffbec769faa34c043d595384 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:33:27 -0500 Subject: [PATCH 03/24] fix: 1.3.3 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 27 ++++++++++++ .../tests/Test-ExternalSharingCalendars.ps1 | 43 ++++++++++++++++--- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 59bae99..ff07d3d 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -65,6 +65,33 @@ function Get-CISExoOutput { # [psobject[]] return $sharingPolicies } + '1.3.3b' { + $mailboxes = Get-Mailbox -ResultSize Unlimited + $results = foreach ($mailbox in $mailboxes) { + # Get the name of the default calendar folder (depends on the mailbox's language) + $calendarFolder = [string](Get-ExoMailboxFolderStatistics $mailbox.PrimarySmtpAddress -FolderScope Calendar | Where-Object {$_.FolderType -eq 'Calendar'}).Name + Write-Verbose "Calendar folder for $($mailbox.PrimarySmtpAddress): $calendarFolder" + # Get users calendar folder settings for their default Calendar folder + # calendar has the format identity:\ + $calendar = Get-MailboxCalendarFolder -Identity "$($mailbox.PrimarySmtpAddress):\$calendarFolder" + #Write-Host "Calendar object for $($mailbox.PrimarySmtpAddress): $calendar" + Write-Verbose "Calendar publishing enabled: $($calendar.PublishEnabled)" + # Check if calendar publishing is enabled and create a custom object + if ($calendar.PublishEnabled) { + [PSCustomObject]@{ + PrimarySmtpAddress = $mailbox.PrimarySmtpAddress + CalendarFolder = $calendarFolder + PublishEnabled = $calendar.PublishEnabled + PublishedCalendarUrl = $calendar.PublishedCalendarUrl + } + } + } + $calendarDetails = @() + foreach ($calendar in $results) { + $calendarDetails += "Calendar: $($calendar.PrimarySmtpAddress); URL: $($calendar.PublishedCalendarUrl)" + } + return $calendarDetails + } '1.3.6' { # Test-CustomerLockbox.ps1 # Step: Retrieve the organization configuration (Condition C: Pass/Fail) diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index ba0d8a3..05f0aae 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -12,7 +12,7 @@ function Test-ExternalSharingCalendars { # Initialization code, if needed $recnum = "1.3.3" - + Write-Verbose "Running Test-ExternalSharingCalendars Rec#: $recnum" # Conditions for 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated) # # Validate test for a pass: @@ -31,8 +31,16 @@ function Test-ExternalSharingCalendars { process { try { # Step: Retrieve sharing policies related to calendar sharing + # $sharingPolicies Mock Object + <# + $mockPolicy = [PSCustomObject]@{ + Name = "Default Sharing Policy" + Domains = @("Anonymous:CalendarSharingFreeBusySimple") + Enabled = $true + Default = $true + } + #> $sharingPolicies = Get-CISExoOutput -Rec $recnum - # Step (Condition A & B: Pass/Fail): Check if calendar sharing is disabled in all applicable policies $isExternalSharingDisabled = $true $sharingPolicyDetails = @() @@ -42,21 +50,42 @@ function Test-ExternalSharingCalendars { $sharingPolicyDetails += "$($policy.Name): Enabled" } } - - # Step: Prepare failure reasons and details based on compliance (Condition A & B: Fail) + # Retrieve calendars with publishing enabled (from 1.3.3b) + # $calendarDetails Mock Object + <# + $mailboxDetails = @( + [PSCustomObject]@{ + Calendar = "user1@example.com" + URL = "https://example.com/calendar/user1" + }, + [PSCustomObject]@{ + Calendar = "user2@example.com" + URL = "https://example.com/calendar/user2" + }, + [PSCustomObject]@{ + Calendar = "user3@example.com" + URL = "https://example.com/calendar/user3" + } + ) + #> + $calendarDetails = Get-CISExoOutput -Rec "$("$recnum" + "b")" + # Build the failure reason string $failureReasons = if (-not $isExternalSharingDisabled) { - "Calendar sharing with external users is enabled in one or more policies." + $baseMessage = "Calendar sharing with external users is enabled in one or more policies." + if ($calendarDetails.Count -gt 0) { + $baseMessage += "`nPrior to remediating, check the following mailboxes that have calendar publishing enabled: `n$($calendarDetails -join '`n')" + } + $baseMessage } else { "N/A" } - # Step: Prepare details for the audit result (Condition A & B: Pass/Fail) $details = if ($isExternalSharingDisabled) { "Calendar sharing with external users is disabled." } else { - "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" + "Enabled Sharing Policies:`n$($sharingPolicyDetails -join ', ')" } # Step: Create and populate the CISAuditResult object From 5d0839430ad4e68883385372e1be3ec86d5af2cb Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:14:28 -0500 Subject: [PATCH 04/24] fix: 1.2.2 simplified output and added object comment --- .../tests/Test-BlockSharedMailboxSignIn.ps1 | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index 8d30fd1..2e6166e 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -11,7 +11,7 @@ function Test-BlockSharedMailboxSignIn { # Initialization code, if needed $recnum = "1.2.2" - + Write-Verbose "Running Test-BlockSharedMailboxSignIn for $recnum..." # Conditions for 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked # # Validate test for a pass: @@ -30,7 +30,36 @@ function Test-BlockSharedMailboxSignIn { process { try { # Step: Retrieve shared mailbox details + # $objectids Mock Object + <# + $objectids = @( + "123e4567-e89b-12d3-a456-426614174000", + "987e6543-21ba-12d3-a456-426614174000", + "abcddcba-98fe-76dc-a456-426614174000" + ) + #> $objectids = Get-CISExoOutput -Rec $recnum + # Step: Retrieve user details from Azure AD + # $users Mock Object + <# + $accountDetails = @( + [PSCustomObject]@{ + ObjectId = "123e4567-e89b-12d3-a456-426614174000" + DisplayName = "SMBuser1" + AccountEnabled = $true + }, + [PSCustomObject]@{ + ObjectId = "987e6543-21ba-12d3-a456-426614174000" + DisplayName = "SMBuser2" + AccountEnabled = $true + }, + [PSCustomObject]@{ + ObjectId = "abcddcba-98fe-76dc-a456-426614174000" + DisplayName = "SMBuser3" + AccountEnabled = $true + } + ) + #> $users = Get-CISAadOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) $sharedMailboxDetails = $users | Where-Object {$_.objectid -in $objectids} From 0daa4c7f6b45bd0467d586228dea1737845ee5b8 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:14:44 -0500 Subject: [PATCH 05/24] fix: 1.3.3 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 66 ++++++++++++++++--- .../tests/Test-ExternalSharingCalendars.ps1 | 16 ++--- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index ff07d3d..b636804 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -52,24 +52,72 @@ function Get-CISExoOutput { # Test-BlockSharedMailboxSignIn.ps1 $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox # [object[]] - # Example output: - # 123e4567-e89b-12d3-a456-426614174000 - # 987e6543-21ba-12d3-a456-426614174000 - # abcddcba-98fe-76dc-a456-426614174000 + # $MBX example output: + <# + $MBX = @( + [PSCustomObject]@{ + UserPrincipalName = "SMBuser1@domain.com" + ExternalDirectoryObjectId = "123e4567-e89b-12d3-a456-426614174000" + }, + [PSCustomObject]@{ + UserPrincipalName = "SMBuser2@domain.com" + ExternalDirectoryObjectId = "987e6543-21ba-12d3-a456-426614174000" + }, + [PSCustomObject]@{ + UserPrincipalName = "SMBuser3@domain.com" + ExternalDirectoryObjectId = "abcddcba-98fe-76dc-a456-426614174000" + } + ) + #> return $MBX.ExternalDirectoryObjectId } '1.3.3' { # Test-ExternalSharingCalendars.ps1 # Step: Retrieve sharing policies related to calendar sharing + # $sharingPolicies Mock Object + <# + $sharingPolicies = [PSCustomObject]@{ + Name = "Default Sharing Policy" + Domains = @("Anonymous:CalendarSharingFreeBusySimple") + Enabled = $true + Default = $true + } + #> $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } # [psobject[]] return $sharingPolicies } '1.3.3b' { $mailboxes = Get-Mailbox -ResultSize Unlimited + <# + $mailboxes = @( + [PSCustomObject]@{ + UserPrincipalName = "SMBuser1@domain.com" + ExternalDirectoryObjectId = "123e4567-e89b-12d3-a456-426614174000" + PrimarySmtpAddress = "SMBuser1@domain.com" + PublishEnabled = $False + PublishedCalendarUrl = "https://example.com/calendar/smbuser1" + }, + [PSCustomObject]@{ + UserPrincipalName = "SMBuser2@domain.com" + ExternalDirectoryObjectId = "987e6543-21ba-12d3-a456-426614174000" + PrimarySmtpAddress = "SMBuser2@domain.com" + PublishEnabled = $False + PublishedCalendarUrl = "https://example.com/calendar/smbuser2" + }, + [PSCustomObject]@{ + UserPrincipalName = "SMBuser3@domain.com" + ExternalDirectoryObjectId = "abcddcba-98fe-76dc-a456-426614174000" + PrimarySmtpAddress = "SMBuser3@domain.com" + PublishEnabled = $False + PublishedCalendarUrl = "https://example.com/calendar/smbuser3" + } + ) + #> $results = foreach ($mailbox in $mailboxes) { # Get the name of the default calendar folder (depends on the mailbox's language) - $calendarFolder = [string](Get-ExoMailboxFolderStatistics $mailbox.PrimarySmtpAddress -FolderScope Calendar | Where-Object {$_.FolderType -eq 'Calendar'}).Name + # Return single string Ex: return "Calendar" x 3 in array + $calendarFolder = [string](Get-EXOMailboxFolderStatistics $mailbox.PrimarySmtpAddress -Folderscope Calendar | Where-Object { $_.FolderType -eq 'Calendar' }).Name Write-Verbose "Calendar folder for $($mailbox.PrimarySmtpAddress): $calendarFolder" # Get users calendar folder settings for their default Calendar folder # calendar has the format identity:\ @@ -79,9 +127,9 @@ function Get-CISExoOutput { # Check if calendar publishing is enabled and create a custom object if ($calendar.PublishEnabled) { [PSCustomObject]@{ - PrimarySmtpAddress = $mailbox.PrimarySmtpAddress - CalendarFolder = $calendarFolder - PublishEnabled = $calendar.PublishEnabled + PrimarySmtpAddress = $mailbox.PrimarySmtpAddress + CalendarFolder = $calendarFolder + PublishEnabled = $calendar.PublishEnabled PublishedCalendarUrl = $calendar.PublishedCalendarUrl } } @@ -315,7 +363,7 @@ function Get-CISExoOutput { # 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,ReportJunkAddresses,ReportNotJunkAddresses,ReportPhishAddresses,ReportChatMessageEnabled,ReportChatMessageToCustomizedAddressEnabled + $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportJunkAddresses, ReportNotJunkAddresses, ReportPhishAddresses, ReportChatMessageEnabled, ReportChatMessageToCustomizedAddressEnabled return $ReportSubmissionPolicy } default { throw "No match found for test: $Rec" } diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index 05f0aae..3ac3ecf 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -33,7 +33,7 @@ function Test-ExternalSharingCalendars { # Step: Retrieve sharing policies related to calendar sharing # $sharingPolicies Mock Object <# - $mockPolicy = [PSCustomObject]@{ + $sharingPolicies = [PSCustomObject]@{ Name = "Default Sharing Policy" Domains = @("Anonymous:CalendarSharingFreeBusySimple") Enabled = $true @@ -53,18 +53,18 @@ function Test-ExternalSharingCalendars { # Retrieve calendars with publishing enabled (from 1.3.3b) # $calendarDetails Mock Object <# - $mailboxDetails = @( + $calendarDetails = @( [PSCustomObject]@{ - Calendar = "user1@example.com" - URL = "https://example.com/calendar/user1" + Calendar = "SMBuser1@domain.com" + URL = "https://example.com/calendar/smbuser1" }, [PSCustomObject]@{ - Calendar = "user2@example.com" - URL = "https://example.com/calendar/user2" + Calendar = "SMBuser2@domain.com" + URL = "https://example.com/calendar/smbuser2" }, [PSCustomObject]@{ - Calendar = "user3@example.com" - URL = "https://example.com/calendar/user3" + Calendar = "SMBuser4@domain.com" + URL = "https://example.com/calendar/smbuser3" } ) #> From 033c4c524764724a3ba4542210c3467339b266ea Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:22:09 -0500 Subject: [PATCH 06/24] fix: 1.3.6 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 12 ++++++++++-- source/tests/Test-CustomerLockbox.ps1 | 5 +++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index b636804..b734fab 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -52,7 +52,7 @@ function Get-CISExoOutput { # Test-BlockSharedMailboxSignIn.ps1 $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox # [object[]] - # $MBX example output: + # $MBX mock object: <# $MBX = @( [PSCustomObject]@{ @@ -88,7 +88,7 @@ function Get-CISExoOutput { return $sharingPolicies } '1.3.3b' { - $mailboxes = Get-Mailbox -ResultSize Unlimited + # $mailboxes Mock Object <# $mailboxes = @( [PSCustomObject]@{ @@ -114,6 +114,7 @@ function Get-CISExoOutput { } ) #> + $mailboxes = Get-Mailbox -ResultSize Unlimited $results = foreach ($mailbox in $mailboxes) { # Get the name of the default calendar folder (depends on the mailbox's language) # Return single string Ex: return "Calendar" x 3 in array @@ -143,6 +144,13 @@ function Get-CISExoOutput { '1.3.6' { # Test-CustomerLockbox.ps1 # Step: Retrieve the organization configuration (Condition C: Pass/Fail) + # $orgConfig Mock Object: + <# + # return $orgConfig + $orgConfig = [PSCustomObject]@{ + CustomerLockBoxEnabled = $true + } + #> $orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled $customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled # [bool] diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index 9a003d9..d71cf5e 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -12,7 +12,7 @@ function Test-CustomerLockbox { # Initialization code, if needed $recnum = "1.3.6" - + Write-Verbose "Running Test-CustomerLockbox for $recnum..." # Conditions for 1.3.6 (L2) Ensure the customer lockbox feature is enabled (Automated) # # Validate test for a pass: @@ -33,8 +33,9 @@ function Test-CustomerLockbox { process { try { # Step: Retrieve the organization configuration (Condition C: Pass/Fail) + # $customerLockboxEnabled Mock Object + # $customerLockboxEnabled = $true $customerLockboxEnabled = Get-CISExoOutput -Rec $recnum - # Step: Prepare failure reasons and details based on compliance (Condition A, B, & C: Fail) $failureReasons = if (-not $customerLockboxEnabled) { "Customer lockbox feature is not enabled." From 5ebb2a6e7d554f28c11805907ef4efa0901cc308 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:38:26 -0500 Subject: [PATCH 07/24] fix: 1.3.3 simplified output and added object comment --- source/tests/Test-ExternalSharingCalendars.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index 3ac3ecf..9cf01d9 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -12,7 +12,7 @@ function Test-ExternalSharingCalendars { # Initialization code, if needed $recnum = "1.3.3" - Write-Verbose "Running Test-ExternalSharingCalendars Rec#: $recnum" + Write-Verbose "Running Test-ExternalSharingCalendars for $recnum..." # Conditions for 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated) # # Validate test for a pass: From 712077b8f11c815c97558d56a584a4cbf0dab7db Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:38:50 -0500 Subject: [PATCH 08/24] fix: 2.1.1 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 31 ++++++++++++++++++++++- source/tests/Test-SafeLinksOfficeApps.ps1 | 4 +-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index b734fab..d3d34cc 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -161,12 +161,41 @@ function Get-CISExoOutput { 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 Mock Object: + <# + $policies = @( + [PSCustomObject]@{ + Name = "PolicyOne" + EnableSafeLinksForEmail = $true + EnableSafeLinksForTeams = $true + EnableSafeLinksForOffice = $true + TrackClicks = $true + AllowClickThrough = $false + }, + [PSCustomObject]@{ + Name = "PolicyTwo" + EnableSafeLinksForEmail = $true + EnableSafeLinksForTeams = $true + EnableSafeLinksForOffice = $true + TrackClicks = $true + AllowClickThrough = $true + }, + [PSCustomObject]@{ + Name = "PolicyThree" + EnableSafeLinksForEmail = $true + EnableSafeLinksForTeams = $true + EnableSafeLinksForOffice = $true + TrackClicks = $true + AllowClickThrough = $false + } + ) + #> $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 + $policyDetails = $policy #Get-SafeLinksPolicy -Identity $policy.Name # Check each required property and record failures # Condition A: Checking policy settings $failures = @() diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index da66d07..154a28d 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -11,7 +11,7 @@ function Test-SafeLinksOfficeApps { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "2.1.1" - + Write-Verbose "Running Test-SafeLinksOfficeApps for $recnum..." <# Conditions for 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled @@ -49,7 +49,7 @@ function Test-SafeLinksOfficeApps { # Prepare the final result # Condition B: Ensuring no misconfigurations $result = $misconfiguredDetails.Count -eq 0 - $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' } + $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join '`n' } $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } # Create and populate the CISAuditResult object From 73c1ecf30e0476fd51d46fa94a67909bf236420d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:43:11 -0500 Subject: [PATCH 09/24] fix: 2.1.2 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 6 ++++ source/tests/Test-CommonAttachmentFilter.ps1 | 38 +++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index d3d34cc..08cc4f4 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -222,6 +222,12 @@ function Get-CISExoOutput { # 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 Mock Object + <# + $attachmentFilter = [PSCustomObject]@{ + EnableFileFilter = $true + } + #> $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter $result = $attachmentFilter.EnableFileFilter # [bool] diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 7c29bcd..7a77945 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -8,38 +8,35 @@ function Test-CommonAttachmentFilter { begin { <# - Conditions for 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled - - 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 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`. - - Condition C: Ensure that the setting is enabled in the highest priority policy listed if custom policies exist. - - 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 Common Attachment Types Filter is not 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 `False`. - - Condition C: Ensure that the setting is not enabled in the highest priority policy listed if custom policies exist. + Conditions for 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled + 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 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`. + - Condition C: Ensure that the setting is enabled in the highest priority policy listed if custom policies exist. + 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 Common Attachment Types Filter is not 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 `False`. + - Condition C: Ensure that the setting is not enabled in the highest priority policy listed if custom policies exist. #> - # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "2.1.2" + Write-Verbose "Running Test-CommonAttachmentFilter for $recnum..." } - process { try { # 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 + # $result Mock Object + # $result = $true $result = Get-CISExoOutput -Rec $recnum - # Prepare failure reasons and details based on compliance $failureReasons = if (-not $result) { # Condition A: The Common Attachment Types Filter is not enabled in the Microsoft 365 Security & Compliance Center. @@ -49,14 +46,12 @@ function Test-CommonAttachmentFilter { else { "N/A" } - $details = if ($result) { "File Filter Enabled: True" } else { "File Filter Enabled: False" } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -72,7 +67,6 @@ function Test-CommonAttachmentFilter { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult From c05ba5aebdc0b9ca6b230419859e9157e869051a Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:51:01 -0500 Subject: [PATCH 10/24] fix: 2.1.3 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 15 +++++++++++++++ source/tests/Test-NotifyMalwareInternal.ps1 | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 08cc4f4..0f981db 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -237,6 +237,21 @@ function Get-CISExoOutput { # 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 Mock Object + <# + $malwareNotifications = @( + [PSCustomObject]@{ + Identity = "Default" + EnableInternalSenderAdminNotifications = $true + RecommendedPolicyType = "Custom" + }, + [PSCustomObject]@{ + Identity = "Anti-malware-Policy" + EnableInternalSenderAdminNotifications = $true + RecommendedPolicyType = "Custom" + } + ) + #> $malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' } # [object[]] return $malwareNotifications diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index b87c381..380bcf5 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -27,6 +27,7 @@ function Test-NotifyMalwareInternal { #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "2.1.3" + Write-Verbose "Running Test-NotifyMalwareInternal for $recnum..." } process { @@ -34,6 +35,21 @@ 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 Mock Object + <# + $malwareNotifications = @( + [PSCustomObject]@{ + Identity = "Default" + EnableInternalSenderAdminNotifications = $true + RecommendedPolicyType = "Custom" + }, + [PSCustomObject]@{ + Identity = "Anti-malware-Policy" + EnableInternalSenderAdminNotifications = $true + RecommendedPolicyType = "Custom" + } + ) + #> $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. @@ -60,7 +76,7 @@ function Test-NotifyMalwareInternal { "All custom malware policies have notifications enabled." } else { - "Misconfigured Policies: $($policiesToReport -join ', ')" + "Misconfigured Policies: $($policiesToReport -join '`n')" } # Create and populate the CISAuditResult object From 37b6557221c8b1c98453f0b0480ffae5448750bc Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:53:50 -0500 Subject: [PATCH 11/24] fix: 2.1.4 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 12 +++ source/tests/Test-SafeAttachmentsPolicy.ps1 | 100 +++++++++----------- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 0f981db..bc0de27 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -261,6 +261,18 @@ function Get-CISExoOutput { if (Get-Command Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue) { # Retrieve all Safe Attachment policies where Enable is set to True # Check if ErrorAction needed below + # $safeAttachmentPolicies Mock Object: + <# + $safeAttachmentPolicies = @( + [PSCustomObject]@{ + Policy = "Strict Preset Security Policy" + Action = "Block" + QuarantineTag = "AdminOnlyAccessPolicy" + Redirect = $false + Enabled = $true + } + ) + #> $safeAttachmentPolicies = Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue | Where-Object { $_.Enable -eq $true } # [object[]] return $safeAttachmentPolicies diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index 6011aa8..2065728 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -5,73 +5,67 @@ function Test-SafeAttachmentsPolicy { begin { $recnum = "2.1.4" - + Write-Verbose "Running Test-SafeAttachmentsPolicy for $recnum..." <# - Conditions for 2.1.4 (L2) Ensure Safe Attachments policy is enabled + Conditions for 2.1.4 (L2) Ensure Safe Attachments policy is enabled: + Validate test for a pass: + - Ensure the highest priority Safe Attachments policy is enabled. + - Check if the policy's action is set to 'Block'. + - Confirm the QuarantineTag is set to 'AdminOnlyAccessPolicy'. + - Verify that the Redirect setting is disabled. - 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 Safe Attachments policy is enabled in the Microsoft 365 Defender portal. - - Condition B: The policy covers all recipients within the organization. - - Condition C: The policy action is set to "Dynamic Delivery" or "Quarantine". - - Condition D: The policy is not 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 Safe Attachments policy is not enabled in the Microsoft 365 Defender portal. - - Condition B: The policy does not cover all recipients within the organization. - - Condition C: The policy action is not set to "Dynamic Delivery" or "Quarantine". - - Condition D: The policy is disabled. + Validate test for a fail: + - If the highest priority Safe Attachments policy's action is not set to 'Block'. + - If the QuarantineTag is not set to 'AdminOnlyAccessPolicy'. + - If the Redirect setting is enabled. + - If no enabled Safe Attachments policies are found. #> } - process { + # 2.1.4 (L2) Ensure Safe Attachments policy is enabled + # $safeAttachmentPolicies Mock Object + <# + $safeAttachmentPolicies = @( + [PSCustomObject]@{ + Policy = "Strict Preset Security Policy" + Action = "Block" + QuarantineTag = "AdminOnlyAccessPolicy" + Redirect = $false + Enabled = $true + } + ) + #> $safeAttachmentPolicies = Get-CISExoOutput -Rec $recnum if ($safeAttachmentPolicies -ne 1) { try { - # Check if any Safe Attachments policy is enabled (Condition A) - $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 - + $highestPriorityPolicy = $safeAttachmentPolicies | Select-Object -First 1 # Initialize details and failure reasons $details = @() $failureReasons = @() - - foreach ($policy in $safeAttachmentPolicies) { - # Initialize policy detail and failed status - $failed = $false - - # Check if the policy action is set to "Dynamic Delivery" or "Quarantine" (Condition C) - if ($policy.Action -notin @("DynamicDelivery", "Quarantine")) { - $failureReasons += "Policy '$($policy.Name)' action is not set to 'Dynamic Delivery' or 'Quarantine'." - $failed = $true - } - - # Check if the policy is not disabled (Condition D) - if (-not $policy.Enable) { - $failureReasons += "Policy '$($policy.Name)' is disabled." - $failed = $true - } - - # Add policy details to the details array - $details += [PSCustomObject]@{ - Policy = $policy.Name - Enabled = $policy.Enable - Action = $policy.Action - Failed = $failed - } + # Check policy specifics as per CIS benchmark requirements + if ($highestPriorityPolicy.Action -ne 'Block') { + $failureReasons += "Policy action is not set to 'Block'." + } + if ($highestPriorityPolicy.QuarantineTag -ne 'AdminOnlyAccessPolicy') { + $failureReasons += "Quarantine policy is not set to 'AdminOnlyAccessPolicy'." + } + if ($highestPriorityPolicy.Redirect -ne $false) { + $failureReasons += "Redirect is not disabled." } - # The result is a pass if there are no failure reasons $result = $failureReasons.Count -eq 0 - + $details = [PSCustomObject]@{ + Policy = $highestPriorityPolicy.Identity + Action = $highestPriorityPolicy.Action + QuarantineTag = $highestPriorityPolicy.QuarantineTag + Redirect = $highestPriorityPolicy.Redirect + Enabled = $highestPriorityPolicy.Enable + } # Format details for output manually - $detailsString = "Policy|Enabled|Action|Failed`n" + ($details | - ForEach-Object {"$($_.Policy)|$($_.Enabled)|$($_.Action)|$($_.Failed)`n"} + $detailsString = "Policy|Action|QuarantineTag|Redirect|Enabled`n" + ($details | + ForEach-Object { "$($_.Policy)|$($_.Action)|$($_.QuarantineTag)|$($_.Redirect)|$($_.Enabled)`n" } ) - $failureReasonsString = ($failureReasons | ForEach-Object { $_ }) -join ' ' - + $failureReasonsString = ($failureReasons -join "`n") # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -84,13 +78,10 @@ function Test-SafeAttachmentsPolicy { } 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 } @@ -106,7 +97,6 @@ function Test-SafeAttachmentsPolicy { $auditResult = Initialize-CISAuditResult @params } } - end { # Return the audit result return $auditResult From 2466692e352a430a34c90463547f7fddc544df1d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:19:03 -0500 Subject: [PATCH 12/24] fix: 2.1.5 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 11 ++++++ source/tests/Test-SafeAttachmentsTeams.ps1 | 41 ++++++++++++++++------ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index bc0de27..9d83b5c 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -288,6 +288,17 @@ function Get-CISExoOutput { # Retrieve the ATP policies for Office 365 and check Safe Attachments settings $atpPolicies = Get-AtpPolicyForO365 # Check if the required ATP policies are enabled + # $atpPolicyResult Mock Object: + <# + $atpPolicyResult = @( + [PSCustomObject]@{ + Name = "Default" + EnableATPForSPOTeamsODB = $true + EnableSafeDocs = $true + AllowSafeDocsOpen = $false + } + ) + #> $atpPolicyResult = $atpPolicies | Where-Object { $_.EnableATPForSPOTeamsODB -eq $true -and $_.EnableSafeDocs -eq $true -and diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index 5ae5f14..d274411 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -5,7 +5,6 @@ function Test-SafeAttachmentsTeams { # Aligned # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 @@ -28,32 +27,58 @@ function Test-SafeAttachmentsTeams { # Initialization code, if needed $recnum = "2.1.5" + Write-Verbose "Running Test-SafeAttachmentsTeams for $recnum..." } - process { + # $atpPolicyResult Mock Object + <# + $atpPolicyResult = @( + [PSCustomObject]@{ + Name = "Default" + EnableATPForSPOTeamsODB = $true + EnableSafeDocs = $true + AllowSafeDocsOpen = $false + } + ) + #> $atpPolicyResult = Get-CISExoOutput -Rec $recnum if ($atpPolicyResult -ne 1) { try { # Condition A: Check Safe Attachments for SharePoint # Condition B: Check Safe Attachments for OneDrive # Condition C: Check Safe Attachments for Microsoft Teams - # Determine the result based on the ATP policy settings $result = $null -ne $atpPolicyResult + $atpPolicyResult | Where-Object { $_.Identity -eq "Default" } + if ($result) { + $detailpass = [PSCustomObject]@{ + Name = $atpPolicyResult.Name + EnableATPForSPOTeamsODB = $atpPolicyResult.EnableATPForSPOTeamsODB + EnableSafeDocs = $atpPolicyResult.EnableSafeDocs + AllowSafeDocsOpen = $atpPolicyResult.AllowSafeDocsOpen + } + $detailsString = $detailpass | ForEach-Object { + @" +Name: $($_.Name) +EnableATPForSPOTeamsODB: $($_.EnableATPForSPOTeamsODB) +EnableSafeDocs: $($_.EnableSafeDocs) +AllowSafeDocsOpen: $($_.AllowSafeDocsOpen) +`n +"@ + } + } $details = if ($result) { - "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." + $detailsString } else { "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." } - $failureReasons = if ($result) { "N/A" } else { "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -66,13 +91,10 @@ function Test-SafeAttachmentsTeams { } 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 } @@ -88,7 +110,6 @@ function Test-SafeAttachmentsTeams { $auditResult = Initialize-CISAuditResult @params } } - end { # Return the audit result return $auditResult From 3ecc8f4466dfe212228fc900c9375b6d87bcfbb5 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:42:48 -0500 Subject: [PATCH 13/24] fix: 2.1.5 simplified output and added object comment --- source/tests/Test-SafeAttachmentsTeams.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index d274411..c850c7e 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -49,7 +49,7 @@ function Test-SafeAttachmentsTeams { # Condition C: Check Safe Attachments for Microsoft Teams # Determine the result based on the ATP policy settings $result = $null -ne $atpPolicyResult - $atpPolicyResult | Where-Object { $_.Identity -eq "Default" } + #$atpPolicyResult | Where-Object { $_.Identity -eq "Default" } if ($result) { $detailpass = [PSCustomObject]@{ Name = $atpPolicyResult.Name From ad6ec465c836c81ce0130d6a9dc3c4bf48bf832d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:01:40 -0500 Subject: [PATCH 14/24] fix: 2.1.6 simplified output and added object comment --- source/Private/Get-CISExoOutput.ps1 | 36 ++++- source/tests/Test-SpamPolicyAdminNotify.ps1 | 146 +++++++++++++------- 2 files changed, 127 insertions(+), 55 deletions(-) diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index 9d83b5c..da7a5e3 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -313,9 +313,39 @@ function Get-CISExoOutput { } '2.1.6' { # Test-SpamPolicyAdminNotify.ps1 - # Retrieve the default hosted outbound spam filter policy - $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } - return $hostedOutboundSpamFilterPolicy + # Retrieve the hosted outbound spam filter policies + # $spamPolicies Mock Object: + <# + # Mock data representing multiple spam filter policies + $spamPolicies = @( + [PSCustomObject]@{ + Name = "Default" + IsDefault = $true + NotifyOutboundSpam = $true + BccSuspiciousOutboundMail = $true + NotifyOutboundSpamRecipients = "admin@example.com" + BccSuspiciousOutboundAdditionalRecipients = "bccadmin@example.com" + }, + [PSCustomObject]@{ + Name = "Custom Policy 1" + IsDefault = $false + NotifyOutboundSpam = $false + BccSuspiciousOutboundMail = $true + NotifyOutboundSpamRecipients = "" + BccSuspiciousOutboundAdditionalRecipients = "" + }, + [PSCustomObject]@{ + Name = "Custom Policy 2" + IsDefault = $false + NotifyOutboundSpam = $true + BccSuspiciousOutboundMail = $false + NotifyOutboundSpamRecipients = "notify@example.com" + BccSuspiciousOutboundAdditionalRecipients = "bccnotify@example.com" + } + ) + #> + $spamPolicies = Get-HostedOutboundSpamFilterPolicy + return $spamPolicies } '2.1.7' { # Test-AntiPhishingPolicy.ps1 diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index a574612..5f39b31 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -1,78 +1,120 @@ function Test-SpamPolicyAdminNotify { [CmdletBinding()] [OutputType([CISAuditResult])] - param ( - # Aligned - # Parameters can be added if needed - ) + param () begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed - - $recnum = "2.1.6" - <# - Conditions for 2.1.6 (L1) Ensure Exchange Online Spam Policies are set to notify administrators - - Validate recommendation details: - - Confirm that the recommendation details are accurate and complete as per the CIS benchmark. - - 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: In the Microsoft 365 Security & Compliance Center, the Exchange Online Spam Policies are set to notify administrators when a sender in the organization has been blocked for sending spam emails. - - Condition B: Using PowerShell, the `NotifyOutboundSpam` and `NotifyOutboundSpamContact` properties are correctly set in all relevant spam filter policies. - - 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: In the Microsoft 365 Security & Compliance Center, the Exchange Online Spam Policies are not set to notify administrators when a sender in the organization has been blocked for sending spam emails. - - Condition B: Using PowerShell, the `NotifyOutboundSpam` and `NotifyOutboundSpamContact` properties are not correctly set in all relevant spam filter policies. + Conditions for 2.1.6 (L1) Ensure Exchange Online Spam Policies are set to notify administrators: + Description: + - This test ensures that Exchange Online Spam Policies are configured to notify administrators when a sender in the organization has been blocked for sending spam. It checks for the proper setup of both Bcc and Notify properties and the inclusion of valid email addresses. + Validate test for a pass: + - Checks that the 'BccSuspiciousOutboundMail' and 'NotifyOutboundSpam' properties are enabled for the default policy. + - Ensures that valid email addresses are provided for 'NotifyOutboundSpamRecipients' and 'BccSuspiciousOutboundAdditionalRecipients'. + Validate test for a fail: + - If the default policy is not found, this is flagged as a critical compliance issue. + - The test fails if any of the following settings are incorrect: + - 'BccSuspiciousOutboundMail' is not enabled. + - 'NotifyOutboundSpam' is not enabled. + - 'NotifyOutboundSpamRecipients' does not contain at least one valid email address. + - 'BccSuspiciousOutboundAdditionalRecipients' does not contain at least one valid email address. + Note: + - While the primary focus is on the default policy, the function also retrieves and displays settings from additional policies that are not default, providing comprehensive insight into the organization's configuration. These additional policies are not used to determine the test's pass/fail status but are included in the details for informational purposes. #> + $recnum = "2.1.6" + Write-Verbose "Running Test-SpamPolicyAdminNotify for $recnum..." } - process { try { - # 2.1.6 Ensure Exchange Online Spam Policies are set to notify administrators - - # Retrieve the default hosted outbound spam filter policy - $hostedOutboundSpamFilterPolicy = Get-CISExoOutput -Rec $recnum - - # Check if both settings are enabled (Condition A and Condition B for pass) - $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail - $notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam - $areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled - - # Prepare failure details if any setting is not enabled (Condition A and Condition B for fail) - $failureDetails = @() - if (-not $bccSuspiciousOutboundMailEnabled) { - $failureDetails += "BccSuspiciousOutboundMail is not enabled." + # Mock data representing multiple spam filter policies + <# + $spamPolicies = @( + [PSCustomObject]@{ + Name = "Default" + IsDefault = $true + NotifyOutboundSpam = $false + BccSuspiciousOutboundMail = $true + NotifyOutboundSpamRecipients = "admin@example.com" + BccSuspiciousOutboundAdditionalRecipients = "bccadmin@example.com" + }, + [PSCustomObject]@{ + Name = "Custom Policy 1" + IsDefault = $false + NotifyOutboundSpam = $false + BccSuspiciousOutboundMail = $true + NotifyOutboundSpamRecipients = "" + BccSuspiciousOutboundAdditionalRecipients = "" + }, + [PSCustomObject]@{ + Name = "Custom Policy 2" + IsDefault = $false + NotifyOutboundSpam = $true + BccSuspiciousOutboundMail = $false + NotifyOutboundSpamRecipients = "notify@example.com" + BccSuspiciousOutboundAdditionalRecipients = "bccnotify@example.com" + } + ) + #> + $spamPolicies = Get-CISExoOutput -Rec $recnum + $defaultPolicy = $spamPolicies | Where-Object { $_.IsDefault -eq $true } + $additionalPolicies = $spamPolicies | Where-Object { $_.IsDefault -eq $false } + $details = @() + $failureReasons = @() + # Check the default policy settings and format details + # Build the details string for the default policy + if ($defaultPolicy) { + $details += "Default Policy: $($defaultPolicy.Name)`n`n" + + "Bcc Suspicious Outbound Mail: $($defaultPolicy.BccSuspiciousOutboundMail)`n" + + "Notify Outbound Spam: $($defaultPolicy.NotifyOutboundSpam)`n" + + "Notify Emails: $($defaultPolicy.NotifyOutboundSpamRecipients -join ', ')`n" + + "Bcc Emails: $($defaultPolicy.BccSuspiciousOutboundAdditionalRecipients -join ', ')" + if (-not $defaultPolicy.BccSuspiciousOutboundMail) { + $failureReasons += "BccSuspiciousOutboundMail should be enabled." + } + if (-not $defaultPolicy.NotifyOutboundSpam) { + $failureReasons += "NotifyOutboundSpam should be enabled." + } + if (-not $defaultPolicy.NotifyOutboundSpamRecipients) { + $failureReasons += "NotifyOutboundSpamRecipients should have at least one valid email." + } + if (-not $defaultPolicy.BccSuspiciousOutboundAdditionalRecipients) { + $failureReasons += "BccSuspiciousOutboundAdditionalRecipients should have at least one valid email." + } } - if (-not $notifyOutboundSpamEnabled) { - $failureDetails += "NotifyOutboundSpam is not enabled." + else { + $failureReasons += "No default policy found. This is critical for compliance." } - - # Create an instance of CISAuditResult and populate it + # Format additional policy details + foreach ($policy in $additionalPolicies) { + $details += "`n`nAdditional Policy: $($policy.Name)`n`n" + + "Bcc Suspicious Outbound Mail: $($policy.BccSuspiciousOutboundMail)`n" + + "Notify Outbound Spam: $($policy.NotifyOutboundSpam)`n" + + "Notify Emails: $($policy.NotifyOutboundSpamRecipients -join ', ')`n" + + "Bcc Emails: $($policy.BccSuspiciousOutboundAdditionalRecipients -join ', ')" + } + $result = $failureReasons.Count -eq 0 + $detailsString = $details -join "`n" + $failureReasonsString = $failureReasons -join "`n" + # Create and populate the CISAuditResult object $params = @{ Rec = $recnum - Result = $areSettingsEnabled - Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" } - Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' } - FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" } + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $detailsString + FailureReason = if (-not $result) { $failureReasonsString } else { "All settings are correct based on the default policy." } } $auditResult = Initialize-CISAuditResult @params } catch { - $LastError = $_ - $auditResult = Get-TestError -LastError $LastError -recnum $recnum + Write-Error "An error occurred during the test: $_" + $auditResult = Get-TestError -LastError $_ -recnum $recnum } } - end { - # Return auditResult + # Return the audit result return $auditResult } -} - +} \ No newline at end of file From db38fe827e5184d62dad3deae034ba9589e22b9d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 15:24:54 -0500 Subject: [PATCH 15/24] fix: 2.1.7 simplified output and added object comment-mid --- source/tests/Test-AntiPhishingPolicy.ps1 | 293 +++++++++++++++++------ 1 file changed, 224 insertions(+), 69 deletions(-) diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index f2027ca..522aa69 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -1,104 +1,259 @@ + +function Is-PolicyCompliant { + param ($policy) + return ($policy.Enabled -eq $true -and + $policy.PhishThresholdLevel -ge 2 -and + $policy.EnableMailboxIntelligenceProtection -eq $true -and + $policy.EnableMailboxIntelligence -eq $true -and + $policy.EnableSpoofIntelligence -eq $true) +} + +function Get-PolicyDetails { + param ( + [Parameter(Mandatory = $true)] + [pscustomobject]$policy, + + [Parameter(Mandatory = $true)] + [bool]$isCompliant + ) + + return "Policy: $($policy.Identity)`n" + + "Enabled: $($policy.Enabled)`n" + + "PhishThresholdLevel: $($policy.PhishThresholdLevel)`n" + + "MailboxIntelligenceProtection: $($policy.EnableMailboxIntelligenceProtection)`n" + + "MailboxIntelligence: $($policy.EnableMailboxIntelligence)`n" + + "SpoofIntelligence: $($policy.EnableSpoofIntelligence)`n" + + "TargetedUsersToProtect: $($policy.TargetedUsersToProtect -join ', ')`n" + + "IsCompliant: $isCompliant" +} + function Test-AntiPhishingPolicy { [CmdletBinding()] [OutputType([CISAuditResult])] - param ( - # Aligned - # Parameters can be added if needed - ) + param () begin { - # Dot source the class script if necessary - #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed - #$auditResults = @() $recnum = "2.1.7" - + Write-Verbose "Running Test-AntiPhishingPolicy for $recnum..." + . .\source\Classes\CISAuditResult.ps1 <# - Conditions for 2.1.7 (L1) Ensure that an anti-phishing policy has been created - + Conditions for 2.1.7 (L1) Ensure robust anti-phishing policies are enforced 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: Verify that an anti-phishing policy exists in the Microsoft 365 Security Center. - - Condition B: Using PowerShell, ensure the anti-phishing policy is configured with appropriate settings such as enabling impersonation protection and spoof intelligence. - + - Ensure the policies are checked in the following order of precedence: Strict, Standard, Custom, and Default. + - Specific conditions to check: + - Condition A: At least one policy (preferably Strict or Standard) should cover all users or be marked as default. + - Condition B: The policy must have enabled settings including PhishThresholdLevel at least 2, EnableMailboxIntelligenceProtection, EnableMailboxIntelligence, and EnableSpoofIntelligence. 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: No anti-phishing policy exists in the Microsoft 365 Security Center. - - Condition B: Using PowerShell, the anti-phishing policy is not configured with the required settings. + - Identify any policy misconfigurations or absence of comprehensive coverage. + - Specific conditions to check: + - Condition A: No policy comprehensively covers all users or meets specified security criteria. + - Condition B: Critical security features like Spoof Intelligence or Mailbox Intelligence are disabled in the relevant policies. #> } process { - try { - # Condition A: Ensure that an anti-phishing policy has been created - $antiPhishPolicies = Get-CISExoOutput -Rec $recnum + # Step 1: Retrieve all anti-phishing policies + $VerbosePreference = "Continue" + Write-Verbose "Retrieving all anti-phishing policies..." + #$antiPhishPolicies = Get-CISExoOutput -Rec $recnum - # Condition B: Verify the anti-phishing policy settings using PowerShell - $validatedPolicies = $antiPhishPolicies | Where-Object { - $_.Enabled -eq $true -and - $_.PhishThresholdLevel -ge 2 -and - $_.EnableMailboxIntelligenceProtection -eq $true -and - $_.EnableMailboxIntelligence -eq $true -and - $_.EnableSpoofIntelligence -eq $true + # Step 2: Initialize variables to track compliance and details + $compliantPolicy = $null + $details = @() + $failureReasons = @() + $hasFullCoveragePolicy = $false + $policiesEvaluated = @() + $PassedTests = @() + $FailedTests = @() + + Write-Verbose "Evaluating each policy for compliance..." + + # Separate policies based on type + $strictPolicy = $antiPhishPolicies | Where-Object { $_.Identity -match "Strict Preset Security Policy" } + $standardPolicy = $antiPhishPolicies | Where-Object { $_.Identity -match "Standard Preset Security Policy" } + $customPolicies = $antiPhishPolicies | Where-Object { -not ($_.Identity -match "Strict Preset Security Policy" -or $_.Identity -match "Standard Preset Security Policy" -or $_.IsDefault) } + $defaultPolicy = $antiPhishPolicies | Where-Object { $_.IsDefault } + + # Step 3: Check for Strict Preset Security Policy + if ($null -ne $strictPolicy) { + Write-Verbose "Evaluating policy: $($strictPolicy.Identity)" + $policiesEvaluated += $strictPolicy.Identity + # Check if policy is compliant + $isCompliant = Is-PolicyCompliant -policy $strictPolicy + # Log failure reasons for non-compliant policies + if (-not $isCompliant) { + $failureReasons += "Policy $($strictPolicy.Identity) does not meet compliance criteria." + Write-Verbose "Policy $($strictPolicy.Identity) fails to meet one or more required conditions." + $FailedTests += $strictPolicy.Identity + } + # Compile details of each policy using the new function + $details += Get-PolicyDetails -policy $strictPolicy -isCompliant $isCompliant + # Check if policy is Strict and covers all users + if ($isCompliant) { + $PassedTests += $strictPolicy.Identity + Write-Verbose "Policy $($strictPolicy.Identity) is compliant." + $strictUsersToProtect = $strictPolicy.TargetedUsersToProtect + if ($strictUsersToProtect.count -eq 0) { + $hasFullCoveragePolicy = $true + $compliantPolicy = $strictPolicy + $details += "Is Full Coverage Policy: $hasFullCoveragePolicy`n`n" + Write-Verbose "$($strictPolicy.Identity) is compliant and covers all users. Stopping further evaluation." + } + else { + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } } - - # Check if there is at least one policy that meets the requirements - $nonCompliantItems = $antiPhishPolicies | Where-Object { - $_.Enabled -ne $true -or - $_.PhishThresholdLevel -lt 2 -or - $_.EnableMailboxIntelligenceProtection -ne $true -or - $_.EnableMailboxIntelligence -ne $true -or - $_.EnableSpoofIntelligence -ne $true + # Step 4: Check for Standard Preset Security Policy if no full coverage from Strict + if ($null -ne $standardPolicy -and $hasFullCoveragePolicy -ne $true) { + Write-Verbose "Evaluating policy: $($standardPolicy.Identity)" + $policiesEvaluated += $standardPolicy.Identity + # Check if policy is compliant + $isCompliant = Is-PolicyCompliant -policy $standardPolicy + # Log failure reasons for non-compliant policies + if (-not $isCompliant) { + $failureReasons += "$($standardPolicy.Identity) does not meet compliance criteria." + Write-Verbose "$($standardPolicy.Identity) fails to meet one or more required conditions." + $FailedTests += $standardPolicy.Identity + } + # Compile details of each policy using the new function + $details += Get-PolicyDetails -policy $standardPolicy -isCompliant $isCompliant + # Check if policy is Strict and covers all users + if ($isCompliant) { + Write-Verbose "$($standardPolicy.Identity) is compliant." + $PassedTests += $standardPolicy.Identity + $standardUsersToProtect = $standardPolicy.TargetedUsersToProtect + if ($standardUsersToProtect.count -eq 0) { + $hasFullCoveragePolicy = $true + $compliantPolicy = $standardPolicy + $details += "Is Full Coverage Policy: $hasFullCoveragePolicy`n`n" + Write-Verbose "$($standardPolicy.Identity) is compliant and covers all users. Stopping further evaluation." + } + else { + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } } - $compliantItems = $validatedPolicies - $isCompliant = $compliantItems.Count -gt 0 - - # Prepare failure reasons for non-compliant items - $nonCompliantNames = $nonCompliantItems | ForEach-Object { $_.Name } - $failureReasons = if ($nonCompliantNames.Count -gt 0) { - "Reason: Does not meet one or more compliance criteria.`nNon-compliant Policies:`n" + ($nonCompliantNames -join "`n") + elseif ($null -ne $standardPolicy) { + Write-Verbose "$($standardPolicy.Identity) was not evaluated." + $isCompliant = Is-PolicyCompliant -policy $standardPolicy + $details += Get-PolicyDetails -policy $standardPolicy -isCompliant $isCompliant + $details += "Is Full Coverage Policy: $($false)`n`n" + } + # Step 5: Check Custom Policies if no full coverage from Strict or Standard + if ($null -ne $customPolicies -and $hasFullCoveragePolicy -ne $true) { + foreach ($policy in $customPolicies) { + if (-not $compliantPolicy) { + Write-Verbose "Evaluating policy: $($policy.Identity)" + $policiesEvaluated += $policy.Identity + # Check if policy is compliant + $isCompliant = Is-PolicyCompliant -policy $policy + # Log failure reasons for non-compliant policies + if (-not $isCompliant) { + $failureReasons += "$($policy.Identity) Policy does not meet compliance criteria." + Write-Verbose "$($policy.Identity) Policy fails to meet one or more required conditions." + $FailedTests += $policy.Identity + } + # Compile details of each policy using the new function + $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + # Check if policy is Custom and covers all users + if ($isCompliant) { + Write-Verbose "$($policy.Identity) is compliant." + $PassedTests += $policy.Identity + $custompolicyUsersToProtect = $policy.TargetedUsersToProtect + if ($custompolicyUsersToProtect.count -eq 0) { + $hasFullCoveragePolicy = $true + $compliantPolicy = $policy + $details += "Is Full Coverage Policy: $hasFullCoveragePolicy`n" + Write-Verbose "$($policy.Identity) is compliant and covers all users. Stopping further evaluation." + } + else { + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } + } + elseif ($compliantPolicy) { + Write-Verbose "$($policy.Identity) was not evaluated." + $isCompliant = Is-PolicyCompliant -policy $policy + $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } + } + elseif ($null -ne $customPolicies ) { + foreach ($policy in $customPolicies) { + Write-Verbose "$($policy.Identity) was not evaluated." + $isCompliant = Is-PolicyCompliant -policy $policy + $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } + # Step 6: Check Default Policy if no full coverage from Strict, Standard, or Custom + if ($null -ne $defaultPolicy -and $hasFullCoveragePolicy -ne $true) { + Write-Verbose "Evaluating policy: $($defaultPolicy.Identity)" + $policiesEvaluated += $defaultPolicy.Identity + # Check if policy is compliant + $isCompliant = Is-PolicyCompliant -policy $defaultPolicy + # Log failure reasons for non-compliant policies + if (-not $isCompliant) { + $failureReasons += "$($defaultPolicy.Identity) Policy does not meet compliance criteria." + Write-Verbose "$($defaultPolicy.Identity) Policy fails to meet one or more required conditions." + $FailedTests += $defaultPolicy.Identity + } + # Compile details of each policy using the new function + $details += Get-PolicyDetails -policy $defaultPolicy -isCompliant $isCompliant + # Check if policy is Default and covers all users + if ($isCompliant) { + Write-Verbose "$($defaultPolicy.Identity) is compliant." + $PassedTests += $defaultPolicy.Identity + $defaultUsersToProtect = $defaultPolicy.TargetedUsersToProtect + if ($defaultUsersToProtect.count -eq 0) { + $hasFullCoveragePolicy = $true + $compliantPolicy = $defaultPolicy + $details += "Is Full Coverage Policy: $hasFullCoveragePolicy`n" + Write-Verbose "$($defaultPolicy.Identity) is compliant and covers all users. Stopping further evaluation." + } + else { + $details += "Is Full Coverage Policy: $($false)`n`n" + } + } + } + elseif ($null -ne $defaultPolicy) { + Write-Verbose "$($defaultPolicy.Identity) was not evaluated." + $isCompliant = Is-PolicyCompliant -policy $defaultPolicy + $details += Get-PolicyDetails -policy $defaultPolicy -isCompliant $isCompliant + $details += "Is Full Coverage Policy: $($false)`n`n" + } + # Need new steps for below: + $isOverallCompliant = $hasFullCoveragePolicy -and $null -ne $compliantPolicy + $resultDetails = if ($isOverallCompliant) { + "Compliant Policy: $($compliantPolicy.Identity)`nDetails:`n" + ($details -join "`n") } else { - "N/A" + "Non-Compliant or No Policy Fully Covers All Users.`nDetails:`n" + ($details -join "`n") } - # Prepare details for non-compliant items - $nonCompliantDetails = $nonCompliantItems | ForEach-Object { - "Policy: $($_.Name)" - } - $nonCompliantDetails = $nonCompliantDetails -join "`n" - - # Prepare details based on compliance - $details = if ($nonCompliantItems) { - "Non-Compliant Items: $($nonCompliantItems.Count)`nDetails:`n$nonCompliantDetails" - } - else { - "Compliant Items: $($compliantItems.Count)" - } - - # Parameter splat for Initialize-CISAuditResult function + $VerbosePreference = "SilentlyContinue" $params = @{ Rec = $recnum - Result = $nonCompliantItems.Count -eq 0 - Status = if ($isCompliant) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $isOverallCompliant + Status = if ($isOverallCompliant) { "Pass" } else { "Fail" } + Details = $resultDetails + FailureReason = if (-not $isOverallCompliant) { $failureReasons -join "`n" } else { "All settings are correct based on the highest precedence policy that applies to all users." } } - - # Create and populate the CISAuditResult object $auditResult = Initialize-CISAuditResult @params } catch { - $LastError = $_ - $auditResult = Get-TestError -LastError $LastError -recnum $recnum + Write-Error "An error occurred during the test: $_" + $auditResult = Get-TestError -LastError $_ -recnum $recnum } } end { - # Return auditResult return $auditResult } } + + From d660f46eeb624fa041010d881bcb7ef32221a6e8 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 15:32:19 -0500 Subject: [PATCH 16/24] fix: 2.1.7 simplified output and added object comment-rc --- source/Private/Get-CISExoOutput.ps1 | 41 ++++++++++++- source/Private/Get-PhishPolicyDetail.ps1 | 18 ++++++ source/Private/Test-PhishPolicyCompliance.ps1 | 8 +++ source/tests/Test-AntiPhishingPolicy.ps1 | 59 ++++++++----------- .../Private/Get-PhishPolicyDetail.tests.ps1 | 27 +++++++++ .../Test-PhishPolicyCompliance.tests.ps1 | 27 +++++++++ 6 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 source/Private/Get-PhishPolicyDetail.ps1 create mode 100644 source/Private/Test-PhishPolicyCompliance.ps1 create mode 100644 tests/Unit/Private/Get-PhishPolicyDetail.tests.ps1 create mode 100644 tests/Unit/Private/Test-PhishPolicyCompliance.tests.ps1 diff --git a/source/Private/Get-CISExoOutput.ps1 b/source/Private/Get-CISExoOutput.ps1 index da7a5e3..615c421 100644 --- a/source/Private/Get-CISExoOutput.ps1 +++ b/source/Private/Get-CISExoOutput.ps1 @@ -349,7 +349,46 @@ function Get-CISExoOutput { } '2.1.7' { # Test-AntiPhishingPolicy.ps1 - # Condition A: Ensure that an anti-phishing policy has been created + <# + $antiPhishPolicies = @( + [PSCustomObject]@{ + Identity = "Strict Preset Security Policy" + Enabled = $true + PhishThresholdLevel = 4 + EnableMailboxIntelligenceProtection = $true + EnableMailboxIntelligence = $true + EnableSpoofIntelligence = $true + TargetedUsersToProtect = "John Doe;jdoe@contoso.net, Jane Does;janedoe@contoso.net" + }, + [PSCustomObject]@{ + Identity = "Office365 AntiPhish Default" + Enabled = $true + PhishThresholdLevel = 2 + EnableMailboxIntelligenceProtection = $true + EnableMailboxIntelligence = $true + EnableSpoofIntelligence = $true + TargetedUsersToProtect = $null # Assuming it targets all users as it's the default + }, + [PSCustomObject]@{ + Identity = "Admin" + Enabled = $true + PhishThresholdLevel = 2 + EnableMailboxIntelligenceProtection = $true + EnableMailboxIntelligence = $true + EnableSpoofIntelligence = $true + TargetedUsersToProtect = $null # Assuming it targets all users + }, + [PSCustomObject]@{ + Identity = "Standard Preset Security Policy" + Enabled = $true + PhishThresholdLevel = 3 + EnableMailboxIntelligenceProtection = $true + EnableMailboxIntelligence = $true + EnableSpoofIntelligence = $true + TargetedUsersToProtect = $null # Assuming it targets all users + } + ) + #> $antiPhishPolicies = Get-AntiPhishPolicy return $antiPhishPolicies } diff --git a/source/Private/Get-PhishPolicyDetail.ps1 b/source/Private/Get-PhishPolicyDetail.ps1 new file mode 100644 index 0000000..c9be82c --- /dev/null +++ b/source/Private/Get-PhishPolicyDetail.ps1 @@ -0,0 +1,18 @@ +function Get-PhishPolicyDetail { + param ( + [Parameter(Mandatory = $true)] + [pscustomobject]$policy, + + [Parameter(Mandatory = $true)] + [bool]$isCompliant + ) + + return "Policy: $($policy.Identity)`n" + + "Enabled: $($policy.Enabled)`n" + + "PhishThresholdLevel: $($policy.PhishThresholdLevel)`n" + + "MailboxIntelligenceProtection: $($policy.EnableMailboxIntelligenceProtection)`n" + + "MailboxIntelligence: $($policy.EnableMailboxIntelligence)`n" + + "SpoofIntelligence: $($policy.EnableSpoofIntelligence)`n" + + "TargetedUsersToProtect: $($policy.TargetedUsersToProtect -join ', ')`n" + + "IsCompliant: $isCompliant" +} \ No newline at end of file diff --git a/source/Private/Test-PhishPolicyCompliance.ps1 b/source/Private/Test-PhishPolicyCompliance.ps1 new file mode 100644 index 0000000..01b99aa --- /dev/null +++ b/source/Private/Test-PhishPolicyCompliance.ps1 @@ -0,0 +1,8 @@ +function Test-PhishPolicyCompliance { + param ($policy) + return ($policy.Enabled -eq $true -and + $policy.PhishThresholdLevel -ge 2 -and + $policy.EnableMailboxIntelligenceProtection -eq $true -and + $policy.EnableMailboxIntelligence -eq $true -and + $policy.EnableSpoofIntelligence -eq $true) +} \ No newline at end of file diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 522aa69..34b84a4 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -1,5 +1,5 @@ -function Is-PolicyCompliant { +function Test-PhishPolicyCompliance { param ($policy) return ($policy.Enabled -eq $true -and $policy.PhishThresholdLevel -ge 2 -and @@ -8,24 +8,7 @@ function Is-PolicyCompliant { $policy.EnableSpoofIntelligence -eq $true) } -function Get-PolicyDetails { - param ( - [Parameter(Mandatory = $true)] - [pscustomobject]$policy, - [Parameter(Mandatory = $true)] - [bool]$isCompliant - ) - - return "Policy: $($policy.Identity)`n" + - "Enabled: $($policy.Enabled)`n" + - "PhishThresholdLevel: $($policy.PhishThresholdLevel)`n" + - "MailboxIntelligenceProtection: $($policy.EnableMailboxIntelligenceProtection)`n" + - "MailboxIntelligence: $($policy.EnableMailboxIntelligence)`n" + - "SpoofIntelligence: $($policy.EnableSpoofIntelligence)`n" + - "TargetedUsersToProtect: $($policy.TargetedUsersToProtect -join ', ')`n" + - "IsCompliant: $isCompliant" -} function Test-AntiPhishingPolicy { [CmdletBinding()] @@ -81,7 +64,7 @@ function Test-AntiPhishingPolicy { Write-Verbose "Evaluating policy: $($strictPolicy.Identity)" $policiesEvaluated += $strictPolicy.Identity # Check if policy is compliant - $isCompliant = Is-PolicyCompliant -policy $strictPolicy + $isCompliant = Test-PhishPolicyCompliance -policy $strictPolicy # Log failure reasons for non-compliant policies if (-not $isCompliant) { $failureReasons += "Policy $($strictPolicy.Identity) does not meet compliance criteria." @@ -89,7 +72,7 @@ function Test-AntiPhishingPolicy { $FailedTests += $strictPolicy.Identity } # Compile details of each policy using the new function - $details += Get-PolicyDetails -policy $strictPolicy -isCompliant $isCompliant + $details += Get-PhishPolicyDetail -policy $strictPolicy -isCompliant $isCompliant # Check if policy is Strict and covers all users if ($isCompliant) { $PassedTests += $strictPolicy.Identity @@ -111,7 +94,7 @@ function Test-AntiPhishingPolicy { Write-Verbose "Evaluating policy: $($standardPolicy.Identity)" $policiesEvaluated += $standardPolicy.Identity # Check if policy is compliant - $isCompliant = Is-PolicyCompliant -policy $standardPolicy + $isCompliant = Test-PhishPolicyCompliance -policy $standardPolicy # Log failure reasons for non-compliant policies if (-not $isCompliant) { $failureReasons += "$($standardPolicy.Identity) does not meet compliance criteria." @@ -119,7 +102,7 @@ function Test-AntiPhishingPolicy { $FailedTests += $standardPolicy.Identity } # Compile details of each policy using the new function - $details += Get-PolicyDetails -policy $standardPolicy -isCompliant $isCompliant + $details += Get-PhishPolicyDetail -policy $standardPolicy -isCompliant $isCompliant # Check if policy is Strict and covers all users if ($isCompliant) { Write-Verbose "$($standardPolicy.Identity) is compliant." @@ -138,8 +121,8 @@ function Test-AntiPhishingPolicy { } elseif ($null -ne $standardPolicy) { Write-Verbose "$($standardPolicy.Identity) was not evaluated." - $isCompliant = Is-PolicyCompliant -policy $standardPolicy - $details += Get-PolicyDetails -policy $standardPolicy -isCompliant $isCompliant + $isCompliant = Test-PhishPolicyCompliance -policy $standardPolicy + $details += Get-PhishPolicyDetail -policy $standardPolicy -isCompliant $isCompliant $details += "Is Full Coverage Policy: $($false)`n`n" } # Step 5: Check Custom Policies if no full coverage from Strict or Standard @@ -149,7 +132,7 @@ function Test-AntiPhishingPolicy { Write-Verbose "Evaluating policy: $($policy.Identity)" $policiesEvaluated += $policy.Identity # Check if policy is compliant - $isCompliant = Is-PolicyCompliant -policy $policy + $isCompliant = Test-PhishPolicyCompliance -policy $policy # Log failure reasons for non-compliant policies if (-not $isCompliant) { $failureReasons += "$($policy.Identity) Policy does not meet compliance criteria." @@ -157,7 +140,7 @@ function Test-AntiPhishingPolicy { $FailedTests += $policy.Identity } # Compile details of each policy using the new function - $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + $details += Get-PhishPolicyDetail -policy $policy -isCompliant $isCompliant # Check if policy is Custom and covers all users if ($isCompliant) { Write-Verbose "$($policy.Identity) is compliant." @@ -176,8 +159,8 @@ function Test-AntiPhishingPolicy { } elseif ($compliantPolicy) { Write-Verbose "$($policy.Identity) was not evaluated." - $isCompliant = Is-PolicyCompliant -policy $policy - $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + $isCompliant = Test-PhishPolicyCompliance -policy $policy + $details += Get-PhishPolicyDetail -policy $policy -isCompliant $isCompliant $details += "Is Full Coverage Policy: $($false)`n`n" } } @@ -185,8 +168,8 @@ function Test-AntiPhishingPolicy { elseif ($null -ne $customPolicies ) { foreach ($policy in $customPolicies) { Write-Verbose "$($policy.Identity) was not evaluated." - $isCompliant = Is-PolicyCompliant -policy $policy - $details += Get-PolicyDetails -policy $policy -isCompliant $isCompliant + $isCompliant = Test-PhishPolicyCompliance -policy $policy + $details += Get-PhishPolicyDetail -policy $policy -isCompliant $isCompliant $details += "Is Full Coverage Policy: $($false)`n`n" } } @@ -195,7 +178,7 @@ function Test-AntiPhishingPolicy { Write-Verbose "Evaluating policy: $($defaultPolicy.Identity)" $policiesEvaluated += $defaultPolicy.Identity # Check if policy is compliant - $isCompliant = Is-PolicyCompliant -policy $defaultPolicy + $isCompliant = Test-PhishPolicyCompliance -policy $defaultPolicy # Log failure reasons for non-compliant policies if (-not $isCompliant) { $failureReasons += "$($defaultPolicy.Identity) Policy does not meet compliance criteria." @@ -203,7 +186,7 @@ function Test-AntiPhishingPolicy { $FailedTests += $defaultPolicy.Identity } # Compile details of each policy using the new function - $details += Get-PolicyDetails -policy $defaultPolicy -isCompliant $isCompliant + $details += Get-PhishPolicyDetail -policy $defaultPolicy -isCompliant $isCompliant # Check if policy is Default and covers all users if ($isCompliant) { Write-Verbose "$($defaultPolicy.Identity) is compliant." @@ -222,20 +205,23 @@ function Test-AntiPhishingPolicy { } elseif ($null -ne $defaultPolicy) { Write-Verbose "$($defaultPolicy.Identity) was not evaluated." - $isCompliant = Is-PolicyCompliant -policy $defaultPolicy - $details += Get-PolicyDetails -policy $defaultPolicy -isCompliant $isCompliant + $isCompliant = Test-PhishPolicyCompliance -policy $defaultPolicy + $details += Get-PhishPolicyDetail -policy $defaultPolicy -isCompliant $isCompliant $details += "Is Full Coverage Policy: $($false)`n`n" } - # Need new steps for below: + # Determine overall compliance based on the evaluations $isOverallCompliant = $hasFullCoveragePolicy -and $null -ne $compliantPolicy + # Prepare result details $resultDetails = if ($isOverallCompliant) { "Compliant Policy: $($compliantPolicy.Identity)`nDetails:`n" + ($details -join "`n") } else { "Non-Compliant or No Policy Fully Covers All Users.`nDetails:`n" + ($details -join "`n") } - + # Verbose output for the overall compliance + Write-Verbose "Overall Compliance: $isOverallCompliant" $VerbosePreference = "SilentlyContinue" + # Prepare the parameters for the audit result $params = @{ Rec = $recnum Result = $isOverallCompliant @@ -243,6 +229,7 @@ function Test-AntiPhishingPolicy { Details = $resultDetails FailureReason = if (-not $isOverallCompliant) { $failureReasons -join "`n" } else { "All settings are correct based on the highest precedence policy that applies to all users." } } + # Initialize the audit result $auditResult = Initialize-CISAuditResult @params } catch { diff --git a/tests/Unit/Private/Get-PhishPolicyDetail.tests.ps1 b/tests/Unit/Private/Get-PhishPolicyDetail.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Get-PhishPolicyDetail.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/Test-PhishPolicyCompliance.tests.ps1 b/tests/Unit/Private/Test-PhishPolicyCompliance.tests.ps1 new file mode 100644 index 0000000..4a2aa69 --- /dev/null +++ b/tests/Unit/Private/Test-PhishPolicyCompliance.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 34a44d07098ccfefa5583d19c390d0bd69343b1c Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:14:55 -0500 Subject: [PATCH 17/24] fix: 2.1.7 simplified output and added object comment-release --- source/tests/Test-AntiPhishingPolicy.ps1 | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index 34b84a4..cd2fbac 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -1,15 +1,3 @@ - -function Test-PhishPolicyCompliance { - param ($policy) - return ($policy.Enabled -eq $true -and - $policy.PhishThresholdLevel -ge 2 -and - $policy.EnableMailboxIntelligenceProtection -eq $true -and - $policy.EnableMailboxIntelligence -eq $true -and - $policy.EnableSpoofIntelligence -eq $true) -} - - - function Test-AntiPhishingPolicy { [CmdletBinding()] [OutputType([CISAuditResult])] @@ -18,7 +6,7 @@ function Test-AntiPhishingPolicy { begin { $recnum = "2.1.7" Write-Verbose "Running Test-AntiPhishingPolicy for $recnum..." - . .\source\Classes\CISAuditResult.ps1 + #. .\source\Classes\CISAuditResult.ps1 <# Conditions for 2.1.7 (L1) Ensure robust anti-phishing policies are enforced Validate test for a pass: @@ -38,9 +26,9 @@ function Test-AntiPhishingPolicy { process { try { # Step 1: Retrieve all anti-phishing policies - $VerbosePreference = "Continue" + #$VerbosePreference = "Continue" Write-Verbose "Retrieving all anti-phishing policies..." - #$antiPhishPolicies = Get-CISExoOutput -Rec $recnum + $antiPhishPolicies = Get-CISExoOutput -Rec $recnum # Step 2: Initialize variables to track compliance and details $compliantPolicy = $null @@ -220,14 +208,14 @@ function Test-AntiPhishingPolicy { } # Verbose output for the overall compliance Write-Verbose "Overall Compliance: $isOverallCompliant" - $VerbosePreference = "SilentlyContinue" + #$VerbosePreference = "SilentlyContinue" # Prepare the parameters for the audit result $params = @{ Rec = $recnum Result = $isOverallCompliant Status = if ($isOverallCompliant) { "Pass" } else { "Fail" } Details = $resultDetails - FailureReason = if (-not $isOverallCompliant) { $failureReasons -join "`n" } else { "All settings are correct based on the highest precedence policy that applies to all users." } + FailureReason = if (-not $isOverallCompliant) { $failureReasons -join "`n" } else { "None: All settings are correct based on the highest precedence policy that applies to all users." } } # Initialize the audit result $auditResult = Initialize-CISAuditResult @params From 9805e9910923f507ed7bc729bae4290dd757bb41 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:16:27 -0500 Subject: [PATCH 18/24] fix: 2.1.9 simplified output and added object comment-release --- source/tests/Test-EnableDKIM.ps1 | 34 +++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index fa86282..ec92d52 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -4,6 +4,7 @@ function Test-EnableDKIM { param ( # Aligned # Parameters can be added if needed + [string]$DomainName ) begin { @@ -37,9 +38,16 @@ function Test-EnableDKIM { # Retrieve DKIM configuration for all domains $dkimConfig = Get-CISExoOutput -Rec $recnum - $dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false - $dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain } - + if (-not $DomainName) { + $dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false + $dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain } + } + else { + $dkimResult = ($dkimConfig | Where-Object { $_.Domain -eq $DomainName }).Enabled + if ($dkimResult -eq $false) { + $dkimFailedDomains = $dkimConfig | Where-Object { $_.Domain -eq $DomainName } | ForEach-Object { $_.Domain } + } + } # Prepare failure reasons and details based on compliance $failureReasons = if (-not $dkimResult) { "DKIM is not enabled for some domains" # Condition A fail @@ -47,21 +55,25 @@ function Test-EnableDKIM { else { "N/A" } - + $basedetails = "All domains have DKIM enabled" $details = if ($dkimResult) { - "All domains have DKIM enabled" # Condition A pass + if ($DomainName) { + "Domain: $DomainName; $basedetails" + } + else { + $basedetails + } # Condition A pass } else { "DKIM not enabled for: $($dkimFailedDomains -join ', ')" # Condition B fail } - # Create and populate the CISAuditResult object $params = @{ - Rec = $recnum - Result = $dkimResult - Status = if ($dkimResult) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Rec = $recnum + Result = $dkimResult + Status = if ($dkimResult) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params } From bd0ce158afe3a58244a4a00a28dc65f2d6adb0d3 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:54:18 -0500 Subject: [PATCH 19/24] fix: 2.1.9 simplified output and added object comment-release --- source/tests/Test-EnableDKIM.ps1 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/source/tests/Test-EnableDKIM.ps1 b/source/tests/Test-EnableDKIM.ps1 index ec92d52..358d40d 100644 --- a/source/tests/Test-EnableDKIM.ps1 +++ b/source/tests/Test-EnableDKIM.ps1 @@ -6,23 +6,19 @@ function Test-EnableDKIM { # Parameters can be added if needed [string]$DomainName ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "2.1.9" - + Write-Verbose "Running Test-EnableDKIM for $recnum..." <# Conditions for 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains (Automated) - 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: DKIM is enabled for all Exchange Online domains in the Microsoft 365 security center. - Condition B: Using the Exchange Online PowerShell Module, the `CnameConfiguration.Enabled` property for each domain is set to `True`. - 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: @@ -30,12 +26,9 @@ function Test-EnableDKIM { - Condition B: Using the Exchange Online PowerShell Module, the `CnameConfiguration.Enabled` property for one or more domains is set to `False`. #> } - process { - try { # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains - # Retrieve DKIM configuration for all domains $dkimConfig = Get-CISExoOutput -Rec $recnum if (-not $DomainName) { @@ -82,7 +75,6 @@ function Test-EnableDKIM { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult From c7f7fe11cc46a544b3385e00fa20203ce93f64d8 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:54:32 -0500 Subject: [PATCH 20/24] fix: formatting --- source/tests/Test-AntiPhishingPolicy.ps1 | 7 ------ source/tests/Test-AuditDisabledFalse.ps1 | 10 +------- source/tests/Test-AuditLogSearch.ps1 | 14 ++--------- source/tests/Test-BlockMailForwarding.ps1 | 17 ++------------ .../tests/Test-BlockSharedMailboxSignIn.ps1 | 7 ------ source/tests/Test-CommonAttachmentFilter.ps1 | 1 - source/tests/Test-CustomerLockbox.ps1 | 4 ---- .../tests/Test-ExternalSharingCalendars.ps1 | 5 ---- source/tests/Test-IdentifyExternalEmail.ps1 | 10 +------- source/tests/Test-MailTipsEnabled.ps1 | 12 +--------- source/tests/Test-MailboxAuditingE3.ps1 | 23 +------------------ source/tests/Test-MailboxAuditingE5.ps1 | 18 +-------------- .../tests/Test-ModernAuthExchangeOnline.ps1 | 12 +--------- source/tests/Test-NoWhitelistDomains.ps1 | 11 +-------- source/tests/Test-NotifyMalwareInternal.ps1 | 9 -------- source/tests/Test-ReportSecurityInTeams.ps1 | 8 +------ source/tests/Test-RestrictOutlookAddins.ps1 | 12 +--------- .../Test-RestrictStorageProvidersOutlook.ps1 | 10 +------- source/tests/Test-SafeAttachmentsPolicy.ps1 | 12 ++++------ source/tests/Test-SafeAttachmentsTeams.ps1 | 2 -- source/tests/Test-SafeLinksOfficeApps.ps1 | 9 -------- source/tests/Test-SpamPolicyAdminNotify.ps1 | 1 - 22 files changed, 19 insertions(+), 195 deletions(-) diff --git a/source/tests/Test-AntiPhishingPolicy.ps1 b/source/tests/Test-AntiPhishingPolicy.ps1 index cd2fbac..61901c6 100644 --- a/source/tests/Test-AntiPhishingPolicy.ps1 +++ b/source/tests/Test-AntiPhishingPolicy.ps1 @@ -2,7 +2,6 @@ function Test-AntiPhishingPolicy { [CmdletBinding()] [OutputType([CISAuditResult])] param () - begin { $recnum = "2.1.7" Write-Verbose "Running Test-AntiPhishingPolicy for $recnum..." @@ -22,14 +21,12 @@ function Test-AntiPhishingPolicy { - Condition B: Critical security features like Spoof Intelligence or Mailbox Intelligence are disabled in the relevant policies. #> } - process { try { # Step 1: Retrieve all anti-phishing policies #$VerbosePreference = "Continue" Write-Verbose "Retrieving all anti-phishing policies..." $antiPhishPolicies = Get-CISExoOutput -Rec $recnum - # Step 2: Initialize variables to track compliance and details $compliantPolicy = $null $details = @() @@ -38,15 +35,12 @@ function Test-AntiPhishingPolicy { $policiesEvaluated = @() $PassedTests = @() $FailedTests = @() - Write-Verbose "Evaluating each policy for compliance..." - # Separate policies based on type $strictPolicy = $antiPhishPolicies | Where-Object { $_.Identity -match "Strict Preset Security Policy" } $standardPolicy = $antiPhishPolicies | Where-Object { $_.Identity -match "Standard Preset Security Policy" } $customPolicies = $antiPhishPolicies | Where-Object { -not ($_.Identity -match "Strict Preset Security Policy" -or $_.Identity -match "Standard Preset Security Policy" -or $_.IsDefault) } $defaultPolicy = $antiPhishPolicies | Where-Object { $_.IsDefault } - # Step 3: Check for Strict Preset Security Policy if ($null -ne $strictPolicy) { Write-Verbose "Evaluating policy: $($strictPolicy.Identity)" @@ -225,7 +219,6 @@ function Test-AntiPhishingPolicy { $auditResult = Get-TestError -LastError $_ -recnum $recnum } } - end { return $auditResult } diff --git a/source/tests/Test-AuditDisabledFalse.ps1 b/source/tests/Test-AuditDisabledFalse.ps1 index fd8436e..ccfc555 100644 --- a/source/tests/Test-AuditDisabledFalse.ps1 +++ b/source/tests/Test-AuditDisabledFalse.ps1 @@ -5,11 +5,9 @@ function Test-AuditDisabledFalse { param ( # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Conditions for 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' # # Validate test for a pass: @@ -25,18 +23,15 @@ function Test-AuditDisabledFalse { # - Condition A: The `AuditDisabled` organizational setting is set to `True` in the Microsoft 365 admin center. # - Condition B: Using PowerShell, the `AuditDisabled` property in the organization's configuration is set to `True`. # - Condition C: Mailbox auditing is not enabled by default at the organizational level. - # Initialization code, if needed $recnum = "6.1.1" + Write-Verbose "Running Test-AuditDisabledFalse for $recnum..." } - process { try { # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' - # Retrieve the AuditDisabled configuration (Condition B) $auditNotDisabled = Get-CISExoOutput -Rec $recnum - # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditNotDisabled) { "AuditDisabled is set to True" # Condition A Fail @@ -44,14 +39,12 @@ function Test-AuditDisabledFalse { else { "N/A" } - $details = if ($auditNotDisabled) { "Audit is not disabled organizationally" # Condition C Pass } else { "Audit is disabled organizationally" # Condition C Fail } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -67,7 +60,6 @@ function Test-AuditDisabledFalse { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-AuditLogSearch.ps1 b/source/tests/Test-AuditLogSearch.ps1 index 2d44b69..a1a8962 100644 --- a/source/tests/Test-AuditLogSearch.ps1 +++ b/source/tests/Test-AuditLogSearch.ps1 @@ -5,23 +5,20 @@ function Test-AuditLogSearch { # Aligned # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "3.1.1" - + Write-Verbose "Running Test-AuditLogSearch for $recnum..." <# Conditions for 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - 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: Audit log search is enabled in the Microsoft Purview compliance portal. - Condition B: The audit log retains user and admin activity for 90 days. - Condition C: Audit log search capabilities are functional (search results are displayed for activities within the past 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: @@ -30,14 +27,10 @@ function Test-AuditLogSearch { - Condition C: Audit log search capabilities are non-functional (no search results are displayed for activities within the past 30 days). #> } - process { - try { # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled - $auditLogResult = Get-CISExoOutput -Rec $recnum - # Prepare failure reasons and details based on compliance $failureReasons = if (-not $auditLogResult) { # Condition A (Fail): Audit log search is not enabled in the Microsoft Purview compliance portal @@ -46,7 +39,6 @@ function Test-AuditLogSearch { else { "N/A" } - $details = if ($auditLogResult) { # Condition A (Pass): Audit log search is enabled in the Microsoft Purview compliance portal "UnifiedAuditLogIngestionEnabled: True" @@ -54,7 +46,6 @@ function Test-AuditLogSearch { else { "UnifiedAuditLogIngestionEnabled: False" } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -70,9 +61,8 @@ function Test-AuditLogSearch { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult } -} +} \ No newline at end of file diff --git a/source/tests/Test-BlockMailForwarding.ps1 b/source/tests/Test-BlockMailForwarding.ps1 index 81177ff..645b783 100644 --- a/source/tests/Test-BlockMailForwarding.ps1 +++ b/source/tests/Test-BlockMailForwarding.ps1 @@ -4,23 +4,20 @@ function Test-BlockMailForwarding { param ( # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "6.2.1" - + Write-Verbose "Running Test-BlockMailForwarding for $recnum..." <# Conditions for 6.2.1 (L1) Ensure all forms of mail forwarding are blocked and/or disabled - 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: Transport rules do not forward email to external domains. - Condition B: Anti-spam outbound policy is configured to disable automatic email forwarding to external domains. - Condition C: No exceptions to the forwarding rules unless explicitly defined by organizational policy. - 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: @@ -29,26 +26,20 @@ function Test-BlockMailForwarding { - Condition C: Unapproved exceptions to the forwarding rules are present. #> } - process { try { # 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-CISExoOutput -Rec $recnum $transportForwardingBlocked = $transportRules.Count -eq 0 - # Step 2: Check all anti-spam outbound policies $nonCompliantSpamPoliciesArray = @($nonCompliantSpamPolicies) $spamForwardingBlocked = $nonCompliantSpamPoliciesArray.Count -eq 0 - # Determine overall compliance $forwardingBlocked = $transportForwardingBlocked -and $spamForwardingBlocked - # Prepare failure reasons and details based on compliance $failureReasons = @() $details = @() - if ($transportRules -ne 1) { # Fail Condition A $failureReasons += "Mail forwarding rules found: $($transportRules.Name -join ', ')" @@ -58,7 +49,6 @@ function Test-BlockMailForwarding { } $details += "`n" } - if ($nonCompliantSpamPoliciesArray.Count -gt 0) { # Fail Condition B $failureReasons += "Outbound spam policies allowing automatic forwarding found." @@ -67,7 +57,6 @@ function Test-BlockMailForwarding { "$($_.Name)|$($_.AutoForwardingMode)" } } - if ($failureReasons.Count -eq 0) { $failureReasons = "N/A" $details = "Both transport rules and outbound spam policies are configured correctly to block forwarding." @@ -76,7 +65,6 @@ function Test-BlockMailForwarding { $failureReasons = $failureReasons -join " | " $details = $details -join "`n" } - # Populate the audit result $params = @{ Rec = $recnum @@ -92,9 +80,8 @@ function Test-BlockMailForwarding { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult } -} +} \ No newline at end of file diff --git a/source/tests/Test-BlockSharedMailboxSignIn.ps1 b/source/tests/Test-BlockSharedMailboxSignIn.ps1 index 2e6166e..d0edeaf 100644 --- a/source/tests/Test-BlockSharedMailboxSignIn.ps1 +++ b/source/tests/Test-BlockSharedMailboxSignIn.ps1 @@ -8,7 +8,6 @@ function Test-BlockSharedMailboxSignIn { begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "1.2.2" Write-Verbose "Running Test-BlockSharedMailboxSignIn for $recnum..." @@ -26,7 +25,6 @@ function Test-BlockSharedMailboxSignIn { # - Condition A: One or more shared mailboxes have the "Sign-in blocked" option enabled in the properties pane on the Microsoft 365 admin center. # - Condition B: Using PowerShell, the `AccountEnabled` property for one or more shared mailboxes is set to `True`. } - process { try { # Step: Retrieve shared mailbox details @@ -63,11 +61,9 @@ function Test-BlockSharedMailboxSignIn { $users = Get-CISAadOutput -Rec $recnum # Step: Retrieve details of shared mailboxes from Azure AD (Condition B: Pass/Fail) $sharedMailboxDetails = $users | Where-Object {$_.objectid -in $objectids} - # Step: Identify enabled mailboxes (Condition B: Pass/Fail) $enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName } $allBlocked = $enabledMailboxes.Count -eq 0 - # Step: Determine failure reasons based on enabled mailboxes (Condition A & B: Fail) $failureReasons = if (-not $allBlocked) { "Some mailboxes have sign-in enabled (AccountEnabled:True):`n$($enabledMailboxes -join ', ')" @@ -75,7 +71,6 @@ function Test-BlockSharedMailboxSignIn { else { "N/A" } - # Step: Prepare details for the audit result (Condition A & B: Pass/Fail) $details = if ($allBlocked) { "All shared mailboxes have sign-in blocked." @@ -83,7 +78,6 @@ function Test-BlockSharedMailboxSignIn { else { "AccountEnabled set to True Mailboxes: $($enabledMailboxes -join ', ')" } - # Step: Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -99,7 +93,6 @@ function Test-BlockSharedMailboxSignIn { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-CommonAttachmentFilter.ps1 b/source/tests/Test-CommonAttachmentFilter.ps1 index 7a77945..0abb2c8 100644 --- a/source/tests/Test-CommonAttachmentFilter.ps1 +++ b/source/tests/Test-CommonAttachmentFilter.ps1 @@ -5,7 +5,6 @@ function Test-CommonAttachmentFilter { # Aligned # Parameters can be added if needed ) - begin { <# Conditions for 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled diff --git a/source/tests/Test-CustomerLockbox.ps1 b/source/tests/Test-CustomerLockbox.ps1 index d71cf5e..9f4b830 100644 --- a/source/tests/Test-CustomerLockbox.ps1 +++ b/source/tests/Test-CustomerLockbox.ps1 @@ -29,7 +29,6 @@ function Test-CustomerLockbox { # - Condition B: Using the SecureScore portal, the Customer Lockbox feature is not enabled. # - Condition C: Using PowerShell, the Customer Lockbox feature is not set to `True`. } - process { try { # Step: Retrieve the organization configuration (Condition C: Pass/Fail) @@ -43,7 +42,6 @@ function Test-CustomerLockbox { else { "N/A" } - # Step: Prepare details for the audit result (Condition A, B, & C: Pass/Fail) $details = if ($customerLockboxEnabled) { "Customer Lockbox Enabled: True" @@ -51,7 +49,6 @@ function Test-CustomerLockbox { else { "Customer Lockbox Enabled: False" } - # Step: Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -67,7 +64,6 @@ function Test-CustomerLockbox { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-ExternalSharingCalendars.ps1 b/source/tests/Test-ExternalSharingCalendars.ps1 index 9cf01d9..0a43079 100644 --- a/source/tests/Test-ExternalSharingCalendars.ps1 +++ b/source/tests/Test-ExternalSharingCalendars.ps1 @@ -5,11 +5,9 @@ function Test-ExternalSharingCalendars { # Aligned # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "1.3.3" Write-Verbose "Running Test-ExternalSharingCalendars for $recnum..." @@ -27,7 +25,6 @@ function Test-ExternalSharingCalendars { # - Condition A: In the Microsoft 365 admin center, external calendar sharing is enabled. # - Condition B: Using the Exchange Online PowerShell Module, the `OrganizationConfig` property `ExternalSharingEnabled` is set to `True`. } - process { try { # Step: Retrieve sharing policies related to calendar sharing @@ -87,7 +84,6 @@ function Test-ExternalSharingCalendars { else { "Enabled Sharing Policies:`n$($sharingPolicyDetails -join ', ')" } - # Step: Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -103,7 +99,6 @@ function Test-ExternalSharingCalendars { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-IdentifyExternalEmail.ps1 b/source/tests/Test-IdentifyExternalEmail.ps1 index 571b41d..5191899 100644 --- a/source/tests/Test-IdentifyExternalEmail.ps1 +++ b/source/tests/Test-IdentifyExternalEmail.ps1 @@ -9,10 +9,9 @@ function Test-IdentifyExternalEmail { begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "6.2.3" - + Write-Verbose "Running Test-IdentifyExternalEmail for $recnum..." # Conditions for 6.2.3 (L1) Ensure email from external senders is identified # # Validate test for a pass: @@ -29,16 +28,12 @@ function Test-IdentifyExternalEmail { # - Condition B: The BypassAllowList contains unauthorized email addresses. # - Condition C: External sender tag does not appear in email messages received from external sources. } - process { - try { # 6.2.3 (L1) Ensure email from external senders is identified - # Retrieve external sender tagging configuration $externalInOutlook = Get-CISExoOutput -Rec $recnum $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true - # Prepare failure reasons and details based on compliance $failureReasons = if (-not $externalTaggingEnabled) { # Condition A: External tagging is not enabled using PowerShell for all identities. @@ -47,10 +42,8 @@ function Test-IdentifyExternalEmail { else { "N/A" } - # Details for external tagging configuration $details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)" - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -66,7 +59,6 @@ function Test-IdentifyExternalEmail { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-MailTipsEnabled.ps1 b/source/tests/Test-MailTipsEnabled.ps1 index aa6d6bc..be58075 100644 --- a/source/tests/Test-MailTipsEnabled.ps1 +++ b/source/tests/Test-MailTipsEnabled.ps1 @@ -5,15 +5,12 @@ function Test-MailTipsEnabled { # Aligned # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed - - $auditResult = [CISAuditResult]::new() $recnum = "6.5.2" - + Write-Verbose "Running Test-MailTipsEnabled for $recnum..." # Conditions for 6.5.2 (L2) Ensure MailTips are enabled for end users # # Validate test for a pass: @@ -32,18 +29,14 @@ function Test-MailTipsEnabled { # - Condition C: MailTipsGroupMetricsEnabled is not set to True. # - Condition D: MailTipsLargeAudienceThreshold is not set to an acceptable value (default is 25). } - process { try { # 6.5.2 (L2) Ensure MailTips are enabled for end users - # Retrieve organization configuration for MailTips settings $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 $externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled - # Prepare failure reasons and details based on compliance $failureReasons = if (-not ($allTipsEnabled -and $externalRecipientsTipsEnabled)) { "One or more MailTips settings are not configured as required." @@ -51,14 +44,12 @@ function Test-MailTipsEnabled { else { "N/A" } - $details = if ($allTipsEnabled -and $externalRecipientsTipsEnabled) { "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)" } else { "One or more MailTips settings are not configured as required." } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -74,7 +65,6 @@ function Test-MailTipsEnabled { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index d05c18c..ab6b2dc 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -4,11 +4,9 @@ function Test-MailboxAuditingE3 { param ( # Parameters can be added if needed ) - begin { <# Conditions for 6.1.2 (L1) Ensure mailbox auditing for E3 users is Enabled - 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: @@ -16,7 +14,6 @@ function Test-MailboxAuditingE3 { - Condition B: The `AuditAdmin` actions include `ApplyRecord`, `Create`, `HardDelete`, `MoveToDeletedItems`, `SendAs`, `SendOnBehalf`, `SoftDelete`, `Update`, `UpdateCalendarDelegation`, `UpdateFolderPermissions`, and `UpdateInboxRules`. - Condition C: The `AuditDelegate` actions include `ApplyRecord`, `Create`, `HardDelete`, `MoveToDeletedItems`, `SendAs`, `SendOnBehalf`, `SoftDelete`, `Update`, `UpdateFolderPermissions`, and `UpdateInboxRules`. - Condition D: The `AuditOwner` actions include `ApplyRecord`, `HardDelete`, `MoveToDeletedItems`, `SoftDelete`, `Update`, `UpdateCalendarDelegation`, `UpdateFolderPermissions`, and `UpdateInboxRules`. - 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: @@ -25,24 +22,19 @@ function Test-MailboxAuditingE3 { - Condition C: The `AuditDelegate` actions do not include `ApplyRecord`, `Create`, `HardDelete`, `MoveToDeletedItems`, `SendAs`, `SendOnBehalf`, `SoftDelete`, `Update`, `UpdateFolderPermissions`, and `UpdateInboxRules`. - Condition D: The `AuditOwner` actions do not include `ApplyRecord`, `HardDelete`, `MoveToDeletedItems`, `SoftDelete`, `Update`, `UpdateCalendarDelegation`, `UpdateFolderPermissions`, and `UpdateInboxRules`. #> - # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - - $actionDictionaries = Get-Action -Dictionaries # E3 specific actions $AdminActions = $actionDictionaries.AdminActions.Keys | Where-Object { $_ -notin @("MailItemsAccessed", "Send") } $DelegateActions = $actionDictionaries.DelegateActions.Keys | Where-Object { $_ -notin @("MailItemsAccessed") } $OwnerActions = $actionDictionaries.OwnerActions.Keys | Where-Object { $_ -notin @("MailItemsAccessed", "Send") } - $allFailures = @() $recnum = "6.1.2" + Write-Verbose "Running Test-MailboxAuditingE3 for $recnum..." $allUsers = Get-CISMgOutput -Rec $recnum $processedUsers = @{} # Dictionary to track processed users - } - process { if ($null -ne $allUsers) { $mailboxes = Get-CISExoOutput -Rec $recnum @@ -52,14 +44,11 @@ function Test-MailboxAuditingE3 { Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" continue } - $userUPN = $user.UserPrincipalName $mailbox = $mailboxes | Where-Object { $_.UserPrincipalName -eq $user.UserPrincipalName } - $missingAdminActions = @() $missingDelegateActions = @() $missingOwnerActions = @() - if ($mailbox.AuditEnabled) { foreach ($action in $AdminActions) { if ($mailbox.AuditAdmin -notcontains $action) { @@ -76,7 +65,6 @@ function Test-MailboxAuditingE3 { $missingOwnerActions += (Get-Action -Actions $action -ActionType "Owner") } } - if ($missingAdminActions.Count -gt 0 -or $missingDelegateActions.Count -gt 0 -or $missingOwnerActions.Count -gt 0) { $allFailures += "$userUPN|True|$($missingAdminActions -join ',')|$($missingDelegateActions -join ',')|$($missingOwnerActions -join ',')" } @@ -84,11 +72,9 @@ function Test-MailboxAuditingE3 { else { $allFailures += "$userUPN|False|||" # Condition A for fail } - # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } - # Prepare failure reasons and details based on compliance if ($allFailures.Count -eq 0) { $failureReasons = "N/A" @@ -102,7 +88,6 @@ function Test-MailboxAuditingE3 { else { "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") } - # Populate the audit result $params = @{ Rec = $recnum @@ -115,13 +100,10 @@ function Test-MailboxAuditingE3 { } 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 } @@ -137,15 +119,12 @@ function Test-MailboxAuditingE3 { $auditResult = Initialize-CISAuditResult @params } } - end { $detailsLength = $details.Length Write-Verbose "Character count of the details: $detailsLength" - if ($detailsLength -gt 32767) { Write-Verbose "Warning: The character count exceeds the limit for Excel cells." } - return $auditResult } } diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index f0ee6be..0bc9020 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -4,11 +4,9 @@ function Test-MailboxAuditingE5 { param ( # Parameters can be added if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Conditions for 6.1.3 (L1) Ensure mailbox auditing for E5 users is Enabled # # Validate test for a pass: @@ -26,18 +24,16 @@ function Test-MailboxAuditingE5 { # - Condition B: AuditAdmin actions do not include all of the following: ApplyRecord, Create, HardDelete, MailItemsAccessed, MoveToDeletedItems, Send, SendAs, SendOnBehalf, SoftDelete, Update, UpdateCalendarDelegation, UpdateFolderPermissions, UpdateInboxRules. # - 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. - $actionDictionaries = Get-Action -Dictionaries $AdminActions = $actionDictionaries.AdminActions.Keys $DelegateActions = $actionDictionaries.DelegateActions.Keys $OwnerActions = $actionDictionaries.OwnerActions.Keys - $allFailures = @() $processedUsers = @{} $recnum = "6.1.3" + Write-Verbose "Running Test-MailboxAuditingE5 for $recnum..." $allUsers = Get-CISMgOutput -Rec $recnum } - process { if ($null -ne $allUsers) { $mailboxes = Get-CISExoOutput -Rec $recnum @@ -47,14 +43,11 @@ function Test-MailboxAuditingE5 { Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" continue } - $mailbox = $mailboxes | Where-Object { $_.UserPrincipalName -eq $user.UserPrincipalName } $userUPN = $user.UserPrincipalName - $missingAdminActions = @() $missingDelegateActions = @() $missingOwnerActions = @() - if ($mailbox.AuditEnabled) { # Validate Admin actions foreach ($action in $AdminActions) { @@ -74,7 +67,6 @@ function Test-MailboxAuditingE5 { $missingOwnerActions += (Get-Action -Actions $action -ActionType "Owner") # Condition D } } - if ($missingAdminActions.Count -gt 0 -or $missingDelegateActions.Count -gt 0 -or $missingOwnerActions.Count -gt 0) { $allFailures += "$userUPN|True|$($missingAdminActions -join ',')|$($missingDelegateActions -join ',')|$($missingOwnerActions -join ',')" } @@ -82,11 +74,9 @@ function Test-MailboxAuditingE5 { else { $allFailures += "$userUPN|False|||" # Condition A for fail } - # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } - # Prepare failure reasons and details based on compliance if ($allFailures.Count -eq 0) { $failureReasons = "N/A" @@ -113,13 +103,10 @@ function Test-MailboxAuditingE5 { } 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 } @@ -135,15 +122,12 @@ function Test-MailboxAuditingE5 { $auditResult = Initialize-CISAuditResult @params } } - end { $detailsLength = $details.Length Write-Verbose "Character count of the details: $detailsLength" - if ($detailsLength -gt 32767) { Write-Verbose "Warning: The character count exceeds the limit for Excel cells." } - return $auditResult } } \ No newline at end of file diff --git a/source/tests/Test-ModernAuthExchangeOnline.ps1 b/source/tests/Test-ModernAuthExchangeOnline.ps1 index d4cd0bc..90f82d5 100644 --- a/source/tests/Test-ModernAuthExchangeOnline.ps1 +++ b/source/tests/Test-ModernAuthExchangeOnline.ps1 @@ -5,11 +5,9 @@ function Test-ModernAuthExchangeOnline { # Aligned # Define your parameters here ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Conditions for 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled # # Validate test for a pass: @@ -25,17 +23,14 @@ function Test-ModernAuthExchangeOnline { # - Condition A: Modern authentication for Exchange Online is not enabled. # - Condition B: Exchange Online clients do not use modern authentication to log in to Microsoft 365 mailboxes. # - Condition C: Users of older email clients, such as Outlook 2013 and Outlook 2016, are still able to authenticate to Exchange using Basic Authentication. - $recnum = "6.5.1" + Write-Verbose "Running Test-ModernAuthExchangeOnline for $recnum..." } - process { try { # 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-CISExoOutput -Rec $recnum - # Prepare failure reasons and details based on compliance $failureReasons = if (-not $orgConfig.OAuth2ClientProfileEnabled) { "Modern authentication is disabled" @@ -43,10 +38,8 @@ function Test-ModernAuthExchangeOnline { else { "N/A" } - # Details include the current setting (Condition A and B) $details = "OAuth2ClientProfileEnabled: $($orgConfig.OAuth2ClientProfileEnabled) for Organization: $($orgConfig.Name)" - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -56,15 +49,12 @@ function Test-ModernAuthExchangeOnline { FailureReason = $failureReasons } $auditResult = Initialize-CISAuditResult @params - } catch { $LastError = $_ $auditResult = Get-TestError -LastError $LastError -recnum $recnum } - } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-NoWhitelistDomains.ps1 b/source/tests/Test-NoWhitelistDomains.ps1 index da28030..dc8f958 100644 --- a/source/tests/Test-NoWhitelistDomains.ps1 +++ b/source/tests/Test-NoWhitelistDomains.ps1 @@ -5,24 +5,20 @@ function Test-NoWhitelistDomains { # Aligned # Define your parameters here ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "6.2.2" - + Write-Verbose "Running Test-NoWhitelistDomains for $recnum..." <# Conditions for 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains (Automated) - 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: No mail transport rules whitelist any specific domains. - Condition B: Verification of mail transport rules using PowerShell shows no domains are whitelisted. - Condition C: Manual review in Exchange Online admin center confirms no whitelisted domains in transport rules. - 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: @@ -31,11 +27,9 @@ function Test-NoWhitelistDomains { - Condition C: Manual review in Exchange Online admin center shows whitelisted domains in transport rules. #> } - process { try { # 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 $whitelistedRules = Get-CISExoOutput -Rec $recnum @@ -47,7 +41,6 @@ function Test-NoWhitelistDomains { else { "N/A" } - # Condition C: Prepare details about the whitelisted rules if found $details = if ($whitelistedRules) { $ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') } @@ -56,7 +49,6 @@ function Test-NoWhitelistDomains { else { "No transport rules whitelisting specific domains found." } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -72,7 +64,6 @@ function Test-NoWhitelistDomains { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return auditResult return $auditResult diff --git a/source/tests/Test-NotifyMalwareInternal.ps1 b/source/tests/Test-NotifyMalwareInternal.ps1 index 380bcf5..f836b8a 100644 --- a/source/tests/Test-NotifyMalwareInternal.ps1 +++ b/source/tests/Test-NotifyMalwareInternal.ps1 @@ -5,7 +5,6 @@ function Test-NotifyMalwareInternal { # Aligned # Parameters can be added if needed ) - begin { <# # Conditions for 2.1.3 (L1) Ensure notifications for internal users sending malware is Enabled @@ -22,14 +21,12 @@ function Test-NotifyMalwareInternal { # - Condition A: Notifications for internal users sending malware are not enabled in the Microsoft 365 Security & Compliance Center. # - Condition B: Using PowerShell, the `NotifyInternal` property in the anti-malware policy is set to `False` or does not include any valid email addresses for notifications. #> - # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "2.1.3" Write-Verbose "Running Test-NotifyMalwareInternal for $recnum..." } - process { try { # 2.1.3 Ensure notifications for internal users sending malware is Enabled @@ -51,7 +48,6 @@ function Test-NotifyMalwareInternal { ) #> $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 = @() foreach ($policy in $malwareNotifications) { @@ -59,10 +55,8 @@ function Test-NotifyMalwareInternal { $policiesToReport += "$($policy.Identity): Notifications Disabled" } } - # Determine the result based on the presence of custom policies without notifications $result = $policiesToReport.Count -eq 0 - # Prepare failure reasons and details based on compliance $failureReasons = if ($result) { "N/A" @@ -71,14 +65,12 @@ function Test-NotifyMalwareInternal { # Condition A: Notifications for internal users sending malware are not enabled in the Microsoft 365 Security & Compliance Center. "Some custom policies do not have notifications for internal users sending malware enabled." } - $details = if ($result) { "All custom malware policies have notifications enabled." } else { "Misconfigured Policies: $($policiesToReport -join '`n')" } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -94,7 +86,6 @@ function Test-NotifyMalwareInternal { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-ReportSecurityInTeams.ps1 b/source/tests/Test-ReportSecurityInTeams.ps1 index 589fa6f..2080cb1 100644 --- a/source/tests/Test-ReportSecurityInTeams.ps1 +++ b/source/tests/Test-ReportSecurityInTeams.ps1 @@ -5,20 +5,17 @@ function Test-ReportSecurityInTeams { # Aligned # Parameters can be defined here if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code, if needed $recnum = "8.6.1" + Write-Verbose "Running Test-ReportSecurityInTeams for $recnum..." } - 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-CISMSTeamsOutput -Rec $recnum @@ -35,7 +32,6 @@ function Test-ReportSecurityInTeams { $null -ne $ReportSubmissionPolicy.ReportPhishAddresses -and $ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled -and -not $ReportSubmissionPolicy.ReportChatMessageEnabled - $detailsString = @" The following settings are required for users to report security concerns in Teams: @@ -75,7 +71,6 @@ ReportChatMessageToCustomizedAddressEnabled: True else { $faildetailstring } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -91,7 +86,6 @@ ReportChatMessageToCustomizedAddressEnabled: True $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-RestrictOutlookAddins.ps1 b/source/tests/Test-RestrictOutlookAddins.ps1 index 4952b24..94755d0 100644 --- a/source/tests/Test-RestrictOutlookAddins.ps1 +++ b/source/tests/Test-RestrictOutlookAddins.ps1 @@ -5,16 +5,14 @@ function Test-RestrictOutlookAddins { # Aligned # Parameters could include credentials or other necessary data ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Initialization code $defaultPolicyFailureDetails = @() $relevantRoles = @('My Custom Apps', 'My Marketplace Apps', 'My ReadWriteMailbox Apps') $recnum = "6.3.1" - + Write-Verbose "Running Test-RestrictOutlookAddins for $recnum..." # Conditions for 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed # # Validate test for a pass: @@ -29,21 +27,17 @@ function Test-RestrictOutlookAddins { # - Condition A: One or more of the roles MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are checked under Other roles. # - Condition B: Using PowerShell, verify that MyCustomApps, MyMarketplaceApps, and MyReadWriteMailboxApps are assigned to users. } - process { try { # 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed - # Check all mailboxes for custom policies with unallowed add-ins # Check Default Role Assignment Policy $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. if ($defaultPolicyRoles) { $defaultPolicyFailureDetails = $defaultPolicyRoles } - # Prepare result details string $detailsString = "" if ($customPolicyFailures) { @@ -53,7 +47,6 @@ function Test-RestrictOutlookAddins { else { $detailsString += "Custom Policy Failures: None | " } - $detailsString += "Default Role Assignment Policy: " if ($defaultPolicyFailureDetails) { $detailsString += ($defaultPolicyFailureDetails -join ', ') @@ -61,10 +54,8 @@ function Test-RestrictOutlookAddins { else { $detailsString += "Compliant" } - # Determine result based on findings $isCompliant = -not ($customPolicyFailures -or $defaultPolicyFailureDetails) - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -80,7 +71,6 @@ function Test-RestrictOutlookAddins { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 index 1db39e5..0160d82 100644 --- a/source/tests/Test-RestrictStorageProvidersOutlook.ps1 +++ b/source/tests/Test-RestrictStorageProvidersOutlook.ps1 @@ -5,7 +5,6 @@ function Test-RestrictStorageProvidersOutlook { # Aligned # Parameters can be added here if needed ) - begin { <# # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web @@ -22,25 +21,21 @@ function Test-RestrictStorageProvidersOutlook { # - Condition A: Using PowerShell, verify that `AdditionalStorageProvidersAvailable` is not set to `False` in the OwaMailboxPolicy. # - Condition B: Ensure that the command `Get-OwaMailboxPolicy | Format-Table Name, AdditionalStorageProvidersAvailable` does not return `False`. #> - # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 # Initialization code, if needed $recnum = "6.5.3" + Write-Verbose "Running Test-RestrictStorageProvidersOutlook for $recnum..." } - process { try { # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web - # Retrieve all OwaMailbox policies $owaPolicies = Get-CISExoOutput -Rec $recnum # Condition A: Check if AdditionalStorageProvidersAvailable is set to False $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } - # Determine compliance $allPoliciesRestricted = $nonCompliantPolicies.Count -eq 0 - # Prepare failure reasons and details based on compliance $failureReasons = if ($allPoliciesRestricted) { "N/A" @@ -48,14 +43,12 @@ function Test-RestrictStorageProvidersOutlook { else { "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." } - $details = if ($allPoliciesRestricted) { "All OwaMailbox policies restrict AdditionalStorageProvidersAvailable" } else { "Non-compliant OwaMailbox policies: $($nonCompliantPolicies.Name -join ', ')" } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -71,7 +64,6 @@ function Test-RestrictStorageProvidersOutlook { $auditResult = Get-TestError -LastError $LastError -recnum $recnum } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index 2065728..9337729 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -2,7 +2,6 @@ function Test-SafeAttachmentsPolicy { [CmdletBinding()] [OutputType([CISAuditResult])] param () - begin { $recnum = "2.1.4" Write-Verbose "Running Test-SafeAttachmentsPolicy for $recnum..." @@ -13,7 +12,6 @@ function Test-SafeAttachmentsPolicy { - Check if the policy's action is set to 'Block'. - Confirm the QuarantineTag is set to 'AdminOnlyAccessPolicy'. - Verify that the Redirect setting is disabled. - Validate test for a fail: - If the highest priority Safe Attachments policy's action is not set to 'Block'. - If the QuarantineTag is not set to 'AdminOnlyAccessPolicy'. @@ -55,11 +53,11 @@ function Test-SafeAttachmentsPolicy { # The result is a pass if there are no failure reasons $result = $failureReasons.Count -eq 0 $details = [PSCustomObject]@{ - Policy = $highestPriorityPolicy.Identity - Action = $highestPriorityPolicy.Action - QuarantineTag = $highestPriorityPolicy.QuarantineTag - Redirect = $highestPriorityPolicy.Redirect - Enabled = $highestPriorityPolicy.Enable + Policy = $highestPriorityPolicy.Identity + Action = $highestPriorityPolicy.Action + QuarantineTag = $highestPriorityPolicy.QuarantineTag + Redirect = $highestPriorityPolicy.Redirect + Enabled = $highestPriorityPolicy.Enable } # Format details for output manually $detailsString = "Policy|Action|QuarantineTag|Redirect|Enabled`n" + ($details | diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index c850c7e..b9f29b9 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -8,7 +8,6 @@ function Test-SafeAttachmentsTeams { begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - # Conditions for 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled # # Validate test for a pass: @@ -24,7 +23,6 @@ function Test-SafeAttachmentsTeams { # - Condition A: Safe Attachments for SharePoint is not enabled. # - Condition B: Safe Attachments for OneDrive is not enabled. # - Condition C: Safe Attachments for Microsoft Teams is not enabled. - # Initialization code, if needed $recnum = "2.1.5" Write-Verbose "Running Test-SafeAttachmentsTeams for $recnum..." diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 154a28d..d41777f 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -5,7 +5,6 @@ function Test-SafeLinksOfficeApps { # Aligned # Define your parameters here if needed ) - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 @@ -14,7 +13,6 @@ function Test-SafeLinksOfficeApps { Write-Verbose "Running Test-SafeLinksOfficeApps for $recnum..." <# Conditions for 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled - 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: @@ -25,7 +23,6 @@ function Test-SafeLinksOfficeApps { - Click protection settings: On - Do not track when users click safe links: Off - Condition B: Using the Exchange Online PowerShell Module, Safe Links policies are retrieved, and the relevant policy shows Safe Links for Office applications is enabled. - 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: @@ -38,7 +35,6 @@ function Test-SafeLinksOfficeApps { - Condition B: Using the Exchange Online PowerShell Module, Safe Links policies are retrieved, and the relevant policy shows Safe Links for Office applications is not enabled. #> } - process { # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled # Retrieve all Safe Links policies @@ -51,7 +47,6 @@ function Test-SafeLinksOfficeApps { $result = $misconfiguredDetails.Count -eq 0 $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join '`n' } $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } - # Create and populate the CISAuditResult object $params = @{ Rec = $recnum @@ -64,13 +59,10 @@ function Test-SafeLinksOfficeApps { } 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 } @@ -86,7 +78,6 @@ function Test-SafeLinksOfficeApps { $auditResult = Initialize-CISAuditResult @params } } - end { # Return the audit result return $auditResult diff --git a/source/tests/Test-SpamPolicyAdminNotify.ps1 b/source/tests/Test-SpamPolicyAdminNotify.ps1 index 5f39b31..194b441 100644 --- a/source/tests/Test-SpamPolicyAdminNotify.ps1 +++ b/source/tests/Test-SpamPolicyAdminNotify.ps1 @@ -2,7 +2,6 @@ function Test-SpamPolicyAdminNotify { [CmdletBinding()] [OutputType([CISAuditResult])] param () - begin { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 From 2c407a469c33e1b30f8dcf00b73d56763521f496 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 20:00:00 -0500 Subject: [PATCH 21/24] fix: Update main function paramter DomainName --- source/Public/Invoke-M365SecurityAudit.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/Public/Invoke-M365SecurityAudit.ps1 b/source/Public/Invoke-M365SecurityAudit.ps1 index 6d63b5b..feea04b 100644 --- a/source/Public/Invoke-M365SecurityAudit.ps1 +++ b/source/Public/Invoke-M365SecurityAudit.ps1 @@ -5,7 +5,7 @@ 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. - .PARAMETER M365DomainForPWPolicyTest + .PARAMETER DomainName 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. @@ -46,7 +46,7 @@ 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" + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -ELevel "E5" -ProfileLevel "L1" Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment. Output: @@ -62,7 +62,7 @@ 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 + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1 Performs an audit including all tests where IG1 is true. Output: @@ -78,7 +78,7 @@ 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' + PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "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 @@ -93,7 +93,7 @@ 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 = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation Captures the audit results into a variable and exports them to a CSV file. @@ -131,7 +131,7 @@ function Invoke-M365SecurityAudit { [Parameter(Mandatory = $false, HelpMessage = "Specify this to test only the default domain for password expiration policy when '1.3.1' is included in the tests to be run. The domain name of your organization, e.g., 'example.com'.")] [ValidatePattern('^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$')] - [string]$M365DomainForPWPolicyTest, + [string]$DomainName, # E-Level with optional ProfileLevel selection [Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter')] @@ -286,7 +286,7 @@ function Invoke-M365SecurityAudit { 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 + $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $DomainName # Add the result to the collection [void]$allAuditResults.Add($auditResult) } From 338ed4e871b4d9aa6f2c85c81b26c8946c5c4ab5 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 20:00:46 -0500 Subject: [PATCH 22/24] docs: Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f600e29..6f5cf4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +### Added + +- Added `Get-PhishPolicyDetail` and `Test-PhishPolicyCompliance` private functions to help test for phishing policy compliance. + +### Fixed + +- Fixed various EXO test to be more accurate and include more properties in the output. + +#### Changed + +- Changed main function parameter for Domain to `DomainName`. + + + +## [0.1.17] - 2024-06-28 + ### Fixed - Fixed `Get-ExceededLengthResultDetail` function paramter validation for Exported Tests to allow for Null. From aae91a5a195371ec334364c6e3a22b4e496fd1f2 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 20:03:33 -0500 Subject: [PATCH 23/24] docs: Update README/HTML HELP --- README.md | Bin 20489 -> 41524 bytes docs/index.html | Bin 54847 -> 111548 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index 8e92751f4322abc42d5ca8d5b02a76243719e285..d1fb1899666265927ce0f64e6719ce96b9eac202 100644 GIT binary patch literal 41524 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

9{4hm-@X9F-8?C8t#1& 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 KdB^C_d;bP8W0hL~ diff --git a/docs/index.html b/docs/index.html index 27236b2e47a28c7ddde385fa1c5402281f910ef2..f940d701f8f759fb9b5ef5ec6c8f6dcaedf1c51a 100644 GIT binary patch literal 111548 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 ZuJ?P1oc99ffd=hgp(Xb9Q_bC1{y(Hkx<&v1 literal 54847 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 UW4ZVPmx7ENMSej#fRXC|0dan-A^-pY From 5f4df00ceaa746288ab4d54fa126717bdddd3037 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Sat, 29 Jun 2024 20:05:04 -0500 Subject: [PATCH 24/24] docs: Update Help/WikiFiles --- help/Export-M365SecurityAuditTable.md | 90 +- help/Get-AdminRoleUserLicense.md | 17 +- help/Get-MFAStatus.md | 32 +- help/Grant-M365SecurityAuditConsent.md | 68 +- help/Invoke-M365SecurityAudit.md | 194 ++- help/M365FoundationsCISReport.md | 5 +- help/Remove-RowsWithEmptyCSVStatus.md | 18 +- help/Sync-CISExcelAndCsvData.md | 31 +- .../en-US/M365FoundationsCISReport-help.xml | 1356 +++++++---------- 9 files changed, 718 insertions(+), 1093 deletions(-) diff --git a/help/Export-M365SecurityAuditTable.md b/help/Export-M365SecurityAuditTable.md index a2a7ba0..858ea30 100644 --- a/help/Export-M365SecurityAuditTable.md +++ b/help/Export-M365SecurityAuditTable.md @@ -1,4 +1,4 @@ ---- +--- external help file: M365FoundationsCISReport-help.xml Module Name: M365FoundationsCISReport online version: https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Export-M365SecurityAuditTable @@ -15,25 +15,24 @@ Exports M365 security audit results to a CSV file or outputs a specific test res ### OutputObjectFromAuditResultsSingle ``` Export-M365SecurityAuditTable [-AuditResults] [-OutputTestNumber] - [-ProgressAction ] [] + [] ``` ### ExportAllResultsFromAuditResults ``` Export-M365SecurityAuditTable [-AuditResults] [-ExportAllTests] -ExportPath - [-ExportOriginalTests] [-ExportToExcel] [-ProgressAction ] [] + [-ExportOriginalTests] [-ExportToExcel] [] ``` ### OutputObjectFromCsvSingle ``` -Export-M365SecurityAuditTable [-CsvPath] [-OutputTestNumber] - [-ProgressAction ] [] +Export-M365SecurityAuditTable [-CsvPath] [-OutputTestNumber] [] ``` ### ExportAllResultsFromCsv ``` Export-M365SecurityAuditTable [-CsvPath] [-ExportAllTests] -ExportPath [-ExportOriginalTests] - [-ExportToExcel] [-ProgressAction ] [] + [-ExportToExcel] [] ``` ## DESCRIPTION @@ -110,6 +109,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -OutputTestNumber +The test number to output as an object. +Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". + +```yaml +Type: String +Parameter Sets: OutputObjectFromAuditResultsSingle, OutputObjectFromCsvSingle +Aliases: + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -ExportAllTests Switch to export all test results. @@ -118,23 +133,8 @@ Type: SwitchParameter Parameter Sets: ExportAllResultsFromAuditResults, ExportAllResultsFromCsv Aliases: -Required: True -Position: 1 -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ExportOriginalTests -Switch to export the original audit results to a CSV file. - -```yaml -Type: SwitchParameter -Parameter Sets: ExportAllResultsFromAuditResults, ExportAllResultsFromCsv -Aliases: - Required: False -Position: Named +Position: 1 Default value: False Accept pipeline input: False Accept wildcard characters: False @@ -155,6 +155,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ExportOriginalTests +Switch to export the original audit results to a CSV file. + +```yaml +Type: SwitchParameter +Parameter Sets: ExportAllResultsFromAuditResults, ExportAllResultsFromCsv +Aliases: + +Required: True +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -ExportToExcel Switch to export the results to an Excel file. @@ -170,37 +185,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -OutputTestNumber -The test number to output as an object. -Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". - -```yaml -Type: String -Parameter Sets: OutputObjectFromAuditResultsSingle, OutputObjectFromCsvSingle -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/help/Get-AdminRoleUserLicense.md b/help/Get-AdminRoleUserLicense.md index 22625bb..0100860 100644 --- a/help/Get-AdminRoleUserLicense.md +++ b/help/Get-AdminRoleUserLicense.md @@ -13,7 +13,7 @@ Retrieves user licenses and roles for administrative accounts from Microsoft 365 ## SYNTAX ``` -Get-AdminRoleUserLicense [-SkipGraphConnection] [-ProgressAction ] [] +Get-AdminRoleUserLicense [-SkipGraphConnection] [] ``` ## DESCRIPTION @@ -38,21 +38,6 @@ This example retrieves all administrative role users along with their licenses w ## PARAMETERS -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -SkipGraphConnection A switch parameter that, when set, skips the connection to Microsoft Graph if already established. This is useful for batch processing or when used within scripts where multiple calls are made and the connection is managed externally. diff --git a/help/Get-MFAStatus.md b/help/Get-MFAStatus.md index 9e9b878..c64235b 100644 --- a/help/Get-MFAStatus.md +++ b/help/Get-MFAStatus.md @@ -1,4 +1,4 @@ ---- +--- external help file: M365FoundationsCISReport-help.xml Module Name: M365FoundationsCISReport online version: https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-MFAStatus @@ -13,8 +13,7 @@ Retrieves the MFA (Multi-Factor Authentication) status for Azure Active Director ## SYNTAX ``` -Get-MFAStatus [[-UserId] ] [-SkipMSOLConnectionChecks] [-ProgressAction ] - [] +Get-MFAStatus [[-UserId] ] [-SkipMSOLConnectionChecks] [] ``` ## DESCRIPTION @@ -37,16 +36,17 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com ## PARAMETERS -### -ProgressAction -{{ Fill ProgressAction Description }} +### -UserId +The User Principal Name (UPN) of a specific user to retrieve MFA status for. +If not provided, the function retrieves MFA status for all users. ```yaml -Type: ActionPreference +Type: String Parameter Sets: (All) -Aliases: proga +Aliases: Required: False -Position: Named +Position: 1 Default value: None Accept pipeline input: False Accept wildcard characters: False @@ -67,22 +67,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -UserId -The User Principal Name (UPN) of a specific user to retrieve MFA status for. -If not provided, the function retrieves MFA status for all users. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/help/Grant-M365SecurityAuditConsent.md b/help/Grant-M365SecurityAuditConsent.md index e2c864c..c5c5564 100644 --- a/help/Grant-M365SecurityAuditConsent.md +++ b/help/Grant-M365SecurityAuditConsent.md @@ -14,8 +14,7 @@ Grants Microsoft Graph permissions for an auditor. ``` Grant-M365SecurityAuditConsent [-UserPrincipalNameForConsent] [-SkipGraphConnection] - [-SkipModuleCheck] [-SuppressRevertOutput] [-DoNotDisconnect] [-ProgressAction ] [-WhatIf] - [-Confirm] [] + [-SkipModuleCheck] [-SuppressRevertOutput] [-DoNotDisconnect] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -41,33 +40,18 @@ Grants Microsoft Graph permissions to user@example.com, skipping the connection ## PARAMETERS -### -DoNotDisconnect -If specified, does not disconnect from Microsoft Graph after granting consent. +### -UserPrincipalNameForConsent +Specify the UPN of the user to grant consent for. ```yaml -Type: SwitchParameter +Type: String Parameter Sets: (All) Aliases: -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named +Required: True +Position: 1 Default value: None -Accept pipeline input: False +Accept pipeline input: True (ByPropertyName, ByValue) Accept wildcard characters: False ``` @@ -116,32 +100,17 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -UserPrincipalNameForConsent -Specify the UPN of the user to grant consent for. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: True (ByPropertyName, ByValue) -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. +### -DoNotDisconnect +If specified, does not disconnect from Microsoft Graph after granting consent. ```yaml Type: SwitchParameter Parameter Sets: (All) -Aliases: cf +Aliases: Required: False Position: Named -Default value: None +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` @@ -162,6 +131,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/help/Invoke-M365SecurityAudit.md b/help/Invoke-M365SecurityAudit.md index 706ab6f..21ad90a 100644 --- a/help/Invoke-M365SecurityAudit.md +++ b/help/Invoke-M365SecurityAudit.md @@ -1,4 +1,4 @@ ---- +--- external help file: M365FoundationsCISReport-help.xml Module Name: M365FoundationsCISReport online version: https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit @@ -14,51 +14,47 @@ Invokes a security audit for Microsoft 365 environments. ### Default (Default) ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] [-DoNotConnect] - [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-ProgressAction ] [-WhatIf] - [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] [-DoNotConnect] [-DoNotDisconnect] + [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] [] ``` ### ELevelFilter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] -ELevel +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] -ELevel -ProfileLevel [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] - [-ProgressAction ] [-WhatIf] [-Confirm] [] + [-WhatIf] [-Confirm] [] ``` ### IG1Filter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] [-IncludeIG1] - [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] - [-ProgressAction ] [-WhatIf] [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] [-IncludeIG1] [-DoNotConnect] + [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] [] ``` ### IG2Filter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] [-IncludeIG2] - [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] - [-ProgressAction ] [-WhatIf] [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] [-IncludeIG2] [-DoNotConnect] + [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] [] ``` ### IG3Filter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] [-IncludeIG3] - [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] - [-ProgressAction ] [-WhatIf] [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] [-IncludeIG3] [-DoNotConnect] + [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] [] ``` ### RecFilter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] - -IncludeRecommendation [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] - [-DoNotConfirmConnections] [-ProgressAction ] [-WhatIf] [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] -IncludeRecommendation + [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] + [] ``` ### SkipRecFilter ``` -Invoke-M365SecurityAudit [-TenantAdminUrl ] [-M365DomainForPWPolicyTest ] - -SkipRecommendation [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] - [-ProgressAction ] [-WhatIf] [-Confirm] [] +Invoke-M365SecurityAudit [-TenantAdminUrl ] [-DomainName ] -SkipRecommendation + [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-DoNotConfirmConnections] [-WhatIf] [-Confirm] + [] ``` ## DESCRIPTION @@ -164,47 +160,33 @@ What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsof ## PARAMETERS -### -DoNotConfirmConnections -If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. +### -TenantAdminUrl +The URL of the tenant admin. +If not specified, none of the SharePoint Online tests will run. ```yaml -Type: SwitchParameter +Type: String Parameter Sets: (All) Aliases: Required: False Position: Named -Default value: False +Default value: None Accept pipeline input: False Accept wildcard characters: False ``` -### -DoNotConnect -If specified, the cmdlet will not establish a connection to Microsoft 365 services. +### -DomainName +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. ```yaml -Type: SwitchParameter +Type: String Parameter Sets: (All) Aliases: Required: False Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -DoNotDisconnect -If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False +Default value: None Accept pipeline input: False Accept wildcard characters: False ``` @@ -225,6 +207,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ProfileLevel +Specifies the profile level (L1 or L2) for the audit. +This parameter is optional and can be combined with the ELevel parameter. + +```yaml +Type: String +Parameter Sets: ELevelFilter +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -IncludeIG1 If specified, includes tests where IG1 is true. @@ -286,18 +284,48 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -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. +### -SkipRecommendation +Specifies specific recommendations to exclude from the audit. +Accepts an array of recommendation numbers. ```yaml -Type: String +Type: String[] +Parameter Sets: SkipRecFilter +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DoNotConnect +If specified, the cmdlet will not establish a connection to Microsoft 365 services. + +```yaml +Type: SwitchParameter Parameter Sets: (All) Aliases: Required: False Position: Named -Default value: None +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DoNotDisconnect +If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` @@ -317,61 +345,29 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -ProfileLevel -Specifies the profile level (L1 or L2) for the audit. -This parameter is optional and can be combined with the ELevel parameter. +### -DoNotConfirmConnections +If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. ```yaml -Type: String -Parameter Sets: ELevelFilter -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference +Type: SwitchParameter Parameter Sets: (All) -Aliases: proga +Aliases: Required: False Position: Named -Default value: None +Default value: False Accept pipeline input: False Accept wildcard characters: False ``` -### -SkipRecommendation -Specifies specific recommendations to exclude from the audit. -Accepts an array of recommendation numbers. +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. ```yaml -Type: String[] -Parameter Sets: SkipRecFilter -Aliases: - -Required: True -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -TenantAdminUrl -The URL of the tenant admin. -If not specified, none of the SharePoint Online tests will run. - -```yaml -Type: String +Type: SwitchParameter Parameter Sets: (All) -Aliases: +Aliases: wi Required: False Position: Named @@ -395,22 +391,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/help/M365FoundationsCISReport.md b/help/M365FoundationsCISReport.md index 267b0ab..3c8e111 100644 --- a/help/M365FoundationsCISReport.md +++ b/help/M365FoundationsCISReport.md @@ -1,4 +1,4 @@ ---- +--- Module Name: M365FoundationsCISReport Module Guid: 0d064bfb-d1ce-484b-a173-993b55984dc9 Download Help Link: {{Please enter Link manually}} @@ -20,6 +20,9 @@ Retrieves user licenses and roles for administrative accounts from Microsoft 365 ### [Get-MFAStatus](Get-MFAStatus.md) Retrieves the MFA (Multi-Factor Authentication) status for Azure Active Directory users. +### [Grant-M365SecurityAuditConsent](Grant-M365SecurityAuditConsent.md) +Grants Microsoft Graph permissions for an auditor. + ### [Invoke-M365SecurityAudit](Invoke-M365SecurityAudit.md) Invokes a security audit for Microsoft 365 environments. diff --git a/help/Remove-RowsWithEmptyCSVStatus.md b/help/Remove-RowsWithEmptyCSVStatus.md index eca797b..4b0f57d 100644 --- a/help/Remove-RowsWithEmptyCSVStatus.md +++ b/help/Remove-RowsWithEmptyCSVStatus.md @@ -13,8 +13,7 @@ Removes rows from an Excel worksheet where the 'CSV_Status' column is empty and ## SYNTAX ``` -Remove-RowsWithEmptyCSVStatus [-FilePath] [-WorksheetName] - [-ProgressAction ] [] +Remove-RowsWithEmptyCSVStatus [-FilePath] [-WorksheetName] [] ``` ## DESCRIPTION @@ -46,21 +45,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -WorksheetName The name of the worksheet within the Excel file to be processed. diff --git a/help/Sync-CISExcelAndCsvData.md b/help/Sync-CISExcelAndCsvData.md index 663e3e4..758f362 100644 --- a/help/Sync-CISExcelAndCsvData.md +++ b/help/Sync-CISExcelAndCsvData.md @@ -1,4 +1,4 @@ ---- +--- external help file: M365FoundationsCISReport-help.xml Module Name: M365FoundationsCISReport online version: https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Sync-CISExcelAndCsvData @@ -14,7 +14,7 @@ Synchronizes and updates data in an Excel worksheet with new information from a ``` Sync-CISExcelAndCsvData [[-ExcelPath] ] [[-CsvPath] ] [[-SheetName] ] - [-ProgressAction ] [] + [] ``` ## DESCRIPTION @@ -32,22 +32,6 @@ Updates the 'AuditData' worksheet in 'excel.xlsx' with data from 'data.csv', add ## PARAMETERS -### -CsvPath -Specifies the path to the CSV file containing new data. -This parameter is mandatory. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -ExcelPath Specifies the path to the Excel file to be updated. This parameter is mandatory. @@ -64,16 +48,17 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -ProgressAction -{{ Fill ProgressAction Description }} +### -CsvPath +Specifies the path to the CSV file containing new data. +This parameter is mandatory. ```yaml -Type: ActionPreference +Type: String Parameter Sets: (All) -Aliases: proga +Aliases: Required: False -Position: Named +Position: 2 Default value: None Accept pipeline input: False Accept wildcard characters: False diff --git a/source/en-US/M365FoundationsCISReport-help.xml b/source/en-US/M365FoundationsCISReport-help.xml index 4ea02c5..507ad97 100644 --- a/source/en-US/M365FoundationsCISReport-help.xml +++ b/source/en-US/M365FoundationsCISReport-help.xml @@ -39,22 +39,10 @@ None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - Export-M365SecurityAuditTable - + ExportAllTests Switch to export all test results. @@ -77,17 +65,6 @@ None - - ExportOriginalTests - - Switch to export the original audit results to a CSV file. - - - SwitchParameter - - - False - ExportPath @@ -100,6 +77,17 @@ None + + ExportOriginalTests + + Switch to export the original audit results to a CSV file. + + + SwitchParameter + + + False + ExportToExcel @@ -111,18 +99,6 @@ False - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - Export-M365SecurityAuditTable @@ -150,22 +126,10 @@ None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - Export-M365SecurityAuditTable - + ExportAllTests Switch to export all test results. @@ -188,17 +152,6 @@ None - - ExportOriginalTests - - Switch to export the original audit results to a CSV file. - - - SwitchParameter - - - False - ExportPath @@ -211,6 +164,17 @@ None + + ExportOriginalTests + + Switch to export the original audit results to a CSV file. + + + SwitchParameter + + + False + ExportToExcel @@ -222,18 +186,6 @@ False - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - @@ -261,7 +213,19 @@ None - + + OutputTestNumber + + The test number to output as an object. Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". + + String + + String + + + None + + ExportAllTests Switch to export all test results. @@ -273,18 +237,6 @@ False - - ExportOriginalTests - - Switch to export the original audit results to a CSV file. - - SwitchParameter - - SwitchParameter - - - False - ExportPath @@ -297,6 +249,18 @@ None + + ExportOriginalTests + + Switch to export the original audit results to a CSV file. + + SwitchParameter + + SwitchParameter + + + False + ExportToExcel @@ -309,30 +273,6 @@ False - - OutputTestNumber - - The test number to output as an object. Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". - - String - - String - - - None - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - @@ -431,18 +371,6 @@ Get-AdminRoleUserLicense - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - SkipGraphConnection @@ -457,18 +385,6 @@ - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - SkipGraphConnection @@ -565,18 +481,6 @@ None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - SkipMSOLConnectionChecks @@ -591,14 +495,14 @@ - - ProgressAction + + UserId - {{ Fill ProgressAction Description }} + The User Principal Name (UPN) of a specific user to retrieve MFA status for. If not provided, the function retrieves MFA status for all users. - ActionPreference + String - ActionPreference + String None @@ -615,18 +519,6 @@ False - - UserId - - The User Principal Name (UPN) of a specific user to retrieve MFA status for. If not provided, the function retrieves MFA status for all users. - - String - - String - - - None - @@ -760,29 +652,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - - DoNotDisconnect - - If specified, does not disconnect from Microsoft Graph after granting consent. - - - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - SkipGraphConnection @@ -816,10 +685,10 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - Confirm + + DoNotDisconnect - Prompts you for confirmation before running the cmdlet. + If specified, does not disconnect from Microsoft Graph after granting consent. SwitchParameter @@ -838,29 +707,28 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + - - DoNotDisconnect + + UserPrincipalNameForConsent - If specified, does not disconnect from Microsoft Graph after granting consent. + Specify the UPN of the user to grant consent for. - SwitchParameter + String - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference + String None @@ -901,22 +769,10 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - UserPrincipalNameForConsent + + DoNotDisconnect - Specify the UPN of the user to grant consent for. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. + If specified, does not disconnect from Microsoft Graph after granting consent. SwitchParameter @@ -937,6 +793,18 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + Confirm + + Prompts you for confirmation before running the cmdlet. + + SwitchParameter + + SwitchParameter + + + False + @@ -993,37 +861,28 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com Invoke-M365SecurityAudit - DoNotConfirmConnections + TenantAdminUrl - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + String - SwitchParameter + String - False + None - DoNotConnect + DomainName - If specified, the cmdlet will not establish a connection to Microsoft 365 services. + 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. + String - SwitchParameter + String - False - - - DoNotDisconnect - - If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. - - - SwitchParameter - - - False + None ELevel @@ -1037,29 +896,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - - SwitchParameter - - - False - ProfileLevel @@ -1072,66 +908,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - TenantAdminUrl - - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. - - - SwitchParameter - - - False - - - WhatIf - - Shows what would happen if the cmdlet runs. The cmdlet is not run. - - - SwitchParameter - - - False - - - - Invoke-M365SecurityAudit - - DoNotConfirmConnections - - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - - - SwitchParameter - - - False - DoNotConnect @@ -1154,6 +930,77 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + + + SwitchParameter + + + False + + + WhatIf + + Shows what would happen if the cmdlet runs. The cmdlet is not run. + + + SwitchParameter + + + False + + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + + + + Invoke-M365SecurityAudit + + TenantAdminUrl + + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + + String + + String + + + None + + + DomainName + + 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. + + String + + String + + + None + IncludeIG1 @@ -1165,89 +1012,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - TenantAdminUrl - - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. - - - SwitchParameter - - - False - - - WhatIf - - Shows what would happen if the cmdlet runs. The cmdlet is not run. - - - SwitchParameter - - - False - - - - Invoke-M365SecurityAudit - - DoNotConfirmConnections - - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - - - SwitchParameter - - - False - DoNotConnect @@ -1270,6 +1034,77 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + + + SwitchParameter + + + False + + + WhatIf + + Shows what would happen if the cmdlet runs. The cmdlet is not run. + + + SwitchParameter + + + False + + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + + + + Invoke-M365SecurityAudit + + TenantAdminUrl + + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + + String + + String + + + None + + + DomainName + + 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. + + String + + String + + + None + IncludeIG2 @@ -1281,89 +1116,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - TenantAdminUrl - - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. - - - SwitchParameter - - - False - - - WhatIf - - Shows what would happen if the cmdlet runs. The cmdlet is not run. - - - SwitchParameter - - - False - - - - Invoke-M365SecurityAudit - - DoNotConfirmConnections - - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - - - SwitchParameter - - - False - DoNotConnect @@ -1386,6 +1138,77 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + + + SwitchParameter + + + False + + + WhatIf + + Shows what would happen if the cmdlet runs. The cmdlet is not run. + + + SwitchParameter + + + False + + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + + + + Invoke-M365SecurityAudit + + TenantAdminUrl + + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + + String + + String + + + None + + + DomainName + + 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. + + String + + String + + + None + IncludeIG3 @@ -1397,89 +1220,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - TenantAdminUrl - - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. - - - SwitchParameter - - - False - - - WhatIf - - Shows what would happen if the cmdlet runs. The cmdlet is not run. - - - SwitchParameter - - - False - - - - Invoke-M365SecurityAudit - - DoNotConfirmConnections - - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - - - SwitchParameter - - - False - DoNotConnect @@ -1502,6 +1242,77 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + + + SwitchParameter + + + False + + + WhatIf + + Shows what would happen if the cmdlet runs. The cmdlet is not run. + + + SwitchParameter + + + False + + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + + + + Invoke-M365SecurityAudit + + TenantAdminUrl + + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + + String + + String + + + None + + + DomainName + + 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. + + String + + String + + + None + IncludeRecommendation @@ -1514,89 +1325,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - - SwitchParameter - - - False - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - TenantAdminUrl - - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - - String - - String - - - None - - - Confirm - - Prompts you for confirmation before running the cmdlet. - - - SwitchParameter - - - False - - - WhatIf - - Shows what would happen if the cmdlet runs. The cmdlet is not run. - - - SwitchParameter - - - False - - - - Invoke-M365SecurityAudit - - DoNotConfirmConnections - - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - - - SwitchParameter - - - False - DoNotConnect @@ -1619,18 +1347,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - 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. - - String - - String - - - None - NoModuleCheck @@ -1642,14 +1358,62 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False - - ProgressAction + + DoNotConfirmConnections - {{ Fill ProgressAction Description }} + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. - ActionPreference - ActionPreference + SwitchParameter + + + False + + + WhatIf + + Shows what would happen if the cmdlet runs. The cmdlet is not run. + + + SwitchParameter + + + False + + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + + + + Invoke-M365SecurityAudit + + TenantAdminUrl + + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + + String + + String + + + None + + + DomainName + + 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. + + String + + String None @@ -1667,21 +1431,42 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - TenantAdminUrl + DoNotConnect - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + If specified, the cmdlet will not establish a connection to Microsoft 365 services. - String - String + SwitchParameter - None + False - - Confirm + + DoNotDisconnect - Prompts you for confirmation before running the cmdlet. + If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. + + + SwitchParameter + + + False + + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. SwitchParameter @@ -1700,44 +1485,43 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + Confirm + + Prompts you for confirmation before running the cmdlet. + + + SwitchParameter + + + False + - DoNotConfirmConnections + TenantAdminUrl - If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. + The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. - SwitchParameter + String - SwitchParameter + String - False + None - DoNotConnect + DomainName - If specified, the cmdlet will not establish a connection to Microsoft 365 services. + 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. - SwitchParameter + String - SwitchParameter + String - False - - - DoNotDisconnect - - If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. - - SwitchParameter - - SwitchParameter - - - False + None ELevel @@ -1751,6 +1535,18 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None + + ProfileLevel + + Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter. + + String + + String + + + None + IncludeIG1 @@ -1799,54 +1595,6 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - - 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. - - String - - String - - - None - - - NoModuleCheck - - If specified, the cmdlet will not check for the presence of required modules. - - SwitchParameter - - SwitchParameter - - - False - - - ProfileLevel - - Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter. - - String - - String - - - None - - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - SkipRecommendation @@ -1860,21 +1608,45 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com None - TenantAdminUrl + DoNotConnect - The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run. + If specified, the cmdlet will not establish a connection to Microsoft 365 services. - String + SwitchParameter - String + SwitchParameter - None + False - - Confirm + + DoNotDisconnect - Prompts you for confirmation before running the cmdlet. + If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. + + SwitchParameter + + SwitchParameter + + + False + + + NoModuleCheck + + If specified, the cmdlet will not check for the presence of required modules. + + SwitchParameter + + SwitchParameter + + + False + + + DoNotConfirmConnections + + If specified, the cmdlet will not prompt for confirmation before proceeding with established connections and will disconnect from all of them. SwitchParameter @@ -1895,6 +1667,18 @@ Retrieves the MFA status for the specified user with the UPN "example@domain.com False + + Confirm + + Prompts you for confirmation before running the cmdlet. + + SwitchParameter + + SwitchParameter + + + False + @@ -2038,18 +1822,6 @@ PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - @@ -2065,18 +1837,6 @@ PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - WorksheetName @@ -2160,33 +1920,9 @@ This command imports data from the "Sheet1" worksheet in the "Report.xlsx" file, None - - ProgressAction - - {{ Fill ProgressAction Description }} - - ActionPreference - - ActionPreference - - - None - - - CsvPath - - Specifies the path to the CSV file containing new data. This parameter is mandatory. - - String - - String - - - None - ExcelPath @@ -2199,14 +1935,14 @@ This command imports data from the "Sheet1" worksheet in the "Report.xlsx" file, None - - ProgressAction + + CsvPath - {{ Fill ProgressAction Description }} + Specifies the path to the CSV file containing new data. This parameter is mandatory. - ActionPreference + String - ActionPreference + String None