fix: Output suppression

This commit is contained in:
DrIOS
2024-08-01 21:14:56 -05:00
parent 37e2b70ba4
commit db73d755ed
7 changed files with 190 additions and 177 deletions

View File

@@ -1,37 +1,40 @@
function Assert-ModuleAvailability { function Assert-ModuleAvailability {
[CmdletBinding()]
[OutputType([void]) ] [OutputType([void]) ]
param( param(
[string]$ModuleName, [string]$ModuleName,
[string]$RequiredVersion, [string]$RequiredVersion,
[string[]]$SubModules = @() [string[]]$SubModules = @()
) )
process {
try {
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }
if ($null -eq $module) {
Write-Verbose "Installing $ModuleName module..."
Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null
}
elseif ($module.Version -lt [version]$RequiredVersion) {
Write-Verbose "Updating $ModuleName module to required version..."
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
}
else {
Write-Verbose "$ModuleName module is already at required version or newer."
}
if ($SubModules.Count -gt 0) {
foreach ($subModule in $SubModules) {
Write-Verbose "Importing submodule $ModuleName.$subModule..."
Get-Module "$ModuleName.$subModule" | Import-Module -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
}
else {
Write-Verbose "Importing module $ModuleName..."
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
}
try {
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }
if ($null -eq $module) {
Write-Host "Installing $ModuleName module..." -ForegroundColor Yellow
Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null
} }
elseif ($module.Version -lt [version]$RequiredVersion) { catch {
Write-Host "Updating $ModuleName module to required version..." -ForegroundColor Yellow throw "Assert-ModuleAvailability:`n$_"
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
}
else {
Write-Host "$ModuleName module is already at required version or newer." -ForegroundColor Gray
}
if ($SubModules.Count -gt 0) {
foreach ($subModule in $SubModules) {
Write-Host "Importing submodule $ModuleName.$subModule..." -ForegroundColor DarkGray
Import-Module -Name "$ModuleName.$subModule" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
} else {
Write-Host "Importing module $ModuleName..." -ForegroundColor DarkGray
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
} }
} }
catch {
Write-Warning "An error occurred with module $ModuleName`: $_"
}
} }

View File

