diff --git a/CHANGELOG.md b/CHANGELOG.md index 717dd66..cc58231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +### Added + +- Added hash and compress steps to `Export-M365SecurityAuditTable` function. + +## [0.1.21] - 2024-07-01 + ### Fixed - Formatting for MgGraph tests. diff --git a/README.md b/README.md index e3d4e22..4c04a53 100644 Binary files a/README.md and b/README.md differ diff --git a/docs/index.html b/docs/index.html index 3c65f0f..9dae1e9 100644 Binary files a/docs/index.html and b/docs/index.html differ diff --git a/help/Export-M365SecurityAuditTable.md b/help/Export-M365SecurityAuditTable.md index 858ea30..07eb7cf 100644 --- a/help/Export-M365SecurityAuditTable.md +++ b/help/Export-M365SecurityAuditTable.md @@ -190,10 +190,11 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## INPUTS -### [CISAuditResult[]], [string] +### [CISAuditResult[]] - An array of CISAuditResult objects. +### [string] - A path to a CSV file. ## OUTPUTS -### [PSCustomObject] +### [PSCustomObject] - A custom object containing the path to the zip file and its hash. ## NOTES ## RELATED LINKS diff --git a/helpers/Build-Help.ps1 b/helpers/Build-Help.ps1 index 9dd530c..6bc99f2 100644 --- a/helpers/Build-Help.ps1 +++ b/helpers/Build-Help.ps1 @@ -4,7 +4,7 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1 <# - $ver = "v0.1.18" + $ver = "v0.1.22" git checkout main git pull origin main git tag -a $ver -m "Release version $ver refactor Update" @@ -59,6 +59,5 @@ Set-Secret -Name "GitHubToken" -Vault ModuleBuildCreds $GalleryApiToken = Get-Secret -Name "GalleryApiToken" -Vault ModuleBuildCreds -AsPlainText $GitHubToken = Get-Secret -Name "GitHubToken" -Vault ModuleBuildCreds -AsPlainText - $GalleryApiToken -$GitHubToken +$GitHubToken \ No newline at end of file diff --git a/source/Public/Export-M365SecurityAuditTable.ps1 b/source/Public/Export-M365SecurityAuditTable.ps1 index 29bd75c..e194998 100644 --- a/source/Public/Export-M365SecurityAuditTable.ps1 +++ b/source/Public/Export-M365SecurityAuditTable.ps1 @@ -1,47 +1,52 @@ <# .SYNOPSIS - Exports M365 security audit results to a CSV file or outputs a specific test result as an object. + Exports Microsoft 365 security audit results to CSV or Excel files and supports outputting specific test results as objects. .DESCRIPTION - This function exports M365 security audit results from either an array of CISAuditResult objects or a CSV file. - It can export all results to a specified path or output a specific test result as an object. + The Export-M365SecurityAuditTable function exports Microsoft 365 security audit results from an array of CISAuditResult objects or a CSV file. + It can export all results to a specified path, output a specific test result as an object, and includes options for exporting results to Excel. + Additionally, it computes hashes for the exported files and includes them in the zip archive for verification purposes. .PARAMETER AuditResults - An array of CISAuditResult objects containing the audit results. + An array of CISAuditResult objects containing the audit results. This parameter is mandatory when exporting from audit results. .PARAMETER CsvPath - The path to a CSV file containing the audit results. + The path to a CSV file containing the audit results. This parameter is mandatory when exporting from a CSV file. .PARAMETER OutputTestNumber - The test number to output as an object. Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". + The test number to output as an object. Valid values are "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4". This parameter is used to output a specific test result. .PARAMETER ExportAllTests - Switch to export all test results. + Switch to export all test results. When specified, all test results are exported to the specified path. .PARAMETER ExportPath - The path where the CSV files will be exported. + The path where the CSV or Excel files will be exported. This parameter is mandatory when exporting all tests. .PARAMETER ExportOriginalTests - Switch to export the original audit results to a CSV file. + Switch to export the original audit results to a CSV file. When specified, the original test results are exported along with the processed results. .PARAMETER ExportToExcel - Switch to export the results to an Excel file. + Switch to export the results to an Excel file. When specified, results are exported in Excel format. .INPUTS - [CISAuditResult[]], [string] + [CISAuditResult[]] - An array of CISAuditResult objects. + [string] - A path to a CSV file. .OUTPUTS - [PSCustomObject] + [PSCustomObject] - A custom object containing the path to the zip file and its hash. .EXAMPLE - Export-M365SecurityAuditTable -AuditResults $object -OutputTestNumber 6.1.2 - # Output object for a single test number from audit results + Export-M365SecurityAuditTable -AuditResults $object -OutputTestNumber 6.1.2 + # Outputs the result of test number 6.1.2 from the provided audit results as an object. .EXAMPLE - Export-M365SecurityAuditTable -ExportAllTests -AuditResults $object -ExportPath "C:\temp" - # Export all results from audit results to the specified path + Export-M365SecurityAuditTable -ExportAllTests -AuditResults $object -ExportPath "C:\temp" + # Exports all audit results to the specified path in CSV format. .EXAMPLE - Export-M365SecurityAuditTable -CsvPath "C:\temp\auditresultstoday1.csv" -OutputTestNumber 6.1.2 - # Output object for a single test number from CSV + Export-M365SecurityAuditTable -CsvPath "C:\temp\auditresultstoday1.csv" -OutputTestNumber 6.1.2 + # Outputs the result of test number 6.1.2 from the CSV file as an object. .EXAMPLE - Export-M365SecurityAuditTable -ExportAllTests -CsvPath "C:\temp\auditresultstoday1.csv" -ExportPath "C:\temp" - # Export all results from CSV to the specified path + Export-M365SecurityAuditTable -ExportAllTests -CsvPath "C:\temp\auditresultstoday1.csv" -ExportPath "C:\temp" + # Exports all audit results from the CSV file to the specified path in CSV format. .EXAMPLE - Export-M365SecurityAuditTable -ExportAllTests -AuditResults $object -ExportPath "C:\temp" -ExportOriginalTests - # Export all results from audit results to the specified path along with the original tests + Export-M365SecurityAuditTable -ExportAllTests -AuditResults $object -ExportPath "C:\temp" -ExportOriginalTests + # Exports all audit results along with the original test results to the specified path in CSV format. .EXAMPLE - Export-M365SecurityAuditTable -ExportAllTests -CsvPath "C:\temp\auditresultstoday1.csv" -ExportPath "C:\temp" -ExportOriginalTests - # Export all results from CSV to the specified path along with the original tests + Export-M365SecurityAuditTable -ExportAllTests -CsvPath "C:\temp\auditresultstoday1.csv" -ExportPath "C:\temp" -ExportOriginalTests + # Exports all audit results from the CSV file along with the original test results to the specified path in CSV format. + .EXAMPLE + Export-M365SecurityAuditTable -ExportAllTests -AuditResults $object -ExportPath "C:\temp" -ExportToExcel + # Exports all audit results to the specified path in Excel format. .LINK - https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Export-M365SecurityAuditTable + https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Export-M365SecurityAuditTable #> function Export-M365SecurityAuditTable { [CmdletBinding()] @@ -50,179 +55,206 @@ function Export-M365SecurityAuditTable { [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "OutputObjectFromAuditResultsSingle")] [CISAuditResult[]]$AuditResults, - [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ExportAllResultsFromCsv")] [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "OutputObjectFromCsvSingle")] [ValidateScript({ (Test-Path $_) -and ((Get-Item $_).PSIsContainer -eq $false) })] [string]$CsvPath, - [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "OutputObjectFromAuditResultsSingle")] [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "OutputObjectFromCsvSingle")] [ValidateSet("1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4")] [string]$OutputTestNumber, - [Parameter(Mandatory = $false, Position = 0, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $false, Position = 0, ParameterSetName = "ExportAllResultsFromCsv")] [switch]$ExportAllTests, - [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromCsv")] [string]$ExportPath, - [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $true, ParameterSetName = "ExportAllResultsFromCsv")] [switch]$ExportOriginalTests, - [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromAuditResults")] [Parameter(Mandatory = $false, ParameterSetName = "ExportAllResultsFromCsv")] [switch]$ExportToExcel ) - if ($ExportToExcel) { - Assert-ModuleAvailability -ModuleName ImportExcel -RequiredVersion "7.8.9" - } - if ($PSCmdlet.ParameterSetName -like "ExportAllResultsFromCsv" -or $PSCmdlet.ParameterSetName -eq "OutputObjectFromCsvSingle") { - $AuditResults = Import-Csv -Path $CsvPath | ForEach-Object { - $params = @{ - Rec = $_.Rec - Result = [bool]$_.Result - Status = $_.Status - Details = $_.Details - FailureReason = $_.FailureReason + Begin { + $createdFiles = @() # Initialize an array to keep track of created files + if ($ExportToExcel) { + Assert-ModuleAvailability -ModuleName ImportExcel -RequiredVersion "7.8.9" + } + if ($PSCmdlet.ParameterSetName -like "ExportAllResultsFromCsv" -or $PSCmdlet.ParameterSetName -eq "OutputObjectFromCsvSingle") { + $AuditResults = Import-Csv -Path $CsvPath | ForEach-Object { + $params = @{ + Rec = $_.Rec + Result = [bool]$_.Result + Status = $_.Status + Details = $_.Details + FailureReason = $_.FailureReason + } + Initialize-CISAuditResult @params } - Initialize-CISAuditResult @params } - } - - if ($ExportAllTests) { - $TestNumbers = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4" - } - - $results = @() - - $testsToProcess = if ($OutputTestNumber) { @($OutputTestNumber) } else { $TestNumbers } - - foreach ($test in $testsToProcess) { - $auditResult = $AuditResults | Where-Object { $_.Rec -eq $test } - if (-not $auditResult) { - Write-Information "No audit results found for the test number $test." - continue + if ($ExportAllTests) { + $TestNumbers = "1.1.1", "1.3.1", "6.1.2", "6.1.3", "7.3.4" } - - switch ($test) { - "6.1.2" { - $details = $auditResult.Details - if ($details -ne "No M365 E3 licenses found.") { - $csv = $details | ConvertFrom-Csv -Delimiter '|' - } - else { - $csv = $null - } - - if ($null -ne $csv) { - foreach ($row in $csv) { - $row.AdminActionsMissing = (Get-Action -AbbreviatedActions $row.AdminActionsMissing.Split(',') -ReverseActionType Admin | Where-Object { $_ -notin @("MailItemsAccessed", "Send") }) -join ',' - $row.DelegateActionsMissing = (Get-Action -AbbreviatedActions $row.DelegateActionsMissing.Split(',') -ReverseActionType Delegate | Where-Object { $_ -notin @("MailItemsAccessed") }) -join ',' - $row.OwnerActionsMissing = (Get-Action -AbbreviatedActions $row.OwnerActionsMissing.Split(',') -ReverseActionType Owner | Where-Object { $_ -notin @("MailItemsAccessed", "Send") }) -join ',' + $results = @() + $testsToProcess = if ($OutputTestNumber) { @($OutputTestNumber) } else { $TestNumbers } + } + Process { + foreach ($test in $testsToProcess) { + $auditResult = $AuditResults | Where-Object { $_.Rec -eq $test } + if (-not $auditResult) { + Write-Information "No audit results found for the test number $test." + continue + } + switch ($test) { + "6.1.2" { + $details = $auditResult.Details + if ($details -ne "No M365 E3 licenses found.") { + $csv = $details | ConvertFrom-Csv -Delimiter '|' } - $newObjectDetails = $csv - } - else { - $newObjectDetails = $details - } - $results += [PSCustomObject]@{ TestNumber = $test; Details = $newObjectDetails } - } - "6.1.3" { - $details = $auditResult.Details - if ($details -ne "No M365 E5 licenses found.") { - $csv = $details | ConvertFrom-Csv -Delimiter '|' - } - else { - $csv = $null - } - - if ($null -ne $csv) { - foreach ($row in $csv) { - $row.AdminActionsMissing = (Get-Action -AbbreviatedActions $row.AdminActionsMissing.Split(',') -ReverseActionType Admin) -join ',' - $row.DelegateActionsMissing = (Get-Action -AbbreviatedActions $row.DelegateActionsMissing.Split(',') -ReverseActionType Delegate) -join ',' - $row.OwnerActionsMissing = (Get-Action -AbbreviatedActions $row.OwnerActionsMissing.Split(',') -ReverseActionType Owner) -join ',' + else { + $csv = $null } - $newObjectDetails = $csv - } - else { - $newObjectDetails = $details - } - $results += [PSCustomObject]@{ TestNumber = $test; Details = $newObjectDetails } - } - Default { - $details = $auditResult.Details - $csv = $details | ConvertFrom-Csv -Delimiter '|' - $results += [PSCustomObject]@{ TestNumber = $test; Details = $csv } - } - } - } - - if ($ExportPath) { - $timestamp = (Get-Date).ToString("yyyy.MM.dd_HH.mm.ss") - $exportedTests = @() - - foreach ($result in $results) { - $testDef = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $result.TestNumber } - if ($testDef) { - $fileName = "$ExportPath\$($timestamp)_$($result.TestNumber).$($testDef.TestFileName -replace '\.ps1$').csv" - if ($result.Details.Count -eq 0) { - Write-Information "No results found for test number $($result.TestNumber)." -InformationAction Continue - } - else { - if (($result.Details -ne "No M365 E3 licenses found.") -and ($result.Details -ne "No M365 E5 licenses found.")) { - if ($ExportToExcel) { - $xlsxPath = [System.IO.Path]::ChangeExtension($fileName, '.xlsx') - $result.Details | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2 + if ($null -ne $csv) { + foreach ($row in $csv) { + $row.AdminActionsMissing = (Get-Action -AbbreviatedActions $row.AdminActionsMissing.Split(',') -ReverseActionType Admin | Where-Object { $_ -notin @("MailItemsAccessed", "Send") }) -join ',' + $row.DelegateActionsMissing = (Get-Action -AbbreviatedActions $row.DelegateActionsMissing.Split(',') -ReverseActionType Delegate | Where-Object { $_ -notin @("MailItemsAccessed") }) -join ',' + $row.OwnerActionsMissing = (Get-Action -AbbreviatedActions $row.OwnerActionsMissing.Split(',') -ReverseActionType Owner | Where-Object { $_ -notin @("MailItemsAccessed", "Send") }) -join ',' } - else { - $result.Details | Export-Csv -Path $fileName -NoTypeInformation - } - $exportedTests += $result.TestNumber + $newObjectDetails = $csv } + else { + $newObjectDetails = $details + } + $results += [PSCustomObject]@{ TestNumber = $test; Details = $newObjectDetails } + } + "6.1.3" { + $details = $auditResult.Details + if ($details -ne "No M365 E5 licenses found.") { + $csv = $details | ConvertFrom-Csv -Delimiter '|' + } + else { + $csv = $null + } + if ($null -ne $csv) { + foreach ($row in $csv) { + $row.AdminActionsMissing = (Get-Action -AbbreviatedActions $row.AdminActionsMissing.Split(',') -ReverseActionType Admin) -join ',' + $row.DelegateActionsMissing = (Get-Action -AbbreviatedActions $row.DelegateActionsMissing.Split(',') -ReverseActionType Delegate) -join ',' + $row.OwnerActionsMissing = (Get-Action -AbbreviatedActions $row.OwnerActionsMissing.Split(',') -ReverseActionType Owner) -join ',' + } + $newObjectDetails = $csv + } + else { + $newObjectDetails = $details + } + $results += [PSCustomObject]@{ TestNumber = $test; Details = $newObjectDetails } + } + Default { + $details = $auditResult.Details + $csv = $details | ConvertFrom-Csv -Delimiter '|' + $results += [PSCustomObject]@{ TestNumber = $test; Details = $csv } } } } - if ($exportedTests.Count -gt 0) { - Write-Information "The following tests were exported: $($exportedTests -join ', ')" -InformationAction Continue - } - else { + } + End { + if ($ExportPath) { + $timestamp = (Get-Date).ToString("yyyy.MM.dd_HH.mm.ss") + $exportedTests = @() + foreach ($result in $results) { + $testDef = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $result.TestNumber } + if ($testDef) { + $fileName = "$ExportPath\$($timestamp)_$($result.TestNumber).$($testDef.TestFileName -replace '\.ps1$').csv" + if ($result.Details.Count -eq 0) { + Write-Information "No results found for test number $($result.TestNumber)." -InformationAction Continue + } + else { + if (($result.Details -ne "No M365 E3 licenses found.") -and ($result.Details -ne "No M365 E5 licenses found.")) { + if ($ExportToExcel) { + $xlsxPath = [System.IO.Path]::ChangeExtension($fileName, '.xlsx') + $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 + } + } + } + } + if ($exportedTests.Count -gt 0) { + Write-Information "The following tests were exported: $($exportedTests -join ', ')" -InformationAction Continue + } + 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" -InformationAction Continue + } + else { + Write-Information "No specified tests were included in the export." -InformationAction Continue + } + } 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`_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 + } + } + # 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 + elseif ($OutputTestNumber) { + if ($results[0].Details) { + return $results[0].Details } else { - Write-Information "No specified tests were included in the export." -InformationAction Continue + Write-Information "No results found for test number $($OutputTestNumber)." -InformationAction Continue } } - - if ($ExportOriginalTests) { - # 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`_M365FoundationsAudit.csv" - if ($ExportToExcel) { - $xlsxPath = [System.IO.Path]::ChangeExtension($originalFileName, '.xlsx') - $updatedAuditResults | Export-Excel -Path $xlsxPath -WorksheetName Table -TableName Table -AutoSize -TableStyle Medium2 - } - else { - $updatedAuditResults | Export-Csv -Path $originalFileName -NoTypeInformation - } - } - } - elseif ($OutputTestNumber) { - if ($results[0].Details) { - return $results[0].Details - } else { - Write-Information "No results found for test number $($OutputTestNumber)." -InformationAction Continue + Write-Error "No valid operation specified. Please provide valid parameters." } - } - else { - Write-Error "No valid operation specified. Please provide valid parameters." + # Output the created files at the end + #if ($createdFiles.Count -gt 0) { + ########### $createdFiles + #} } } diff --git a/source/en-US/M365FoundationsCISReport-help.xml b/source/en-US/M365FoundationsCISReport-help.xml index dce77ce..d9a795e 100644 --- a/source/en-US/M365FoundationsCISReport-help.xml +++ b/source/en-US/M365FoundationsCISReport-help.xml @@ -277,7 +277,15 @@ - [CISAuditResult[]], [string] + [CISAuditResult[]] - An array of CISAuditResult objects. + + + + + + + + [string] - A path to a CSV file. @@ -287,7 +295,7 @@ - [PSCustomObject] + [PSCustomObject] - A custom object containing the path to the zip file and its hash.