add new function for exporting nested tables

This commit is contained in:
DrIOS
2024-06-15 14:49:13 -05:00
parent c4b2427539
commit 6752e56be9
6 changed files with 353 additions and 23 deletions

View File

@@ -0,0 +1,113 @@
function Get-Action {
[CmdletBinding(DefaultParameterSetName = "GetDictionaries")]
param (
[Parameter(Position = 0, ParameterSetName = "GetDictionaries")]
[switch]$Dictionaries,
[Parameter(Position = 0, ParameterSetName = "ConvertActions")]
[string[]]$Actions,
[Parameter(Position = 1, Mandatory = $true, ParameterSetName = "ConvertActions")]
[ValidateSet("Admin", "Delegate", "Owner")]
[string]$ActionType,
[Parameter(Position = 0, ParameterSetName = "ReverseActions")]
[string[]]$AbbreviatedActions,
[Parameter(Position = 1, Mandatory = $true, ParameterSetName = "ReverseActions")]
[ValidateSet("Admin", "Delegate", "Owner")]
[string]$ReverseActionType
)
$Dictionary = @{
AdminActions = @{
ApplyRecord = 'AR'
Copy = 'CP'
Create = 'CR'
FolderBind = 'FB'
HardDelete = 'HD'
MailItemsAccessed = 'MIA'
Move = 'MV'
MoveToDeletedItems = 'MTDI'
SendAs = 'SA'
SendOnBehalf = 'SOB'
Send = 'SD'
SoftDelete = 'SD'
Update = 'UP'
UpdateCalendarDelegation = 'UCD'
UpdateFolderPermissions = 'UFP'
UpdateInboxRules = 'UIR'
}
DelegateActions = @{
ApplyRecord = 'AR'
Create = 'CR'
FolderBind = 'FB'
HardDelete = 'HD'
MailItemsAccessed = 'MIA'
Move = 'MV'
MoveToDeletedItems = 'MTDI'
SendAs = 'SA'
SendOnBehalf = 'SOB'
SoftDelete = 'SD'
Update = 'UP'
UpdateFolderPermissions = 'UFP'
UpdateInboxRules = 'UIR'
}
OwnerActions = @{
ApplyRecord = 'AR'
Create = 'CR'
HardDelete = 'HD'
MailboxLogin = 'ML'
MailItemsAccessed = 'MIA'
Move = 'MV'
MoveToDeletedItems = 'MTDI'
Send = 'SD'
SoftDelete = 'SD'
Update = 'UP'
UpdateCalendarDelegation = 'UCD'
UpdateFolderPermissions = 'UFP'
UpdateInboxRules = 'UIR'
}
}
switch ($PSCmdlet.ParameterSetName) {
"GetDictionaries" {
return $Dictionary
}
"ConvertActions" {
$actionDictionary = switch ($ActionType) {
"Admin" { $Dictionary.AdminActions }
"Delegate" { $Dictionary.DelegateActions }
"Owner" { $Dictionary.OwnerActions }
}
$abbreviatedActions = @()
foreach ($action in $Actions) {
if ($actionDictionary.ContainsKey($action)) {
$abbreviatedActions += $actionDictionary[$action]
}
}
return $abbreviatedActions
}
"ReverseActions" {
$reverseDictionary = @{}
$originalDictionary = switch ($ReverseActionType) {
"Admin" { $Dictionary.AdminActions }
"Delegate" { $Dictionary.DelegateActions }
"Owner" { $Dictionary.OwnerActions }
}
foreach ($key in $originalDictionary.Keys) {
$reverseDictionary[$originalDictionary[$key]] = $key
}
$fullNames = @()
foreach ($abbrAction in $AbbreviatedActions) {
if ($reverseDictionary.ContainsKey($abbrAction)) {
$fullNames += $reverseDictionary[$abbrAction]
}
}
return $fullNames
}
}
}

View File