@@ -11,14 +11,18 @@ function Connect-M365Suite {
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[switch]$SkipConfirmation [switch]$SkipConfirmation
) )
if (!$SkipConfirmation) {
$VerbosePreference = "SilentlyContinue" $VerbosePreference = "Continue"
}
else {
$VerbosePreference = "SilentlyContinue"
}
$tenantInfo = @() $tenantInfo = @()
$connectedServices = @() $connectedServices = @()
try { try {
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") { if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") {
Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Yellow Write-Verbose "Connecting to Azure Active Directory..."
Connect-AzureAD -WarningAction SilentlyContinue | Out-Null Connect-AzureAD -WarningAction SilentlyContinue | Out-Null
$tenantDetails = Get-AzureADTenantDetail -WarningAction SilentlyContinue $tenantDetails = Get-AzureADTenantDetail -WarningAction SilentlyContinue
$tenantInfo += [PSCustomObject]@{ $tenantInfo += [PSCustomObject]@{
@@ -27,11 +31,11 @@ function Connect-M365Suite {
TenantID = $tenantDetails.ObjectId TenantID = $tenantDetails.ObjectId
} }
$connectedServices += "AzureAD" $connectedServices += "AzureAD"
Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green Write-Verbose "Successfully connected to Azure Active Directory."
} }
if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "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 Yellow Write-Verbose "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All"
try { try {
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null
$graphOrgDetails = Get-MgOrganization $graphOrgDetails = Get-MgOrganization
@@ -41,10 +45,10 @@ function Connect-M365Suite {
TenantID = $graphOrgDetails.Id TenantID = $graphOrgDetails.Id
} }
$connectedServices += "Microsoft Graph" $connectedServices += "Microsoft Graph"
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green Write-Verbose "Successfully connected to Microsoft Graph with specified scopes."
} }
catch { catch {
Write-Host "Failed to connect to MgGraph, attempting device auth." -ForegroundColor Yellow Write-Verbose "Failed to connect to MgGraph, attempting device auth."
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -UseDeviceCode -NoWelcome | Out-Null Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -UseDeviceCode -NoWelcome | Out-Null
$graphOrgDetails = Get-MgOrganization $graphOrgDetails = Get-MgOrganization
$tenantInfo += [PSCustomObject]@{ $tenantInfo += [PSCustomObject]@{
@@ -53,12 +57,12 @@ function Connect-M365Suite {
TenantID = $graphOrgDetails.Id TenantID = $graphOrgDetails.Id
} }
$connectedServices += "Microsoft Graph" $connectedServices += "Microsoft Graph"
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green Write-Verbose "Successfully connected to Microsoft Graph with specified scopes."
} }
} }
if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "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 Yellow Write-Verbose "Connecting to Exchange Online..."
Connect-ExchangeOnline -ShowBanner:$false | Out-Null Connect-ExchangeOnline -ShowBanner:$false | Out-Null
$exoTenant = (Get-OrganizationConfig).Identity $exoTenant = (Get-OrganizationConfig).Identity
$tenantInfo += [PSCustomObject]@{ $tenantInfo += [PSCustomObject]@{
@@ -67,11 +71,11 @@ function Connect-M365Suite {
TenantID = "N/A" TenantID = "N/A"
} }
$connectedServices += "EXO" $connectedServices += "EXO"
Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green Write-Verbose "Successfully connected to Exchange Online."
} }
if ($RequiredConnections -contains "SPO") { if ($RequiredConnections -contains "SPO") {
Write-Host "Connecting to SharePoint Online..." -ForegroundColor Yellow Write-Verbose "Connecting to SharePoint Online..."
Connect-SPOService -Url $TenantAdminUrl | Out-Null Connect-SPOService -Url $TenantAdminUrl | Out-Null
$spoContext = Get-SPOCrossTenantHostUrl $spoContext = Get-SPOCrossTenantHostUrl
$tenantName = Get-UrlLine -Output $spoContext $tenantName = Get-UrlLine -Output $spoContext
@@ -80,11 +84,11 @@ function Connect-M365Suite {
TenantName = $tenantName TenantName = $tenantName
} }
$connectedServices += "SPO" $connectedServices += "SPO"
Write-Host "Successfully connected to SharePoint Online." -ForegroundColor Green Write-Verbose "Successfully connected to SharePoint Online."
} }
if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") { if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Yellow Write-Verbose "Connecting to Microsoft Teams..."
Connect-MicrosoftTeams | Out-Null Connect-MicrosoftTeams | Out-Null
$teamsTenantDetails = Get-CsTenant $teamsTenantDetails = Get-CsTenant
$tenantInfo += [PSCustomObject]@{ $tenantInfo += [PSCustomObject]@{
@@ -93,20 +97,20 @@ function Connect-M365Suite {
TenantID = $teamsTenantDetails.TenantId TenantID = $teamsTenantDetails.TenantId
} }
$connectedServices += "Microsoft Teams" $connectedServices += "Microsoft Teams"
Write-Host "Successfully connected to Microsoft Teams." -ForegroundColor Green Write-Verbose "Successfully connected to Microsoft Teams."
} }
# Display tenant information and confirm with the user # Display tenant information and confirm with the user
if (-not $SkipConfirmation) { if (-not $SkipConfirmation) {
Write-Host "Connected to the following tenants:" -ForegroundColor Yellow Write-Verbose "Connected to the following tenants:"
foreach ($tenant in $tenantInfo) { foreach ($tenant in $tenantInfo) {
Write-Host "Service: $($tenant.Service)" -ForegroundColor Cyan Write-Verbose "Service: $($tenant.Service)"
Write-Host "Tenant Context: $($tenant.TenantName)`n" -ForegroundColor Green Write-Verbose "Tenant Context: $($tenant.TenantName)`n"
#Write-Host "Tenant ID: $($tenant.TenantID)" #Write-Verbose "Tenant ID: $($tenant.TenantID)"
} }
$confirmation = Read-Host "Do you want to proceed with these connections? (Y/N)" $confirmation = Read-Host "Do you want to proceed with these connections? (Y/N)"
if ($confirmation -notlike 'Y') { if ($confirmation -notlike 'Y') {
Write-Host "Connection setup aborted by user." -ForegroundColor Red Write-Verbose "Connection setup aborted by user."
Disconnect-M365Suite -RequiredConnections $connectedServices Disconnect-M365Suite -RequiredConnections $connectedServices
throw "User aborted connection setup." throw "User aborted connection setup."
} }
@@ -114,7 +118,7 @@ function Connect-M365Suite {
} }
catch { catch {
$VerbosePreference = "Continue" $VerbosePreference = "Continue"
Write-Host "There was an error establishing one or more connections: $_" -ForegroundColor Red Write-Verbose "There was an error establishing one or more connections: $_"
throw $_ throw $_
} }

View File

@@ -8,7 +8,7 @@ function Disconnect-M365Suite {
# Clean up sessions # Clean up sessions
try { try {
if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") { if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Exchange Online..." -ForegroundColor Green Write-Verbose "Disconnecting from Exchange Online..."
Disconnect-ExchangeOnline -Confirm:$false | Out-Null Disconnect-ExchangeOnline -Confirm:$false | Out-Null
} }
} }
@@ -18,7 +18,7 @@ function Disconnect-M365Suite {
try { try {
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") { if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") {
Write-Host "Disconnecting from Azure AD..." -ForegroundColor Green Write-Verbose "Disconnecting from Azure AD..."
Disconnect-AzureAD | Out-Null Disconnect-AzureAD | Out-Null
} }
} }
@@ -28,7 +28,7 @@ function Disconnect-M365Suite {
try { try {
if ($RequiredConnections -contains "Microsoft Graph") { if ($RequiredConnections -contains "Microsoft Graph") {
Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Green Write-Verbose "Disconnecting from Microsoft Graph..."
Disconnect-MgGraph | Out-Null Disconnect-MgGraph | Out-Null
} }
} }
@@ -38,7 +38,7 @@ function Disconnect-M365Suite {
try { try {
if ($RequiredConnections -contains "SPO") { if ($RequiredConnections -contains "SPO") {
Write-Host "Disconnecting from SharePoint Online..." -ForegroundColor Green Write-Verbose "Disconnecting from SharePoint Online..."
Disconnect-SPOService | Out-Null Disconnect-SPOService | Out-Null
} }
} }
@@ -48,13 +48,12 @@ function Disconnect-M365Suite {
try { try {
if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") { if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Microsoft Teams..." -ForegroundColor Green Write-Verbose "Disconnecting from Microsoft Teams..."
Disconnect-MicrosoftTeams | Out-Null Disconnect-MicrosoftTeams | Out-Null
} }
} }
catch { catch {
Write-Warning "Failed to disconnect from Microsoft Teams: $_" Write-Warning "Failed to disconnect from Microsoft Teams: $_"
} }
Write-Verbose "All necessary sessions have been disconnected."
Write-Host "All necessary sessions have been disconnected." -ForegroundColor Green
} }

