From 0cde0ae5e2b3f19249845acea6fbdf4d28fb07e1 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:22:38 -0500 Subject: [PATCH 01/13] docs: 6.1.2,6.1.3 refactored --- helpers/Build-Help.ps1 | 2 +- source/Private/Connect-M365Suite.ps1 | 4 +- source/helper/TestDefinitions.csv | 4 +- source/tests/Test-MailboxAuditingE3.ps1 | 89 ++++++++++++++----------- source/tests/Test-MailboxAuditingE5.ps1 | 88 +++++++++++++----------- 5 files changed, 106 insertions(+), 81 deletions(-) diff --git a/helpers/Build-Help.ps1 b/helpers/Build-Help.ps1 index bebbbf8..fe923fc 100644 --- a/helpers/Build-Help.ps1 +++ b/helpers/Build-Help.ps1 @@ -4,7 +4,7 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1 <# - $ver = "v0.1.9" + $ver = "v0.1.10" git checkout main git pull origin main git tag -a $ver -m "Release version $ver refactor Update" diff --git a/source/Private/Connect-M365Suite.ps1 b/source/Private/Connect-M365Suite.ps1 index 1d57549..04fe394 100644 --- a/source/Private/Connect-M365Suite.ps1 +++ b/source/Private/Connect-M365Suite.ps1 @@ -18,7 +18,7 @@ function Connect-M365Suite { Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green } - if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { + if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "EXO | Microsoft Graph") { Write-Host "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Cyan try { Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null @@ -31,7 +31,7 @@ function Connect-M365Suite { } } - if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { + if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "EXO | Microsoft Graph") { Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan Connect-ExchangeOnline | Out-Null Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green diff --git a/source/helper/TestDefinitions.csv b/source/helper/TestDefinitions.csv index 63434ec..d3f4e8c 100644 --- a/source/helper/TestDefinitions.csv +++ b/source/helper/TestDefinitions.csv @@ -18,8 +18,8 @@ 17,Test-RestrictTenantCreation.ps1,5.1.2.3,Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes',E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,Microsoft Graph 18,Test-PasswordHashSync.ps1,5.1.8.1,Ensure password hash sync is enabled for hybrid deployments,E3,L1,6.7,Centralize Access Control,FALSE,TRUE,TRUE,TRUE,Microsoft Graph 19,Test-AuditDisabledFalse.ps1,6.1.1,Ensure 'AuditDisabled' organizationally is set to 'False',E3,L1,8.2,Collect Audit Logs,TRUE,TRUE,TRUE,TRUE,Microsoft Graph -20,Test-MailboxAuditingE3.ps1,6.1.2,Ensure mailbox auditing for Office E3 users is Enabled,E3,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO | Microsoft Graph -21,Test-MailboxAuditingE5.ps1,6.1.3,Ensure mailbox auditing for Office E5 users is Enabled,E5,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,AzureAD | EXO | Microsoft Graph +20,Test-MailboxAuditingE3.ps1,6.1.2,Ensure mailbox auditing for Office E3 users is Enabled,E3,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,EXO | Microsoft Graph +21,Test-MailboxAuditingE5.ps1,6.1.3,Ensure mailbox auditing for Office E5 users is Enabled,E5,L1,8.2,Collect audit logs.,TRUE,TRUE,TRUE,TRUE,EXO | Microsoft Graph 22,Test-BlockMailForwarding.ps1,6.2.1,Ensure all forms of mail forwarding are blocked and/or disabled,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO 23,Test-NoWhitelistDomains.ps1,6.2.2,Ensure mail transport rules do not whitelist specific domains,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO 24,Test-IdentifyExternalEmail.ps1,6.2.3,Ensure email from external senders is identified,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,EXO diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 010b049..59ead24 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -29,30 +29,33 @@ function Test-MailboxAuditingE3 { # Dot source the class script if necessary #. .\source\Classes\CISAuditResult.ps1 - $e3SkuPartNumbers = @("ENTERPRISEPACK", "OFFICESUBSCRIPTION") + $e3SkuPartNumber = "SPE_E3" $AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") $DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules") $OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") $allFailures = @() - $allUsers = Get-AzureADUser -All $true + #$allUsers = Get-AzureADUser -All $true + $founde3Sku = Get-MgSubscribedSku -All | Where-Object {$_.SkuPartNumber -eq $e3SkuPartNumber} $processedUsers = @{} # Dictionary to track processed users $recnum = "6.1.2" } + process { - try { - foreach ($user in $allUsers) { - if ($processedUsers.ContainsKey($user.UserPrincipalName)) { - Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" - continue - } + if ($null -ne $founde3Sku) { + $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde3Sku.SkuId) )" -All + try { + foreach ($user in $allUsers) { + if ($processedUsers.ContainsKey($user.UserPrincipalName)) { + Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" + continue + } - $licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName - $hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0 - Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license." + #$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName + #$hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0 + #Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license." - if ($hasOfficeE3) { $userUPN = $user.UserPrincipalName $mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit @@ -84,39 +87,49 @@ function Test-MailboxAuditingE3 { # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } - } - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } - $details = if ($allFailures.Count -eq 0) { - "All Office E3 users have correct mailbox audit settings." - } - else { - "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } + $details = if ($allFailures.Count -eq 0) { + "All Office E3 users have correct mailbox audit settings." + } + else { + "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") + } - # Populate the audit result + # Populate the audit result + $params = @{ + Rec = $recnum + Result = $allFailures.Count -eq 0 + Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } + } + else { $params = @{ Rec = $recnum - Result = $allFailures.Count -eq 0 - Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $false + Status = "Fail" + Details = "No M365 E3 licenses found." + FailureReason = "The audit is for M365 E3 licenses, but no such licenses were found." } $auditResult = Initialize-CISAuditResult @params } - catch { - Write-Error "An error occurred during the test: $_" - - # Retrieve the description from the test definitions - $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } - $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - - $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - - # Call Initialize-CISAuditResult with error parameters - $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - } } end { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index f5c3891..62ec0cb 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -27,30 +27,32 @@ function Test-MailboxAuditingE5 { # - Condition C: AuditDelegate actions do not include all of the following: ApplyRecord, Create, HardDelete, MailItemsAccessed, MoveToDeletedItems, SendAs, SendOnBehalf, SoftDelete, Update, UpdateFolderPermissions, UpdateInboxRules. # - Condition D: AuditOwner actions do not include all of the following: ApplyRecord, HardDelete, MailItemsAccessed, MoveToDeletedItems, Send, SoftDelete, Update, UpdateCalendarDelegation, UpdateFolderPermissions, UpdateInboxRules. - $e5SkuPartNumbers = @("SPE_E5", "ENTERPRISEPREMIUM", "OFFICEE5") + $e5SkuPartNumber = "SPE_E5" $AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") $DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules") $OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules") $allFailures = @() - $allUsers = Get-AzureADUser -All $true + #$allUsers = Get-AzureADUser -All $true + $founde5Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber } $processedUsers = @{} # Dictionary to track processed users $recnum = "6.1.3" } process { - try { - foreach ($user in $allUsers) { - if ($processedUsers.ContainsKey($user.UserPrincipalName)) { - Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" - continue - } + if ($null -ne $founde5Sku) { + $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde5Sku.SkuId) )" -All + try { + foreach ($user in $allUsers) { + if ($processedUsers.ContainsKey($user.UserPrincipalName)) { + Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)" + continue + } - $licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName - $hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0 - Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license." + #$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName + #$hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0 + #Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license." - if ($hasOfficeE5) { $userUPN = $user.UserPrincipalName $mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit @@ -81,39 +83,49 @@ function Test-MailboxAuditingE5 { # Mark the user as processed $processedUsers[$user.UserPrincipalName] = $true } - } - # Prepare failure reasons and details based on compliance - $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } - $details = if ($allFailures.Count -eq 0) { - "All Office E5 users have correct mailbox audit settings." # Condition A for pass - } - else { - "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") # Condition A for fail - } + # Prepare failure reasons and details based on compliance + $failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." } + $details = if ($allFailures.Count -eq 0) { + "All Office E5 users have correct mailbox audit settings." # Condition A for pass + } + else { + "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing`n" + ($allFailures -join "`n") # Condition A for fail + } - # Populate the audit result + # Populate the audit result + $params = @{ + Rec = $recnum + Result = $allFailures.Count -eq 0 + Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params + } + catch { + Write-Error "An error occurred during the test: $_" + + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } + + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } + } + else { $params = @{ Rec = $recnum - Result = $allFailures.Count -eq 0 - Status = if ($allFailures.Count -eq 0) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $false + Status = "Fail" + Details = "No M365 E5 licenses found." + FailureReason = "The audit is for M365 E5 licenses, but no such licenses were found." } $auditResult = Initialize-CISAuditResult @params } - catch { - Write-Error "An error occurred during the test: $_" - - # Retrieve the description from the test definitions - $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } - $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - - $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - - # Call Initialize-CISAuditResult with error parameters - $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - } } end { From 3ca779650e69c158b81bf072063a47d3f6d64be2 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:34:37 -0500 Subject: [PATCH 02/13] docs: 6.1.2,6.1.3 refactored --- source/tests/Test-MailboxAuditingE3.ps1 | 3 ++- source/tests/Test-MailboxAuditingE5.ps1 | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 59ead24..8ae5b19 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -45,6 +45,7 @@ function Test-MailboxAuditingE3 { process { if ($null -ne $founde3Sku) { $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde3Sku.SkuId) )" -All + $mailboxes = Get-EXOMailbox -PropertySets Audit try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { @@ -57,7 +58,7 @@ function Test-MailboxAuditingE3 { #Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license." $userUPN = $user.UserPrincipalName - $mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit + $mailbox = $mailboxes | Where-Object { $_.UserPrincipalName -eq $user.UserPrincipalName } $missingActions = @() if ($mailbox.AuditEnabled) { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index 62ec0cb..e9e6852 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -42,6 +42,7 @@ function Test-MailboxAuditingE5 { process { if ($null -ne $founde5Sku) { $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde5Sku.SkuId) )" -All + $mailboxes = Get-EXOMailbox -PropertySets Audit try { foreach ($user in $allUsers) { if ($processedUsers.ContainsKey($user.UserPrincipalName)) { @@ -52,9 +53,9 @@ function Test-MailboxAuditingE5 { #$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName #$hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0 #Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license." - + $mailbox = $mailboxes | Where-Object { $_.UserPrincipalName -eq $user.UserPrincipalName } $userUPN = $user.UserPrincipalName - $mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit + #$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit $missingActions = @() if ($mailbox.AuditEnabled) { From 273630839ea67454c5ad2f5250e49bf84a89d09d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:40:44 -0500 Subject: [PATCH 03/13] fix: 2.1.1,2.1.4,2.1.5 surpress error messages and create a standard object when no e5 --- source/tests/Test-SafeAttachmentsPolicy.ps1 | 88 +++++++++++-------- source/tests/Test-SafeAttachmentsTeams.ps1 | 97 ++++++++++++--------- source/tests/Test-SafeLinksOfficeApps.ps1 | 97 ++++++++++++--------- 3 files changed, 158 insertions(+), 124 deletions(-) diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index 8b5fe06..d9dfca4 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -13,7 +13,7 @@ function Test-SafeAttachmentsPolicy { # Initialization code, if needed $recnum = "2.1.4" - <# + <# Conditions for 2.1.4 (L2) Ensure Safe Attachments policy is enabled Validate test for a pass: @@ -33,56 +33,68 @@ function Test-SafeAttachmentsPolicy { - Condition D: The policy is disabled. #> } - process { - try { - # 2.1.4 (L2) Ensure Safe Attachments policy is enabled + # Retrieve all Safe Attachment policies where Enable is set to True + $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } + if ($null -ne $safeAttachmentPolicies) { + try { + # 2.1.4 (L2) Ensure Safe Attachments policy is enabled - # Retrieve all Safe Attachment policies where Enable is set to True - $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } - # Condition A: Check if any Safe Attachments policy is enabled - $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 - # Condition B, C, D: Additional checks can be added here if more detailed policy attributes are required + # Condition A: Check if any Safe Attachments policy is enabled + $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 - # Determine details and failure reasons based on the presence of enabled policies - $details = if ($result) { - "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" - } - else { - "No Safe Attachments Policies are enabled." + # Condition B, C, D: Additional checks can be added here if more detailed policy attributes are required + + # Determine details and failure reasons based on the presence of enabled policies + $details = if ($result) { + "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" + } + else { + "No Safe Attachments Policies are enabled." + } + + $failureReasons = if ($result) { + "N/A" + } + else { + "Safe Attachments policy is not enabled." + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - $failureReasons = if ($result) { - "N/A" - } - else { - "Safe Attachments policy is not enabled." - } + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } + } + else { $params = @{ Rec = $recnum - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $false + Status = "Fail" + Details = "No M365 E5 licenses found." + FailureReason = "The audit is for M365 E5 licenses and the required EXO commands will not be available otherwise." } $auditResult = Initialize-CISAuditResult @params } - catch { - Write-Error "An error occurred during the test: $_" - - # Retrieve the description from the test definitions - $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } - $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - - $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - - # Call Initialize-CISAuditResult with error parameters - $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - } } end { diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index 8c0b5f3..588eb57 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -31,61 +31,72 @@ function Test-SafeAttachmentsTeams { } process { - try { - # 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled + # Retrieve the ATP policies for Office 365 and check Safe Attachments settings + [void]($atpPolicies = Get-AtpPolicyForO365) + if ($null -ne $atpPolicies) { + try { + # 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled - # Retrieve the ATP policies for Office 365 and check Safe Attachments settings - $atpPolicies = Get-AtpPolicyForO365 + # Check if the required ATP policies are enabled + $atpPolicyResult = $atpPolicies | Where-Object { + $_.EnableATPForSPOTeamsODB -eq $true -and + $_.EnableSafeDocs -eq $true -and + $_.AllowSafeDocsOpen -eq $false + } - # Check if the required ATP policies are enabled - $atpPolicyResult = $atpPolicies | Where-Object { - $_.EnableATPForSPOTeamsODB -eq $true -and - $_.EnableSafeDocs -eq $true -and - $_.AllowSafeDocsOpen -eq $false + # Condition A: Check Safe Attachments for SharePoint + # Condition B: Check Safe Attachments for OneDrive + # Condition C: Check Safe Attachments for Microsoft Teams + + # Determine the result based on the ATP policy settings + $result = $null -ne $atpPolicyResult + $details = if ($result) { + "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." + } + else { + "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." + } + + $failureReasons = if ($result) { + "N/A" + } + else { + "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." + } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - # Condition A: Check Safe Attachments for SharePoint - # Condition B: Check Safe Attachments for OneDrive - # Condition C: Check Safe Attachments for Microsoft Teams + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Determine the result based on the ATP policy settings - $result = $null -ne $atpPolicyResult - $details = if ($result) { - "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." - } - else { - "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." - } + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - $failureReasons = if ($result) { - "N/A" + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure } - else { - "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." - } - - # Create and populate the CISAuditResult object + } + else { $params = @{ Rec = $recnum - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $false + Status = "Fail" + Details = "No M365 E3 licenses found." + FailureReason = "The audit is for M365 E3 licenses, but no such licenses were found." } $auditResult = Initialize-CISAuditResult @params } - catch { - Write-Error "An error occurred during the test: $_" - - # Retrieve the description from the test definitions - $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } - $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - - $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - - # Call Initialize-CISAuditResult with error parameters - $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - } } end { diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 37a864b..86cdac4 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -40,62 +40,73 @@ function Test-SafeLinksOfficeApps { } process { - try { - # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled + # Retrieve all Safe Links policies + [void]($policies = Get-SafeLinksPolicy) + if ($null -ne $policies) { + try { + # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled - # Retrieve all Safe Links policies - $policies = Get-SafeLinksPolicy + # Initialize the details collection + $misconfiguredDetails = @() - # Initialize the details collection - $misconfiguredDetails = @() + foreach ($policy in $policies) { + # Get the detailed configuration of each policy + $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name - foreach ($policy in $policies) { - # Get the detailed configuration of each policy - $policyDetails = Get-SafeLinksPolicy -Identity $policy.Name + # Check each required property and record failures + # Condition A: Checking policy settings + $failures = @() + if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } # Email: On + if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } # Teams: On + if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } # Office 365 Apps: On + if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } # Click protection settings: On + if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } # Do not track when users click safe links: Off - # Check each required property and record failures - # Condition A: Checking policy settings - $failures = @() - if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" } # Email: On - if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" } # Teams: On - if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" } # Office 365 Apps: On - if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" } # Click protection settings: On - if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" } # Do not track when users click safe links: Off - - # Only add details for policies that have misconfigurations - if ($failures.Count -gt 0) { - $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" + # Only add details for policies that have misconfigurations + if ($failures.Count -gt 0) { + $misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')" + } } + + # Prepare the final result + # Condition B: Ensuring no misconfigurations + $result = $misconfiguredDetails.Count -eq 0 + $details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' } + $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } + + # Create and populate the CISAuditResult object + $params = @{ + Rec = $recnum + Result = $result + Status = if ($result) { "Pass" } else { "Fail" } + Details = $details + FailureReason = $failureReasons + } + $auditResult = Initialize-CISAuditResult @params } + catch { + Write-Error "An error occurred during the test: $_" - # 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 ' | ' } - $failureReasons = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" } + # Retrieve the description from the test definitions + $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } + $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - # Create and populate the CISAuditResult object + $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) + + # Call Initialize-CISAuditResult with error parameters + $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure + } + } + else { $params = @{ Rec = $recnum - Result = $result - Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Result = $false + Status = "Fail" + Details = "No M365 E5 licenses found." + FailureReason = "The audit is for M365 E5 licenses and the required EXO commands will not be available otherwise." } $auditResult = Initialize-CISAuditResult @params } - catch { - Write-Error "An error occurred during the test: $_" - - # Retrieve the description from the test definitions - $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } - $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } - - $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $_ }) - - # Call Initialize-CISAuditResult with error parameters - $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure - } } end { From ccacf76e6c5cd210e79ef3ceb4a89c2b8e69bbc5 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:23:03 -0500 Subject: [PATCH 04/13] fix: 2.1.1,2.1.4,2.1.5 surpress error messages and create a standard object when no e5 --- source/tests/Test-SafeAttachmentsPolicy.ps1 | 70 ++++++++++++--------- source/tests/Test-SafeAttachmentsTeams.ps1 | 11 ++-- source/tests/Test-SafeLinksOfficeApps.ps1 | 7 +-- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/source/tests/Test-SafeAttachmentsPolicy.ps1 b/source/tests/Test-SafeAttachmentsPolicy.ps1 index d9dfca4..b3cb316 100644 --- a/source/tests/Test-SafeAttachmentsPolicy.ps1 +++ b/source/tests/Test-SafeAttachmentsPolicy.ps1 @@ -1,10 +1,7 @@ function Test-SafeAttachmentsPolicy { [CmdletBinding()] [OutputType([CISAuditResult])] - param ( - # Aligned - # Parameters can be added if needed - ) + param () begin { # Dot source the class script if necessary @@ -31,44 +28,60 @@ function Test-SafeAttachmentsPolicy { - 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. - #> + #> } + process { - # Retrieve all Safe Attachment policies where Enable is set to True - $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } - if ($null -ne $safeAttachmentPolicies) { + if (Get-Command Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue) { try { - # 2.1.4 (L2) Ensure Safe Attachments policy is enabled - - - - # Condition A: Check if any Safe Attachments policy is enabled + # Retrieve all Safe Attachment policies where Enable is set to True + $safeAttachmentPolicies = Get-SafeAttachmentPolicy -ErrorAction SilentlyContinue | Where-Object { $_.Enable -eq $true } + # Check if any Safe Attachments policy is enabled (Condition A) $result = $null -ne $safeAttachmentPolicies -and $safeAttachmentPolicies.Count -gt 0 - # Condition B, C, D: Additional checks can be added here if more detailed policy attributes are required + # Initialize details and failure reasons + $details = @() + $failureReasons = @() - # Determine details and failure reasons based on the presence of enabled policies - $details = if ($result) { - "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" - } - else { - "No Safe Attachments Policies are enabled." + 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 + } } - $failureReasons = if ($result) { - "N/A" - } - else { - "Safe Attachments policy is not enabled." - } + # The result is a pass if there are no failure reasons + $result = $failureReasons.Count -eq 0 + + # Format details for output + $detailsString = $details | Format-Table -AutoSize | Out-String + $failureReasonsString = ($failureReasons | ForEach-Object { $_ }) -join ' ' # Create and populate the CISAuditResult object $params = @{ Rec = $recnum Result = $result Status = if ($result) { "Pass" } else { "Fail" } - Details = $details - FailureReason = $failureReasons + Details = $detailsString + FailureReason = if ($result) { "N/A" } else { $failureReasonsString } } $auditResult = Initialize-CISAuditResult @params } @@ -102,4 +115,3 @@ function Test-SafeAttachmentsPolicy { return $auditResult } } - diff --git a/source/tests/Test-SafeAttachmentsTeams.ps1 b/source/tests/Test-SafeAttachmentsTeams.ps1 index 588eb57..ceb7547 100644 --- a/source/tests/Test-SafeAttachmentsTeams.ps1 +++ b/source/tests/Test-SafeAttachmentsTeams.ps1 @@ -31,12 +31,11 @@ function Test-SafeAttachmentsTeams { } process { - # Retrieve the ATP policies for Office 365 and check Safe Attachments settings - [void]($atpPolicies = Get-AtpPolicyForO365) - if ($null -ne $atpPolicies) { + if (Get-Command Get-AtpPolicyForO365 -ErrorAction SilentlyContinue) { try { # 2.1.5 (L2) Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled - + # Retrieve the ATP policies for Office 365 and check Safe Attachments settings + $atpPolicies = Get-AtpPolicyForO365 # Check if the required ATP policies are enabled $atpPolicyResult = $atpPolicies | Where-Object { $_.EnableATPForSPOTeamsODB -eq $true -and @@ -92,8 +91,8 @@ function Test-SafeAttachmentsTeams { Rec = $recnum Result = $false Status = "Fail" - Details = "No M365 E3 licenses found." - FailureReason = "The audit is for M365 E3 licenses, but no such licenses were found." + Details = "No M365 E5 licenses found." + FailureReason = "The audit is for M365 E5 licenses and the required EXO commands will not be available otherwise." } $auditResult = Initialize-CISAuditResult @params } diff --git a/source/tests/Test-SafeLinksOfficeApps.ps1 b/source/tests/Test-SafeLinksOfficeApps.ps1 index 86cdac4..8bd329b 100644 --- a/source/tests/Test-SafeLinksOfficeApps.ps1 +++ b/source/tests/Test-SafeLinksOfficeApps.ps1 @@ -40,12 +40,11 @@ function Test-SafeLinksOfficeApps { } process { - # Retrieve all Safe Links policies - [void]($policies = Get-SafeLinksPolicy) - if ($null -ne $policies) { + if (Get-Command Get-SafeLinksPolicy -ErrorAction SilentlyContinue) { try { # 2.1.1 (L2) Ensure Safe Links for Office Applications is Enabled - + # Retrieve all Safe Links policies + $policies = Get-SafeLinksPolicy # Initialize the details collection $misconfiguredDetails = @() From 83332207b4aa44b03c38079fafed361c21eada9e Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:23:53 -0500 Subject: [PATCH 05/13] docs: test scripts --- test-gh.ps1 | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/test-gh.ps1 b/test-gh.ps1 index b878413..cb3a8db 100644 --- a/test-gh.ps1 +++ b/test-gh.ps1 @@ -210,3 +210,196 @@ if ($warnings.Count -gt 0) { Get-GitHubRepository -OwnerName 'CriticalSolutionsNetwork' -RepositoryName 'M365FoundationsCISReport' +######################################################################################### +connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome +# Retrieve the subscribed SKUs +$sub = Get-MgSubscribedSku -All + +# Define the product array +$ProductArray = @( + "Microsoft_Cloud_App_Security_App_Governance_Add_On", + "Defender_Threat_Intelligence", + "THREAT_INTELLIGENCE", + "WIN_DEF_ATP", + "Microsoft_Defender_for_Endpoint_F2", + "DEFENDER_ENDPOINT_P1", + "DEFENDER_ENDPOINT_P1_EDU", + "MDATP_XPLAT", + "MDATP_Server", + "ATP_ENTERPRISE_FACULTY", + "ATA", + "ATP_ENTERPRISE_GOV", + "ATP_ENTERPRISE_USGOV_GCCHIGH", + "THREAT_INTELLIGENCE_GOV", + "TVM_Premium_Standalone", + "TVM_Premium_Add_on", + "ATP_ENTERPRISE", + "Azure_Information_Protection_Premium_P1", + "Azure_Information_Protection_Premium_P2", + "Microsoft_Application_Protection_and_Governance", + "Exchange_Online_Protection", + "Microsoft_365_Defender", + "Cloud_App_Security_Discovery" +) + +# Define the hashtable +$ProductHashTable = @{ + "App governance add-on to Microsoft Defender for Cloud Apps" = "Microsoft_Cloud_App_Security_App_Governance_Add_On" + "Defender Threat Intelligence" = "Defender_Threat_Intelligence" + "Microsoft Defender for Office 365 (Plan 2)" = "THREAT_INTELLIGENCE" + "Microsoft Defender for Endpoint" = "WIN_DEF_ATP" + "Microsoft Defender for Endpoint F2" = "Microsoft_Defender_for_Endpoint_F2" + "Microsoft Defender for Endpoint P1" = "DEFENDER_ENDPOINT_P1" + "Microsoft Defender for Endpoint P1 for EDU" = "DEFENDER_ENDPOINT_P1_EDU" + "Microsoft Defender for Endpoint P2_XPLAT" = "MDATP_XPLAT" + "Microsoft Defender for Endpoint Server" = "MDATP_Server" + "Microsoft Defender for Office 365 (Plan 1) Faculty" = "ATP_ENTERPRISE_FACULTY" + "Microsoft Defender for Identity" = "ATA" + "Microsoft Defender for Office 365 (Plan 1) GCC" = "ATP_ENTERPRISE_GOV" + "Microsoft Defender for Office 365 (Plan 1)_USGOV_GCCHIGH" = "ATP_ENTERPRISE_USGOV_GCCHIGH" + "Microsoft Defender for Office 365 (Plan 2) GCC" = "THREAT_INTELLIGENCE_GOV" + "Microsoft Defender Vulnerability Management" = "TVM_Premium_Standalone" + "Microsoft Defender Vulnerability Management Add-on" = "TVM_Premium_Add_on" + "Microsoft Defender for Office 365 (Plan 1)" = "ATP_ENTERPRISE" + "Azure Information Protection Premium P1" = "Azure_Information_Protection_Premium_P1" + "Azure Information Protection Premium P2" = "Azure_Information_Protection_Premium_P2" + "Microsoft Application Protection and Governance" = "Microsoft_Application_Protection_and_Governance" + "Exchange Online Protection" = "Exchange_Online_Protection" + "Microsoft 365 Defender" = "Microsoft_365_Defender" + "Cloud App Security Discovery" = "Cloud_App_Security_Discovery" +} + +# Reverse the hashtable +$ReverseProductHashTable = @{} +foreach ($key in $ProductHashTable.Keys) { + $ReverseProductHashTable[$ProductHashTable[$key]] = $key +} + +# Loop through each SKU and get the enabled security features +$securityFeatures = foreach ($sku in $sub) { +if ($sku.SkuPartNumber -eq "MDATP_XPLAT_EDU") { +Write-Host "the SKU is: `n$($sku | gm)" + [PSCustomObject]@{ + Skupartnumber = $sku.skupartnumber + AppliesTo = $sku.AppliesTo + ProvisioningStatus = $sku.ProvisioningStatus + ServicePlanId = $sku.ServicePlanId + ServicePlanName = $sku.ServicePlanName + FriendlyName = "Defender P2 for EDU" + } + } + else { + + $sku.serviceplans | Where-Object { $_.serviceplanname -in $ProductArray } | ForEach-Object { + $friendlyName = $ReverseProductHashTable[$_.ServicePlanName] + [PSCustomObject]@{ + Skupartnumber = $sku.skupartnumber + AppliesTo = $_.AppliesTo + ProvisioningStatus = $_.ProvisioningStatus + ServicePlanId = $_.ServicePlanId + ServicePlanName = $_.ServicePlanName + FriendlyName = $friendlyName + } + } + + } + +} + +# Output the security features +$securityFeatures | Format-Table -AutoSize + + + +########## + +# Ensure the ImportExcel module is available + + +# Ensure the ImportExcel module is available +if (-not (Get-Module -ListAvailable -Name ImportExcel)) { + Install-Module -Name ImportExcel -Force -Scope CurrentUser +} + +# Function to wait until the file is available +function Wait-ForFile { + param ( + [string]$FilePath + ) + while (Test-Path -Path $FilePath -PathType Leaf -and -not (Get-Content $FilePath -ErrorAction SilentlyContinue)) { + Start-Sleep -Seconds 1 + } +} + +# Path to the Excel file +$excelFilePath = "C:\Users\dougrios\OneDrive - CRITICALSOLUTIONS NET LLC\Documents\_Tools\Benchies\SKUs.xlsx" + +# Wait for the file to be available + + +# Import the Excel file +$excelData = Import-Excel -Path $excelFilePath + +# Retrieve the subscribed SKUs +$subscribedSkus = Get-MgSubscribedSku -All + +# Define the hashtable with security-related product names +$ProductHashTable = @{ + "App governance add-on to Microsoft Defender for Cloud Apps" = "Microsoft_Cloud_App_Security_App_Governance_Add_On" + "Defender Threat Intelligence" = "Defender_Threat_Intelligence" + "Microsoft Defender for Office 365 (Plan 2)" = "THREAT_INTELLIGENCE" + "Microsoft Defender for Endpoint" = "WIN_DEF_ATP" + "Microsoft Defender for Endpoint F2" = "Microsoft_Defender_for_Endpoint_F2" + "Microsoft Defender for Endpoint P1" = "DEFENDER_ENDPOINT_P1" + "Microsoft Defender for Endpoint P1 for EDU" = "DEFENDER_ENDPOINT_P1_EDU" + "Microsoft Defender for Endpoint P2_XPLAT" = "MDATP_XPLAT" + "Microsoft Defender for Endpoint Server" = "MDATP_Server" + "Microsoft Defender for Office 365 (Plan 1) Faculty" = "ATP_ENTERPRISE_FACULTY" + "Microsoft Defender for Identity" = "ATA" + "Microsoft Defender for Office 365 (Plan 1) GCC" = "ATP_ENTERPRISE_GOV" + "Microsoft Defender for Office 365 (Plan 1)_USGOV_GCCHIGH" = "ATP_ENTERPRISE_USGOV_GCCHIGH" + "Microsoft Defender for Office 365 (Plan 2) GCC" = "THREAT_INTELLIGENCE_GOV" + "Microsoft Defender Vulnerability Management" = "TVM_Premium_Standalone" + "Microsoft Defender Vulnerability Management Add-on" = "TVM_Premium_Add_on" + "Microsoft Defender for Office 365 (Plan 1)" = "ATP_ENTERPRISE" + "Azure Information Protection Premium P1" = "Azure_Information_Protection_Premium_P1" + "Azure Information Protection Premium P2" = "Azure_Information_Protection_Premium_P2" + "Microsoft Application Protection and Governance" = "Microsoft_Application_Protection_and_Governance" + "Exchange Online Protection" = "Exchange_Online_Protection" + "Microsoft 365 Defender" = "Microsoft_365_Defender" + "Cloud App Security Discovery" = "Cloud_App_Security_Discovery" +} + +# Create a hashtable to store the SKU part numbers and their associated security features +$skuSecurityFeatures = @{} + +# Populate the hashtable with data from the Excel file +foreach ($row in $excelData) { + if ($null -ne $row.'String ID' -and $null -ne $row.'Service plans included (friendly names)') { + $skuSecurityFeatures[$row.'String ID'] = $row.'Service plans included (friendly names)' + } +} + +# Display the SKU part numbers and their associated security features +foreach ($sku in $subscribedSkus) { + $skuPartNumber = $sku.SkuPartNumber + if ($skuSecurityFeatures.ContainsKey($skuPartNumber)) { + $securityFeatures = $skuSecurityFeatures[$skuPartNumber] + + # Check if the security feature is in the hashtable + $isSecurityFeature = $ProductHashTable.ContainsKey($securityFeatures) + + if ($isSecurityFeature) { + Write-Output "SKU Part Number: $skuPartNumber" + Write-Output "Security Features: $securityFeatures (Security-related)" + } else { + Write-Output "SKU Part Number: $skuPartNumber" + Write-Output "Security Features: $securityFeatures" + } + Write-Output "----------------------------" + } else { + Write-Output "SKU Part Number: $skuPartNumber" + Write-Output "Security Features: Not Found in Excel" + Write-Output "----------------------------" + } +} From c9940c2a09719118ad582c434c348ef30fe3b1aa Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:24:07 -0500 Subject: [PATCH 06/13] docs: update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58fd6be..175d5a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +### Fixed + +- Fixed 6.1.2/6.1.3 tests to minimize calls to the Graph API. +- Fixed 2.1.1,2.1.4,2.1.5 to suppress error messages and create a standard object when no e5" + +## [0.1.10] - 2024-06-12 + ### Added - Added condition comments to each test. From 9a7de2f549f1060fc98163e4554e253151119941 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:44:53 -0500 Subject: [PATCH 07/13] fix: error handling for 6.1.2/6.1.3 --- source/tests/Test-MailboxAuditingE3.ps1 | 2 +- source/tests/Test-MailboxAuditingE5.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/tests/Test-MailboxAuditingE3.ps1 b/source/tests/Test-MailboxAuditingE3.ps1 index 8ae5b19..d7c699a 100644 --- a/source/tests/Test-MailboxAuditingE3.ps1 +++ b/source/tests/Test-MailboxAuditingE3.ps1 @@ -43,7 +43,7 @@ function Test-MailboxAuditingE3 { process { - if ($null -ne $founde3Sku) { + if (($founde3Sku.count)-ne 0) { $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde3Sku.SkuId) )" -All $mailboxes = Get-EXOMailbox -PropertySets Audit try { diff --git a/source/tests/Test-MailboxAuditingE5.ps1 b/source/tests/Test-MailboxAuditingE5.ps1 index e9e6852..a332baf 100644 --- a/source/tests/Test-MailboxAuditingE5.ps1 +++ b/source/tests/Test-MailboxAuditingE5.ps1 @@ -40,7 +40,7 @@ function Test-MailboxAuditingE5 { } process { - if ($null -ne $founde5Sku) { + if (($founde5Sku.count) -ne 0) { $allUsers = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($founde5Sku.SkuId) )" -All $mailboxes = Get-EXOMailbox -PropertySets Audit try { From 8835ddfbfdf36cf3a7c25e9d960323c0a3eff121 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:45:17 -0500 Subject: [PATCH 08/13] add: public function to check mfa status --- source/Public/Get-MFAStatus.ps1 | 99 +++++++++++++++++++++++ tests/Unit/Public/Get-MFAStatus.tests.ps1 | 71 ++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 source/Public/Get-MFAStatus.ps1 create mode 100644 tests/Unit/Public/Get-MFAStatus.tests.ps1 diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 new file mode 100644 index 0000000..e026790 --- /dev/null +++ b/source/Public/Get-MFAStatus.ps1 @@ -0,0 +1,99 @@ +<# + .SYNOPSIS + Retrieves the MFA (Multi-Factor Authentication) status for Azure Active Directory users. + .DESCRIPTION + The Get-MFAStatus function connects to Microsoft Online Service and retrieves the MFA status for all Azure Active Directory users, excluding guest accounts. Optionally, you can specify a single user by their User Principal Name (UPN) to get their MFA status. + .PARAMETER 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. + .EXAMPLE + Get-MFAStatus + Retrieves the MFA status for all Azure Active Directory users. + .EXAMPLE + Get-MFAStatus -UserId "example@domain.com" + Retrieves the MFA status for the specified user with the UPN "example@domain.com". + .OUTPUTS + System.Object + Returns a sorted list of custom objects containing the following properties: + - UserPrincipalName + - DisplayName + - MFAState + - MFADefaultMethod + - MFAPhoneNumber + - PrimarySMTP + - Aliases + .NOTES + The function requires the MSOL module to be installed and connected to your tenant. + Ensure that you have the necessary permissions to read user and MFA status information. +#> +function Get-MFAStatus { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$UserId + ) + + begin { + # Connect to Microsoft Online service + } + + process { + if (Get-Module MSOnline){ + Connect-MsolService + Write-Host "Finding Azure Active Directory Accounts..." + # Get all users, excluding guests + $Users = if ($PSBoundParameters.ContainsKey('UserId')) { + Get-MsolUser -UserPrincipalName $UserId + } else { + Get-MsolUser -All | Where-Object { $_.UserType -ne "Guest" } + } + $Report = [System.Collections.Generic.List[Object]]::new() # Create output list + Write-Host "Processing" $Users.Count "accounts..." + ForEach ($User in $Users) { + $MFADefaultMethod = ($User.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq "True" }).MethodType + $MFAPhoneNumber = $User.StrongAuthenticationUserDetails.PhoneNumber + $PrimarySMTP = $User.ProxyAddresses | Where-Object { $_ -clike "SMTP*" } | ForEach-Object { $_ -replace "SMTP:", "" } + $Aliases = $User.ProxyAddresses | Where-Object { $_ -clike "smtp*" } | ForEach-Object { $_ -replace "smtp:", "" } + + If ($User.StrongAuthenticationRequirements) { + $MFAState = $User.StrongAuthenticationRequirements.State + } + Else { + $MFAState = 'Disabled' + } + + If ($MFADefaultMethod) { + Switch ($MFADefaultMethod) { + "OneWaySMS" { $MFADefaultMethod = "Text code authentication phone" } + "TwoWayVoiceMobile" { $MFADefaultMethod = "Call authentication phone" } + "TwoWayVoiceOffice" { $MFADefaultMethod = "Call office phone" } + "PhoneAppOTP" { $MFADefaultMethod = "Authenticator app or hardware token" } + "PhoneAppNotification" { $MFADefaultMethod = "Microsoft authenticator app" } + } + } + Else { + $MFADefaultMethod = "Not enabled" + } + + $ReportLine = [PSCustomObject] @{ + UserPrincipalName = $User.UserPrincipalName + DisplayName = $User.DisplayName + MFAState = $MFAState + MFADefaultMethod = $MFADefaultMethod + MFAPhoneNumber = $MFAPhoneNumber + PrimarySMTP = ($PrimarySMTP -join ',') + Aliases = ($Aliases -join ',') + } + + $Report.Add($ReportLine) + } + + Write-Host "Processing complete." + return $Report | Select-Object UserPrincipalName, DisplayName, MFAState, MFADefaultMethod, MFAPhoneNumber, PrimarySMTP, Aliases | Sort-Object UserPrincipalName + } + else { + Write-Host "You must first install MSOL using:`nInstall-Module MSOnline -Scope CurrentUser -Force" + } + } + +} \ No newline at end of file diff --git a/tests/Unit/Public/Get-MFAStatus.tests.ps1 b/tests/Unit/Public/Get-MFAStatus.tests.ps1 new file mode 100644 index 0000000..5998a20 --- /dev/null +++ b/tests/Unit/Public/Get-MFAStatus.tests.ps1 @@ -0,0 +1,71 @@ +BeforeAll { + $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' + + # If the module is not found, run the build task 'noop'. + if (-not (Get-Module -Name $script:moduleName -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Module -Name $script:moduleName +} + +Describe Get-Something { + + Context 'Return values' { + BeforeEach { + $return = Get-Something -Data 'value' + } + + It 'Returns a single object' { + ($return | Measure-Object).Count | Should -Be 1 + } + + } + + Context 'Pipeline' { + It 'Accepts values from the pipeline by value' { + $return = 'value1', 'value2' | Get-Something + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + + It 'Accepts value from the pipeline by property name' { + $return = 'value1', 'value2' | ForEach-Object { + [PSCustomObject]@{ + Data = $_ + OtherProperty = 'other' + } + } | Get-Something + + + $return[0] | Should -Be 'value1' + $return[1] | Should -Be 'value2' + } + } + + Context 'ShouldProcess' { + It 'Supports WhatIf' { + (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true + { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw + } + + + } +} + From da856b96e4508cfb319d97f3573c0d3edf3e0b9d Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:47:26 -0500 Subject: [PATCH 09/13] update help --- README.md | Bin 29808 -> 31930 bytes docs/index.html | Bin 72076 -> 79350 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index f4582578d955466e7a3f3679d84212bce701d59c..9906e7af41fae381501af0b40ac0d1954e594733 100644 GIT binary patch delta 1527 zcmaJ>Jxo(k6uu9LlnMy3{8Mzoa#z_+g2OV4t6CsAhPJZX!mO}W+OWXU-@ArM@+-_}XAOC49=|VbN zex5wGTh{lRr5bHb+Gw`CfzoZY*3Y7^_L?gqERw=4d}2U&#VF1}d=`aC=f+P_`j{h? z6tjrYGZDH7h!&g|MFepMG6#?`jdNTqAj%7m%7{t-w5(^q8$;$9M3}H=df;tlC+{IL z7i1@|tY?V({MI1)(S{w_CQC>IKv)(4^HX6jDB4c%BY>#ec-&$<6FVqE;ttJM)zF9P zRw>8vT`pQHs-s5%TPkbQ#RwW0z&(xc1TaTXau~l+iIQ~~N6c`BR~%e)c$r~B!Zlzq zke!38OPzE{Cn>9k70VrUP0s=Hw4=nJEFXhhNxUVH97n1lLez1$iMCES+)9DN1ZMb5 zAfp`hdfG^Dve8h7jW#=~>3Oh}R*PMArW{BT1VV_8paTy~hjoU&6+20vDxlRsB?U@4 zH8;I4X-?_TSeH{`;t@!&v`Z%T?VSxTQ_k9tPBX|+3+2NbuTNZd&qa+$TMXV|L`KCF zPP}f2XwA$iiXsbWJ&JZ?u&E|qNg*g#z@)}fr#42vrktsHlu>JCA~Lt*qrxwSV2Mfy z42t3McI&dKj;xrJL73c|*6O(QObM=h)JsoW)ac)z(;Sj18Q%NK+vdl@HFI#sJ# z_*ik3qJw!bvm{xVszDf8W`;Cx1S=Ai^D^w2q9qQ^JBP~OQE%Qp780RLH@3aa(nWo&7zUZc+VYs7QOkhPrH&i@ar)LGX6 delta 328 zcmdn>lkvj~#tpyNCo2e=P1fOv+T6sU#IxB%PD)vnmw}5x3kdxgN*PKR3c#e*WI-AA z&7XYMF|#Hx0O4fD0@uk80={gvbCY4jtz&Y6qu=IT?lDkZn+3fu<59gi#c>(Td_iy7 z%{v14m~fkou6kCO8$PoigfC!(x*cTpj7Tj!swdxwIyJeCLum85=p0sH&qt=qqs}L^5Ujpey-eR-ZX`I$*N*5{qD`0 zeTvqBScZB*DFu?dKrW7PKTcGjo547V1p4%RTvJbBwvQ??Zz1X=qv&LA*Lmz4#4bS) zv=p7yZ%r6U9I>T(@er1X5($JY9v-RWpR2r!r=`U0;UG7bSA|<~Y%d`CG+2k;br$b& z{T@TShke_ElHM^VJ$H&R;Ph&G|3GynQ>x-;cY||JfqMeHeK!Ny8lbh(fKFEAF((XB z72-oc$lxjR_2Fq@orsWvWTHS!$0V;)U?s9SnkbZBR+u7)c#1lw^itG@ci9D`eSnlB z6}8}*)*w^9L4`Z9v_)4;DywjeSTY}WhG5(uEwLM)j(d1ut9R|Rnt+3%E_|wUe443; zj&S?FHTOp?l z<)1$Qar)uA6wXe4tmKv37M0A%QKzAO%fsaXK4c~B2NwrC{Nhp}w>5h0?#o^efBUeA z??+b$ENz4{(5?r`ixFWizV~shKaLsckeDuqE+ikUtDm!wim00Hx;0rn!Y=6mDSt24 z4&`d)%;t-np!|3fPCki!rd#*w8YXda&$M-I=vd*C0DcBuI6;Yx5W@j8u1N!E5EMEf zUH*3y*xdHh0Xkd%9AcHtQ zCOxHFZ4j(PcMHFwjA1UFAQ{DUJ%4*lUnH1wcZ*b4J7xhuzw(m>xENK*%`}p?e z2v>Y7?Q+L+Zq5I?lKAtM8_|6CNIq&>dn)|pBl3k<(o!QEpE R_&|6$KOIflRZor|{RJfLl>`6) delta 113 zcmezNnx$tm%Y-xX1`HMq`V2-420(1Wpuk`V Date: Fri, 14 Jun 2024 10:48:38 -0500 Subject: [PATCH 10/13] docs: Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 175d5a3..0b76155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +### Added + +- Added Get-MFAStatus function to help with auditing mfa for conditional access controls. + ### Fixed - Fixed 6.1.2/6.1.3 tests to minimize calls to the Graph API. From d4252a18392897fc6dc3b9100913e4cf9db7eceb Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:51:02 -0500 Subject: [PATCH 11/13] docs: update help link for get-mfastatus --- source/Public/Get-MFAStatus.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 index e026790..d2379eb 100644 --- a/source/Public/Get-MFAStatus.ps1 +++ b/source/Public/Get-MFAStatus.ps1 @@ -24,6 +24,8 @@ .NOTES The function requires the MSOL module to be installed and connected to your tenant. Ensure that you have the necessary permissions to read user and MFA status information. + .LINK + https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-MFAStatus #> function Get-MFAStatus { [CmdletBinding()] From 342d0ac4a9d62ac1ff931a5640a07e0117abcba3 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:54:58 -0500 Subject: [PATCH 12/13] fix: Module check for Get-MFAStatus --- source/Public/Get-MFAStatus.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 index d2379eb..be5f122 100644 --- a/source/Public/Get-MFAStatus.ps1 +++ b/source/Public/Get-MFAStatus.ps1 @@ -28,6 +28,7 @@ https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-MFAStatus #> function Get-MFAStatus { + [OutputType([System.Object])] [CmdletBinding()] param ( [Parameter(Mandatory = $false)] @@ -37,6 +38,7 @@ function Get-MFAStatus { begin { # Connect to Microsoft Online service + Import-Module MSOnline -ErrorAction SilentlyContinue } process { From 4b3e448e48da914442cc941cbbc689c8a6d18748 Mon Sep 17 00:00:00 2001 From: DrIOS <58635327+DrIOSX@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:02:51 -0500 Subject: [PATCH 13/13] fix: write-host in public function due to code scanning alert --- source/Public/Get-MFAStatus.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Public/Get-MFAStatus.ps1 b/source/Public/Get-MFAStatus.ps1 index be5f122..b936e71 100644 --- a/source/Public/Get-MFAStatus.ps1 +++ b/source/Public/Get-MFAStatus.ps1 @@ -44,7 +44,7 @@ function Get-MFAStatus { process { if (Get-Module MSOnline){ Connect-MsolService - Write-Host "Finding Azure Active Directory Accounts..." + Write-Host -Object "Finding Azure Active Directory Accounts..." # Get all users, excluding guests $Users = if ($PSBoundParameters.ContainsKey('UserId')) { Get-MsolUser -UserPrincipalName $UserId @@ -52,7 +52,7 @@ function Get-MFAStatus { Get-MsolUser -All | Where-Object { $_.UserType -ne "Guest" } } $Report = [System.Collections.Generic.List[Object]]::new() # Create output list - Write-Host "Processing" $Users.Count "accounts..." + Write-Host -Object "Processing" $Users.Count "accounts..." ForEach ($User in $Users) { $MFADefaultMethod = ($User.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq "True" }).MethodType $MFAPhoneNumber = $User.StrongAuthenticationUserDetails.PhoneNumber @@ -92,11 +92,11 @@ function Get-MFAStatus { $Report.Add($ReportLine) } - Write-Host "Processing complete." + Write-Host -Object "Processing complete." return $Report | Select-Object UserPrincipalName, DisplayName, MFAState, MFADefaultMethod, MFAPhoneNumber, PrimarySMTP, Aliases | Sort-Object UserPrincipalName } else { - Write-Host "You must first install MSOL using:`nInstall-Module MSOnline -Scope CurrentUser -Force" + Write-Host -Object "You must first install MSOL using:`nInstall-Module MSOnline -Scope CurrentUser -Force" } }