@@ -0,0 +1,110 @@
function Export-M365SecurityAuditTable {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ParameterSetName = "FromAuditResultsSingle")]
[Parameter(Mandatory = $true, ParameterSetName = "FromAuditResultsMultiple")]
[CISAuditResult[]]$AuditResults,
[Parameter(Mandatory = $true, ParameterSetName = "FromCsvSingle")]
[Parameter(Mandatory = $true, ParameterSetName = "FromCsvMultiple")]
[ValidateScript({ Test-Path $_ -and (Get-Item $_).PSIsContainer -eq $false })]
[string]$CsvPath,
[Parameter(Mandatory = $false, Position = 1, ParameterSetName = "FromAuditResultsSingle")]
[Parameter(Mandatory = $false, Position = 1, ParameterSetName = "FromCsvSingle")]
[ValidateSet("6.1.2","6.1.3","7.3.4")]
[string]$TestNumber,
[Parameter(Mandatory = $false, Position = 1, ParameterSetName = "FromAuditResultsMultiple")]
[Parameter(Mandatory = $false, Position = 1, ParameterSetName = "FromCsvMultiple")]
[ValidateSet("6.1.2","6.1.3","7.3.4")]
[string[]]$TestNumbers,
[Parameter(Mandatory = $true, Position = 2, ParameterSetName = "FromAuditResultsMultiple")]
[Parameter(Mandatory = $true, Position = 2, ParameterSetName = "FromCsvMultiple")]
[Parameter(Mandatory = $false, Position = 2, ParameterSetName = "FromAuditResultsSingle")]
[Parameter(Mandatory = $false, Position = 2, ParameterSetName = "FromCsvSingle")]
[string]$ExportPath
)
if ($PSCmdlet.ParameterSetName -like "FromCsv*") {
$AuditResults = Import-Csv -Path $CsvPath | ForEach-Object {
[CISAuditResult]::new(
$_.Status,
$_.ELevel,
$_.ProfileLevel,
[bool]$_.Automated,
$_.Connection,
$_.Rec,
$_.RecDescription,
$_.CISControlVer,
$_.CISControl,
$_.CISDescription,
[bool]$_.IG1,
[bool]$_.IG2,
[bool]$_.IG3,
[bool]$_.Result,
$_.Details,
$_.FailureReason
)
}
}
#$script:TestDefinitionsObject = Import-Csv -Path .\source\helper\TestDefinitions.csv
if (-not $TestNumbers -and -not $TestNumber) {
$TestNumbers = "6.1.2","6.1.3","7.3.4"
if (-not $ExportPath) {
Write-Error "ExportPath is required when exporting all test results."
return
}
}
$results = @()
$testsToProcess = if ($TestNumber) { @($TestNumber) } else { $TestNumbers }
foreach ($test in $testsToProcess) {
$auditResult = $AuditResults | Where-Object { $_.Rec -eq $test }
if (-not $auditResult) {
Write-Error "No audit results found for the test number $test."
continue
}
switch ($test) {
"6.1.3" {
$details = $auditResult.Details
$csv = $details | ConvertFrom-Csv -Delimiter '|'
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
$results += [PSCustomObject]@{ TestNumber = $test; Details = $newObjectDetails }
}
"7.3.4" {
# Placeholder for specific logic for 7.3.4 if needed
}
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")
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"
$result.Details | Out-File -FilePath $fileName
}
}
} else {
return $results.Details
}
}

View File