View File

@@ -14,7 +14,7 @@ function Get-RequiredModule {
return @( return @(
@{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0"; SubModules = @() }, @{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0"; SubModules = @() },
@{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182"; SubModules = @() }, @{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182"; SubModules = @() },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModules = @("Groups", "DeviceManagement", "Users", "Identity.DirectoryManagement", "Identity.SignIns") }, @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModules = @("DeviceManagement", "Users", "Identity.DirectoryManagement", "Identity.SignIns") },
@{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000"; SubModules = @() }, @{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000"; SubModules = @() },
@{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0"; SubModules = @() } @{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0"; SubModules = @() }
) )

View File

@@ -18,15 +18,15 @@ function Measure-AuditResult {
$passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) } $passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) }
# Display the pass percentage to the user # Display the pass percentage to the user
Write-Host "Audit completed. $passedTests out of $totalTests tests passed." -ForegroundColor Cyan Write-Verbose "Audit completed. $passedTests out of $totalTests tests passed."
Write-Host "Your passing percentage is $passPercentage%." -ForegroundColor Magenta Write-Verbose "Your passing percentage is $passPercentage%."
# Display details of failed tests # Display details of failed tests
if ($FailedTests.Count -gt 0) { if ($FailedTests.Count -gt 0) {
Write-Host "The following tests failed to complete:" -ForegroundColor Red Write-Verbose "The following tests failed to complete:"
foreach ($failedTest in $FailedTests) { foreach ($failedTest in $FailedTests) {
Write-Host "Test: $($failedTest.Test)" -ForegroundColor Yellow Write-Verbose "Test: $($failedTest.Test)"
Write-Host "Error: $($failedTest.Error)" -ForegroundColor Yellow Write-Verbose "Error: $($failedTest.Error)"
} }
} }
} }

View File

@@ -49,7 +49,7 @@
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Export-M365SecurityAuditTable https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Export-M365SecurityAuditTable
#> #>
function Export-M365SecurityAuditTable { function Export-M365SecurityAuditTable {
[CmdletBinding()] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
[OutputType([PSCustomObject])] [OutputType([PSCustomObject])]
param ( param (
[Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ExportAllResultsFromAuditResults")]
@@ -74,12 +74,21 @@ function Export-M365SecurityAuditTable {
[switch]$ExportOriginalTests, [switch]$ExportOriginalTests,
[Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")]
[Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")] [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")]
[switch]$ExportToExcel [switch]$ExportToExcel,
# Add Prefix to filename after date when outputting to excel or csv.
[Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")]
[Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")]
# Validate that the count of letters in the prefix is less than 5.
[ValidateLength(0, 5)]
[string]$Prefix = "Corp"
) )
Begin { Begin {
$createdFiles = @() # Initialize an array to keep track of created files $createdFiles = @() # Initialize an array to keep track of created files
if ($ExportToExcel) { if ($ExportToExcel) {
Assert-ModuleAvailability -ModuleName ImportExcel -RequiredVersion "7.8.9" if ($PSCmdlet.ShouldProcess("ImportExcel v7.8.9", "Assert-ModuleAvailability")) {
Assert-ModuleAvailability -ModuleName ImportExcel -RequiredVersion "7.8.9"
}
} }
if ($PSCmdlet.ParameterSetName -like "ExportAllResultsFromCsv" -or $PSCmdlet.ParameterSetName -eq "OutputObjectFromCsvSingle") { if ($PSCmdlet.ParameterSetName -like "ExportAllResultsFromCsv" -or $PSCmdlet.ParameterSetName -eq "OutputObjectFromCsvSingle") {
$AuditResults = Import-Csv -Path $CsvPath | ForEach-Object { $AuditResults = Import-Csv -Path $CsvPath | ForEach-Object {
@@ -127,94 +136,92 @@ function Export-M365SecurityAuditTable {
} }
End { End {
if ($ExportPath) { if ($ExportPath) {
$timestamp = (Get-Date).ToString("yyyy.MM.dd_HH.mm.ss") if ($PSCmdlet.ShouldProcess("Export-M365SecurityAuditTable", "Exporting results to $ExportPath")) {
$exportedTests = @() $timestamp = (Get-Date).ToString("yyyy.MM.dd_HH.mm.ss")
foreach ($result in $results) { $exportedTests = @()
$testDef = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $result.TestNumber } foreach ($result in $results) {
if ($testDef) { $testDef = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $result.TestNumber }
$fileName = "$ExportPath\$($timestamp)_$($result.TestNumber).$($testDef.TestFileName -replace '\.ps1$').csv" if ($testDef) {
if ($result.Details.Count -eq 0) { $fileName = "$ExportPath\$($timestamp)_$($result.TestNumber).$($testDef.TestFileName -replace '\.ps1$').csv"
Write-Information "No results found for test number $($result.TestNumber)." -InformationAction Continue if ($result.Details.Count -eq 0) {
} Write-Information "No results found for test number $($result.TestNumber)."
else { }
if (($result.Details -ne "No M365 E3 licenses found.") -and ($result.Details -ne "No M365 E5 licenses found.")) { else {
if ($ExportToExcel) { if (($result.Details -ne "No M365 E3 licenses found.") -and ($result.Details -ne "No M365 E5 licenses found.")) {
$xlsxPath = [System.IO.Path]::ChangeExtension($fileName, '.xlsx') if ($ExportToExcel) {
$result.Details | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2 $xlsxPath = [System.IO.Path]::ChangeExtension($fileName, '.xlsx')
$createdFiles += $xlsxPath # Add the created file to the array $result.Details | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2
$createdFiles += $xlsxPath # Add the created file to the array
}
else {
$result.Details | Export-Csv -Path $fileName -NoTypeInformation
$createdFiles += $fileName # Add the created file to the array
}
$exportedTests += $result.TestNumber
} }
else {
$result.Details | Export-Csv -Path $fileName -NoTypeInformation
$createdFiles += $fileName # Add the created file to the array
}
$exportedTests += $result.TestNumber
} }
} }
} }
} if ($exportedTests.Count -gt 0) {
if ($exportedTests.Count -gt 0) { Write-Information "The following tests were exported: $($exportedTests -join ', ')"
Write-Information "The following tests were exported: $($exportedTests -join ', ')" -InformationAction Continue }
} else {
else { if ($ExportOriginalTests) {
Write-Information "Full audit results exported however, none of the following tests had exports: `n1.1.1, 1.3.1, 6.1.2, 6.1.3, 7.3.4"
}
else {
Write-Information "No specified tests were included in the export."
}
}
if ($ExportOriginalTests) { if ($ExportOriginalTests) {
Write-Information "Full audit results exported however, none of the following tests had exports: `n1.1.1, 1.3.1, 6.1.2, 6.1.3, 7.3.4" -InformationAction Continue # Define the test numbers to check
$TestNumbersToCheck = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4"
# Check for large details and update the AuditResults array
$updatedAuditResults = Get-ExceededLengthResultDetail -AuditResults $AuditResults -TestNumbersToCheck $TestNumbersToCheck -ExportedTests $exportedTests -DetailsLengthLimit 30000 -PreviewLineCount 25
$originalFileName = "$ExportPath\$timestamp`_$Prefix-M365FoundationsAudit.csv"
if ($ExportToExcel) {
$xlsxPath = [System.IO.Path]::ChangeExtension($originalFileName, '.xlsx')
$updatedAuditResults | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2
$createdFiles += $xlsxPath # Add the created file to the array
}
else {
$updatedAuditResults | Export-Csv -Path $originalFileName -NoTypeInformation
$createdFiles += $originalFileName # Add the created file to the array
}
} }
else { # Hash each file and add it to a dictionary
Write-Information "No specified tests were included in the export." -InformationAction Continue # Hash each file and save the hashes to a text file
$hashFilePath = "$ExportPath\$timestamp`_Hashes.txt"
$fileHashes = @()
foreach ($file in $createdFiles) {
$hash = Get-FileHash -Path $file -Algorithm SHA256
$fileHashes += "$($file): $($hash.Hash)"
} }
} $fileHashes | Set-Content -Path $hashFilePath
if ($ExportOriginalTests) { $createdFiles += $hashFilePath # Add the hash file to the array
# Define the test numbers to check # Create a zip file and add all the created files
$TestNumbersToCheck = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4" $zipFilePath = "$ExportPath\$timestamp`_$Prefix-M365FoundationsAudit.zip"
# Check for large details and update the AuditResults array Compress-Archive -Path $createdFiles -DestinationPath $zipFilePath
$updatedAuditResults = Get-ExceededLengthResultDetail -AuditResults $AuditResults -TestNumbersToCheck $TestNumbersToCheck -ExportedTests $exportedTests -DetailsLengthLimit 30000 -PreviewLineCount 25 # Remove the original files after they have been added to the zip
$originalFileName = "$ExportPath\$timestamp`_M365FoundationsAudit.csv" foreach ($file in $createdFiles) {
if ($ExportToExcel) { Remove-Item -Path $file -Force
$xlsxPath = [System.IO.Path]::ChangeExtension($originalFileName, '.xlsx')
$updatedAuditResults | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2
$createdFiles += $xlsxPath # Add the created file to the array
} }
else { # Compute the hash for the zip file and rename it
$updatedAuditResults | Export-Csv -Path $originalFileName -NoTypeInformation $zipHash = Get-FileHash -Path $zipFilePath -Algorithm SHA256
$createdFiles += $originalFileName # Add the created file to the array $newZipFilePath = "$ExportPath\$timestamp`_$Prefix-M365FoundationsAudit_$($zipHash.Hash.Substring(0, 8)).zip"
Rename-Item -Path $zipFilePath -NewName $newZipFilePath
# Output the zip file path with hash
return [PSCustomObject]@{
ZipFilePath = $newZipFilePath
} }
} }
# Hash each file and add it to a dictionary
# Hash each file and save the hashes to a text file
$hashFilePath = "$ExportPath\$timestamp`_Hashes.txt"
$fileHashes = @()
foreach ($file in $createdFiles) {
$hash = Get-FileHash -Path $file -Algorithm SHA256
$fileHashes += "$($file): $($hash.Hash)"
}
$fileHashes | Set-Content -Path $hashFilePath
$createdFiles += $hashFilePath # Add the hash file to the array
# Create a zip file and add all the created files
$zipFilePath = "$ExportPath\$timestamp`_M365FoundationsAudit.zip"
Compress-Archive -Path $createdFiles -DestinationPath $zipFilePath
# Remove the original files after they have been added to the zip
foreach ($file in $createdFiles) {
Remove-Item -Path $file -Force
}
# Compute the hash for the zip file and rename it
$zipHash = Get-FileHash -Path $zipFilePath -Algorithm SHA256
$newZipFilePath = "$ExportPath\$timestamp`_M365FoundationsAudit_$($zipHash.Hash.Substring(0, 8)).zip"
Rename-Item -Path $zipFilePath -NewName $newZipFilePath
# Output the zip file path with hash
[PSCustomObject]@{
ZipFilePath = $newZipFilePath
}
} # End of ExportPath } # End of ExportPath
elseif ($OutputTestNumber) { elseif ($OutputTestNumber) {
if ($results[0].Details) { if ($results[0].Details) {
return $results[0].Details return $results[0].Details
} }
else { else {
Write-Information "No results found for test number $($OutputTestNumber)." -InformationAction Continue Write-Information "No results found for test number $($OutputTestNumber)."
} }
} }
else { else {

View File

@@ -139,7 +139,8 @@
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit
#> #>
function Invoke-M365SecurityAudit { function Invoke-M365SecurityAudit {
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')] # Add confirm to high
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High" , DefaultParameterSetName = 'Default')]
[OutputType([CISAuditResult[]])] [OutputType([CISAuditResult[]])]
param ( param (
[Parameter(Mandatory = $false, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'. If not specified none of the Sharepoint Online tests will run.")] [Parameter(Mandatory = $false, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'. If not specified none of the Sharepoint Online tests will run.")]
@@ -206,7 +207,7 @@ function Invoke-M365SecurityAudit {
) )
Begin { Begin {
if ($script:MaximumFunctionCount -lt 8192) { if ($script:MaximumFunctionCount -lt 8192) {
Write-Verbose "Setting the `$script:MaximumFunctionCount to 8192 for the test run." -Verbose Write-Verbose "Setting the `$script:MaximumFunctionCount to 8192 for the test run."
$script:MaximumFunctionCount = 8192 $script:MaximumFunctionCount = 8192
} }
# Ensure required modules are installed # Ensure required modules are installed
@@ -214,8 +215,8 @@ function Invoke-M365SecurityAudit {
# Format the required modules list # Format the required modules list
$requiredModulesFormatted = Format-RequiredModuleList -RequiredModules $requiredModules $requiredModulesFormatted = Format-RequiredModuleList -RequiredModules $requiredModules
# Check and install required modules if necessary # Check and install required modules if necessary
if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Check for required modules: $requiredModulesFormatted", "Check")) { if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Modules: $requiredModulesFormatted", "Assert-ModuleAvailability")) {
Write-Host "Checking for and installing required modules..." -ForegroundColor DarkMagenta Write-Information "Checking for and installing required modules..."
foreach ($module in $requiredModules) { foreach ($module in $requiredModules) {
Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModules $module.SubModules Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModules $module.SubModules
} }
@@ -265,46 +266,59 @@ function Invoke-M365SecurityAudit {
try { try {
$actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections $actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections
if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) { if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) {
Write-Host "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -ForegroundColor DarkMagenta Write-Information "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')"
Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections -SkipConfirmation:$DoNotConfirmConnections Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections -SkipConfirmation:$DoNotConfirmConnections
} }
} }
catch { catch {
Write-Host "Connection execution aborted: $_" -ForegroundColor Red Throw "Connection execution aborted: $_"
break
} }
try { try {
Write-Host "A total of $($totalTests) tests were selected to run..." -ForegroundColor DarkMagenta if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", "Measure")) {
# Import the test functions Write-Information "A total of $($totalTests) tests were selected to run..."
$testFiles | ForEach-Object { # Import the test functions
$currentTestIndex++ $testFiles | ForEach-Object {
Write-Progress -Activity "Loading Test Scripts" -Status "Loading $($currentTestIndex) of $($totalTests): $($_.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100) $currentTestIndex++
Try { Write-Progress -Activity "Loading Test Scripts" -Status "Loading $($currentTestIndex) of $($totalTests): $($_.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100)
# Dot source the test function Try {
. $_.FullName # Dot source the test function
. $_.FullName
}
Catch {
# Log the error and add the test to the failed tests collection
Write-Verbose "Failed to load test function $($_.Name): $_"
$script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ })
}
} }
Catch { $currentTestIndex = 0
# Log the error and add the test to the failed tests collection # Execute each test function from the prepared list
Write-Verbose "Failed to load test function $($_.Name): $_" -Verbose foreach ($testFunction in $testFiles) {
$script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) $currentTestIndex++
} Write-Progress -Activity "Executing Tests" -Status "Executing $($currentTestIndex) of $($totalTests): $($testFunction.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100)
} $functionName = $testFunction.BaseName
$currentTestIndex = 0 Write-Information "Executing test function: $functionName"
# Execute each test function from the prepared list
foreach ($testFunction in $testFiles) {
$currentTestIndex++
Write-Progress -Activity "Executing Tests" -Status "Executing $($currentTestIndex) of $($totalTests): $($testFunction.Name)" -PercentComplete (($currentTestIndex / $totalTests) * 100)
$functionName = $testFunction.BaseName
if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) {
$auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $DomainName -ApprovedCloudStorageProviders $ApprovedCloudStorageProviders -ApprovedFederatedDomains $ApprovedFederatedDomains $auditResult = Invoke-TestFunction -FunctionFile $testFunction -DomainName $DomainName -ApprovedCloudStorageProviders $ApprovedCloudStorageProviders -ApprovedFederatedDomains $ApprovedFederatedDomains
# Add the result to the collection # Add the result to the collection
[void]$allAuditResults.Add($auditResult) [void]$allAuditResults.Add($auditResult)
} }
# Call the private function to calculate and display results
Measure-AuditResult -AllAuditResults $allAuditResults -FailedTests $script:FailedTests
# Return all collected audit results
# Define the test numbers to check
$TestNumbersToCheck = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4"
# Check for large details in the audit results
$exceedingTests = Get-ExceededLengthResultDetail -AuditResults $allAuditResults -TestNumbersToCheck $TestNumbersToCheck -ReturnExceedingTestsOnly -DetailsLengthLimit 30000
if ($exceedingTests.Count -gt 0) {
Write-Information "The following tests exceeded the details length limit: $($exceedingTests -join ', ')"
Write-Information "( Assuming the results were instantiated. Ex: `$object = invoke-M365SecurityAudit )`nUse the following command and adjust as necessary to view the full details of the test results:"
Write-Information "Export-M365SecurityAuditTable -ExportAllTests -AuditResults `$object -ExportPath `"C:\temp`" -ExportOriginalTests"
}
return $allAuditResults.ToArray() | Sort-Object -Property Rec
} }
} }
catch { catch {
# Log the error and add the test to the failed tests collection # Log the error and add the test to the failed tests collection
Write-Verbose "Invoke-M365SecurityAudit: Failed to load test function $($_.Name): $_" -Verbose throw "Failed to execute test function $($testFunction.Name): $_"
$script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ }) $script:FailedTests.Add([PSCustomObject]@{ Test = $_.Name; Error = $_ })
} }
finally { finally {
@@ -315,20 +329,6 @@ function Invoke-M365SecurityAudit {
} }
} }
End { End {
if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", "Measure")) {
# Call the private function to calculate and display results
Measure-AuditResult -AllAuditResults $allAuditResults -FailedTests $script:FailedTests
# Return all collected audit results
# Define the test numbers to check
$TestNumbersToCheck = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4"
# Check for large details in the audit results
$exceedingTests = Get-ExceededLengthResultDetail -AuditResults $allAuditResults -TestNumbersToCheck $TestNumbersToCheck -ReturnExceedingTestsOnly -DetailsLengthLimit 30000
if ($exceedingTests.Count -gt 0) {
Write-Information "The following tests exceeded the details length limit: $($exceedingTests -join ', ')" -InformationAction Continue
Write-Host "(Assuming the results were instantiated. Ex: `$object = invoke-M365SecurityAudit) Use the following command and adjust as neccesary to view the full details of the test results:" -ForegroundColor DarkCyan
Write-Host "Export-M365SecurityAuditTable -ExportAllTests -AuditResults `$object -ExportPath `"C:\temp`" -ExportOriginalTests" -ForegroundColor Green
}
return $allAuditResults.ToArray() | Sort-Object -Property Rec
}
} }
} }