@@ -64,7 +64,7 @@ function Test-BlockMailForwarding {
if ($nonCompliantSpamPoliciesArray.Count -gt 0) {
# Fail Condition B
$failureReasons += "Outbound spam policies allowing automatic forwarding found."
$details += "Outbound Spam Policies Details:`nPolicy|AutoForwardingMode"
$details += "Policy|AutoForwardingMode"
$details += $nonCompliantSpamPoliciesArray | ForEach-Object {
"$($_.Name)|$($_.AutoForwardingMode)"
}

View File

@@ -28,14 +28,15 @@ function Test-MailboxAuditingE5 {
# - Condition D: AuditOwner actions do not include all of the following: ApplyRecord, HardDelete, MailItemsAccessed, MoveToDeletedItems, Send, SoftDelete, Update, UpdateCalendarDelegation, UpdateFolderPermissions, UpdateInboxRules.
$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")
$founde5Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber }
$actionDictionaries = Get-Action -Dictionaries
$AdminActions = $actionDictionaries.AdminActions.Keys
$DelegateActions = $actionDictionaries.DelegateActions.Keys
$OwnerActions = $actionDictionaries.OwnerActions.Keys
$allFailures = @()
#$allUsers = Get-AzureADUser -All $true
$founde5Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq $e5SkuPartNumber }
$processedUsers = @{} # Dictionary to track processed users
$processedUsers = @{}
$recnum = "6.1.3"
}
@@ -50,35 +51,39 @@ function Test-MailboxAuditingE5 {
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."
$mailbox = $mailboxes | Where-Object { $_.UserPrincipalName -eq $user.UserPrincipalName }
$userUPN = $user.UserPrincipalName
#$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
$missingActions = @()
$missingAdminActions = @()
$missingDelegateActions = @()
$missingOwnerActions = @()
if ($mailbox.AuditEnabled) {
# Validate Admin actions
foreach ($action in $AdminActions) {
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" } # Condition B
if ($mailbox.AuditAdmin -notcontains $action) {
$missingAdminActions += (Get-Action -Actions $action -ActionType "Admin") # Condition B
}
}
# Validate Delegate actions
foreach ($action in $DelegateActions) {
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" } # Condition C
if ($mailbox.AuditDelegate -notcontains $action) {
$missingDelegateActions += (Get-Action -Actions $action -ActionType "Delegate") # Condition C
}
}
# Validate Owner actions
foreach ($action in $OwnerActions) {
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" } # Condition D
if ($mailbox.AuditOwner -notcontains $action) {
$missingOwnerActions += (Get-Action -Actions $action -ActionType "Owner") # Condition D
}
}
if ($missingActions.Count -gt 0) {
$formattedActions = Format-MissingAction -missingActions $missingActions
$allFailures += "$userUPN|True|$($formattedActions.Admin)|$($formattedActions.Delegate)|$($formattedActions.Owner)"
if ($missingAdminActions.Count -gt 0 -or $missingDelegateActions.Count -gt 0 -or $missingOwnerActions.Count -gt 0) {
$allFailures += "$userUPN|True|$($missingAdminActions -join ',')|$($missingDelegateActions -join ',')|$($missingOwnerActions -join ',')"
}
}
else {
$allFailures += "$userUPN|False|||"
$allFailures += "$userUPN|False|||" # Condition A for fail
}
# Mark the user as processed
@@ -86,7 +91,12 @@ function Test-MailboxAuditingE5 {
}
# Prepare failure reasons and details based on compliance
$failureReasons = if ($allFailures.Count -eq 0) { "N/A" } else { "Audit issues detected." }
if ($allFailures.Count -eq 0) {
$failureReasons = "N/A"
}
else {
$failureReasons = "Audit issues detected."
}
$details = if ($allFailures.Count -eq 0) {
"All Office E5 users have correct mailbox audit settings." # Condition A for pass
}
@@ -130,14 +140,13 @@ function Test-MailboxAuditingE5 {
}
end {
#$verbosePreference = 'Continue'
$detailsLength = $details.Length
Write-Verbose "Character count of the details: $detailsLength"
if ($detailsLength -gt 32767) {
Write-Verbose "Warning: The character count exceeds the limit for Excel cells."
}
#$verbosePreference = 'SilentlyContinue'
return $auditResult
}
}
}

View File

@@ -0,0 +1,27 @@
$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path
$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName
Import-Module $ProjectName
InModuleScope $ProjectName {
Describe Get-PrivateFunction {
Context 'Default' {
BeforeEach {
$return = Get-PrivateFunction -PrivateData 'string'
}
It 'Returns a single object' {
($return | Measure-Object).Count | Should -Be 1
}
It 'Returns a string based on the parameter PrivateData' {
$return | Should -Be 'string'
}
}
}
}

View File

@@ -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
}
}
}