Merge pull request #11 from CriticalSolutionsNetwork/Add-additional-properties-to-merged-doc

Add additional properties to merged doc / refactor connections and filtering of tests.
This commit is contained in:
Doug Rios
2024-05-30 16:55:56 -05:00
committed by GitHub
13 changed files with 500 additions and 103 deletions

View File

@@ -4,6 +4,17 @@ The format is based on and uses the types of changes according to [Keep a Change
## [Unreleased]
### Added
- Test definitions filter function.
- Logging function for future use.
- Test grade written to console.
### Changed
- Updated sync function to include connection info.
- Refactored connect/disconnect functions to evaluate needed connections.
## [0.1.3] - 2024-05-28
### Added

View File

@@ -4,10 +4,10 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
<#
$ver = "v0.1.2"
$ver = "v0.1.3"
git checkout main
git pull origin main
git tag -a $ver -m "Release version $ver Bugfix Update"
git tag -a $ver -m "Release version $ver refactor Update"
git push origin $ver
"Fix: PR #37"
git push origin $ver

View File

@@ -1,56 +1,58 @@
function Connect-M365Suite {
[CmdletBinding()]
param (
# Parameter to specify the SharePoint Online Tenant Admin URL
[Parameter(Mandatory)]
[string]$TenantAdminUrl
[string]$TenantAdminUrl,
[Parameter(Mandatory)]
[string[]]$RequiredConnections
)
$VerbosePreference = "SilentlyContinue"
$VerbosePreference = "SilentlyContinue"
try {
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") {
Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Cyan
Connect-AzureAD | Out-Null
Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green
}
# Attempt to connect to Azure Active Directory
Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Cyan
Connect-AzureAD | Out-Null
Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green
# Attempt to connect to Exchange Online
Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
Connect-ExchangeOnline | Out-Null
Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green
try {
# Attempt to connect to Microsoft Graph with specified scopes
if ($RequiredConnections -contains "Microsoft Graph") {
Write-Host "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Cyan
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
}
catch {
Write-Host "Failed to connect o MgGraph, attempting device auth." -ForegroundColor Yellow
# Attempt to connect to Microsoft Graph with specified scopes
Write-Host "Connecting to Microsoft Graph using device auth with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Cyan
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -UseDeviceCode -NoWelcome | Out-Null
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
try {
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
}
catch {
Write-Host "Failed to connect to MgGraph, attempting device auth." -ForegroundColor Yellow
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -UseDeviceCode -NoWelcome | Out-Null
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
}
}
# Validate SharePoint Online Tenant Admin URL
if (-not $TenantAdminUrl) {
throw "SharePoint Online Tenant Admin URL is required."
if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
Connect-ExchangeOnline | Out-Null
Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green
}
# Attempt to connect to SharePoint Online
Write-Host "Connecting to SharePoint Online..." -ForegroundColor Cyan
Connect-SPOService -Url $TenantAdminUrl | Out-Null
Write-Host "Successfully connected to SharePoint Online." -ForegroundColor Green
if ($RequiredConnections -contains "SPO") {
Write-Host "Connecting to SharePoint Online..." -ForegroundColor Cyan
Connect-SPOService -Url $TenantAdminUrl | Out-Null
Write-Host "Successfully connected to SharePoint Online." -ForegroundColor Green
}
# Attempt to connect to Microsoft Teams
Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Cyan
Connect-MicrosoftTeams | Out-Null
Write-Host "Successfully connected to Microsoft Teams." -ForegroundColor Green
if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Cyan
Connect-MicrosoftTeams | Out-Null
Write-Host "Successfully connected to Microsoft Teams." -ForegroundColor Green
}
}
catch {
$VerbosePreference = "Continue"
Write-Host "There was an error establishing one or more connections: $_" -ForegroundColor Red
throw $_
}
$VerbosePreference = "Continue"
}

View File

@@ -1,39 +1,59 @@
function Disconnect-M365Suite {
param (
[Parameter(Mandatory)]
[string[]]$RequiredConnections
)
# Clean up sessions
try {
Write-Host "Disconnecting from Exchange Online..." -ForegroundColor Green
Disconnect-ExchangeOnline -Confirm:$false | Out-Null
if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Exchange Online..." -ForegroundColor Green
Disconnect-ExchangeOnline -Confirm:$false | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from Exchange Online: $_"
}
try {
Write-Host "Disconnecting from Azure AD..." -ForegroundColor Green
Disconnect-AzureAD | Out-Null
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") {
Write-Host "Disconnecting from Azure AD..." -ForegroundColor Green
Disconnect-AzureAD | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from Azure AD: $_"
}
try {
Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Green
Disconnect-MgGraph | Out-Null
if ($RequiredConnections -contains "Microsoft Graph") {
Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Green
Disconnect-MgGraph | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from Microsoft Graph: $_"
}
try {
Write-Host "Disconnecting from SharePoint Online..." -ForegroundColor Green
Disconnect-SPOService | Out-Null
if ($RequiredConnections -contains "SPO") {
Write-Host "Disconnecting from SharePoint Online..." -ForegroundColor Green
Disconnect-SPOService | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from SharePoint Online: $_"
}
try {
Write-Host "Disconnecting from Microsoft Teams..." -ForegroundColor Green
Disconnect-MicrosoftTeams | Out-Null
if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Microsoft Teams..." -ForegroundColor Green
Disconnect-MicrosoftTeams | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from Microsoft Teams: $_"
}
Write-Host "All sessions have been disconnected." -ForegroundColor Green
Write-Host "All necessary sessions have been disconnected." -ForegroundColor Green
}

View File

@@ -0,0 +1,63 @@
function Get-TestDefinitionsObject {
param (
[Parameter(Mandatory = $true)]
[object[]]$TestDefinitions,
[Parameter(Mandatory = $true)]
[string]$ParameterSetName,
[string]$ELevel,
[string]$ProfileLevel,
[string[]]$IncludeRecommendation,
[string[]]$SkipRecommendation
)
Write-Verbose "Initial test definitions count: $($TestDefinitions.Count)"
switch ($ParameterSetName) {
'ELevelFilter' {
Write-Verbose "Applying ELevelFilter"
if ($null -ne $ELevel -and $null -ne $ProfileLevel) {
Write-Verbose "Filtering on ELevel = $ELevel and ProfileLevel = $ProfileLevel"
$TestDefinitions = $TestDefinitions | Where-Object {
$_.ELevel -eq $ELevel -and $_.ProfileLevel -eq $ProfileLevel
}
}
elseif ($null -ne $ELevel) {
Write-Verbose "Filtering on ELevel = $ELevel"
$TestDefinitions = $TestDefinitions | Where-Object {
$_.ELevel -eq $ELevel
}
}
elseif ($null -ne $ProfileLevel) {
Write-Verbose "Filtering on ProfileLevel = $ProfileLevel"
$TestDefinitions = $TestDefinitions | Where-Object {
$_.ProfileLevel -eq $ProfileLevel
}
}
}
'IG1Filter' {
Write-Verbose "Applying IG1Filter"
$TestDefinitions = $TestDefinitions | Where-Object { $_.IG1 -eq 'TRUE' }
}
'IG2Filter' {
Write-Verbose "Applying IG2Filter"
$TestDefinitions = $TestDefinitions | Where-Object { $_.IG2 -eq 'TRUE' }
}
'IG3Filter' {
Write-Verbose "Applying IG3Filter"
$TestDefinitions = $TestDefinitions | Where-Object { $_.IG3 -eq 'TRUE' }
}
'RecFilter' {
Write-Verbose "Applying RecFilter"
$TestDefinitions = $TestDefinitions | Where-Object { $IncludeRecommendation -contains $_.Rec }
}
'SkipRecFilter' {
Write-Verbose "Applying SkipRecFilter"
$TestDefinitions = $TestDefinitions | Where-Object { $SkipRecommendation -notcontains $_.Rec }
}
}
Write-Verbose "Filtered test definitions count: $($TestDefinitions.Count)"
return $TestDefinitions
}

View File

@@ -23,11 +23,10 @@ function Merge-CISExcelAndCsvData {
foreach ($property in $excelItem.PSObject.Properties) {
$newObject | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value
}
$newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Connection' -Value $csvRow.Connection
$newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Status' -Value $csvRow.Status
$newObject | Add-Member -MemberType NoteProperty -Name 'CSV_Details' -Value $csvRow.Details
$newObject | Add-Member -MemberType NoteProperty -Name 'CSV_FailureReason' -Value $csvRow.FailureReason
return $newObject
}
@@ -37,7 +36,7 @@ function Merge-CISExcelAndCsvData {
if ($csvRow) {
CreateMergedObject -excelItem $item -csvRow $csvRow
} else {
CreateMergedObject -excelItem $item -csvRow ([PSCustomObject]@{Status=$null; Details=$null; FailureReason=$null})
CreateMergedObject -excelItem $item -csvRow ([PSCustomObject]@{Connection=$null;Status=$null; Details=$null; FailureReason=$null })
}
}

View File

@@ -0,0 +1,22 @@
function Test-IsAdmin {
<#
.SYNOPSIS
Checks if the current user is an administrator on the machine.
.DESCRIPTION
This private function returns a Boolean value indicating whether
the current user has administrator privileges on the machine.
It does this by creating a new WindowsPrincipal object, passing
in a WindowsIdentity object representing the current user, and
then checking if that principal is in the Administrator role.
.INPUTS
None.
.OUTPUTS
Boolean. Returns True if the current user is an administrator, and False otherwise.
.EXAMPLE
PS C:\> Test-IsAdmin
True
#>
# Create a new WindowsPrincipal object for the current user and check if it is in the Administrator role
(New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

View File

@@ -0,0 +1,212 @@
function Write-AuditLog {
<#
.SYNOPSIS
Writes log messages to the console and updates the script-wide log variable.
.DESCRIPTION
The Write-AuditLog function writes log messages to the console based on the severity (Verbose, Warning, or Error) and updates
the script-wide log variable ($script:LogString) with the log entry. You can use the Start, End, and EndFunction switches to
manage the lifecycle of the logging.
.INPUTS
System.String
You can pipe a string to the Write-AuditLog function as the Message parameter.
You can also pipe an object with a Severity property as the Severity parameter.
.OUTPUTS
None
The Write-AuditLog function doesn't output any objects to the pipeline. It writes messages to the console and updates the
script-wide log variable ($script:LogString).
.PARAMETER BeginFunction
Sets the message to "Begin [FunctionName] function log.", where FunctionName is the name of the calling function, and adds it to the log variable.
.PARAMETER Message
The message string to log.
.PARAMETER Severity
The severity of the log message. Accepted values are 'Information', 'Warning', and 'Error'. Defaults to 'Information'.
.PARAMETER Start
Initializes the script-wide log variable and sets the message to "Begin [FunctionName] Log.", where FunctionName is the name of the calling function.
.PARAMETER End
Sets the message to "End Log" and exports the log to a CSV file if the OutputPath parameter is provided.
.PARAMETER EndFunction
Sets the message to "End [FunctionName] log.", where FunctionName is the name of the calling function, and adds it to the log variable.
.PARAMETER OutputPath
The file path for exporting the log to a CSV file when using the End switch.
.EXAMPLE
Write-AuditLog -Message "This is a test message."
Writes a test message with the default severity (Information) to the console and adds it to the log variable.
.EXAMPLE
Write-AuditLog -Message "This is a warning message." -Severity "Warning"
Writes a warning message to the console and adds it to the log variable.
.EXAMPLE
Write-AuditLog -Start
Initializes the log variable and sets the message to "Begin [FunctionName] Log.", where FunctionName is the name of the calling function.
.EXAMPLE
Write-AuditLog -BeginFunction
Sets the message to "Begin [FunctionName] function log.", where FunctionName is the name of the calling function, and adds it to the log variable.
.EXAMPLE
Write-AuditLog -EndFunction
Sets the message to "End [FunctionName] log.", where FunctionName is the name of the calling function, and adds it to the log variable.
.EXAMPLE
Write-AuditLog -End -OutputPath "C:\Logs\auditlog.csv"
Sets the message to "End Log", adds it to the log variable, and exports the log to a CSV file.
.NOTES
Author: DrIOSx
#>
[CmdletBinding(DefaultParameterSetName = 'Default')]
param(
###
[Parameter(
Mandatory = $false,
HelpMessage = 'Input a Message string.',
Position = 0,
ParameterSetName = 'Default',
ValueFromPipeline = $true
)]
[ValidateNotNullOrEmpty()]
[string]$Message,
###
[Parameter(
Mandatory = $false,
HelpMessage = 'Information, Warning or Error.',
Position = 1,
ParameterSetName = 'Default',
ValueFromPipelineByPropertyName = $true
)]
[ValidateNotNullOrEmpty()]
[ValidateSet('Information', 'Warning', 'Error')]
[string]$Severity = 'Information',
###
[Parameter(
Mandatory = $false,
ParameterSetName = 'End'
)]
[switch]$End,
###
[Parameter(
Mandatory = $false,
ParameterSetName = 'BeginFunction'
)]
[switch]$BeginFunction,
[Parameter(
Mandatory = $false,
ParameterSetName = 'EndFunction'
)]
[switch]$EndFunction,
###
[Parameter(
Mandatory = $false,
ParameterSetName = 'Start'
)]
[switch]$Start,
###
[Parameter(
Mandatory = $false,
ParameterSetName = 'End'
)]
[string]$OutputPath
)
begin {
$ErrorActionPreference = "SilentlyContinue"
# Define variables to hold information about the command that was invoked.
$ModuleName = $Script:MyInvocation.MyCommand.Name -replace '\..*'
$callStack = Get-PSCallStack
if ($callStack.Count -gt 1) {
$FuncName = $callStack[1].Command
} else {
$FuncName = "DirectCall" # Or any other default name you prefer
}
#Write-Verbose "Funcname Name is $FuncName!" -Verbose
$ModuleVer = $MyInvocation.MyCommand.Version.ToString()
# Set the error action preference to continue.
$ErrorActionPreference = "Continue"
}
process {
try {
if (-not $Start -and -not (Test-Path variable:script:LogString)) {
throw "The logging variable is not initialized. Please call Write-AuditLog with the -Start switch or ensure $script:LogString is set."
}
$Function = $($FuncName + '.v' + $ModuleVer)
if ($Start) {
$script:LogString = @()
$Message = '+++ Begin Log | ' + $Function + ' |'
}
elseif ($BeginFunction) {
$Message = '>>> Begin Function Log | ' + $Function + ' |'
}
$logEntry = [pscustomobject]@{
Time = ((Get-Date).ToString('yyyy-MM-dd hh:mmTss'))
Module = $ModuleName
PSVersion = ($PSVersionTable.PSVersion).ToString()
PSEdition = ($PSVersionTable.PSEdition).ToString()
IsAdmin = $(Test-IsAdmin)
User = "$Env:USERDOMAIN\$Env:USERNAME"
HostName = $Env:COMPUTERNAME
InvokedBy = $Function
Severity = $Severity
Message = $Message
RunID = -1
}
if ($BeginFunction) {
$maxRunID = ($script:LogString | Where-Object { $_.InvokedBy -eq $Function } | Measure-Object -Property RunID -Maximum).Maximum
if ($null -eq $maxRunID) { $maxRunID = -1 }
$logEntry.RunID = $maxRunID + 1
}
else {
$lastRunID = ($script:LogString | Where-Object { $_.InvokedBy -eq $Function } | Select-Object -Last 1).RunID
if ($null -eq $lastRunID) { $lastRunID = 0 }
$logEntry.RunID = $lastRunID
}
if ($EndFunction) {
$FunctionStart = "$((($script:LogString | Where-Object {$_.InvokedBy -eq $Function -and $_.RunId -eq $lastRunID } | Sort-Object Time)[0]).Time)"
$startTime = ([DateTime]::ParseExact("$FunctionStart", 'yyyy-MM-dd hh:mmTss', $null))
$endTime = Get-Date
$timeTaken = $endTime - $startTime
$Message = '<<< End Function Log | ' + $Function + ' | Runtime: ' + "$($timeTaken.Minutes) min $($timeTaken.Seconds) sec"
$logEntry.Message = $Message
}
elseif ($End) {
$startTime = ([DateTime]::ParseExact($($script:LogString[0].Time), 'yyyy-MM-dd hh:mmTss', $null))
$endTime = Get-Date
$timeTaken = $endTime - $startTime
$Message = '--- End Log | ' + $Function + ' | Runtime: ' + "$($timeTaken.Minutes) min $($timeTaken.Seconds) sec"
$logEntry.Message = $Message
}
$script:LogString += $logEntry
switch ($Severity) {
'Warning' {
Write-Warning ('[WARNING] ! ' + $Message)
$UserInput = Read-Host "Warning encountered! Do you want to continue? (Y/N)"
if ($UserInput -eq 'N') {
throw "Script execution stopped by user."
}
}
'Error' { Write-Error ('[ERROR] X - ' + $FuncName + ' ' + $Message) -ErrorAction Continue }
'Verbose' { Write-Verbose ('[VERBOSE] ~ ' + $Message) }
Default { Write-Information ('[INFO] * ' + $Message) -InformationAction Continue}
}
}
catch {
throw "Write-AuditLog encountered an error (process block): $($_)"
}
}
end {
try {
if ($End) {
if (-not [string]::IsNullOrEmpty($OutputPath)) {
$script:LogString | Export-Csv -Path $OutputPath -NoTypeInformation
Write-Verbose "LogPath: $(Split-Path -Path $OutputPath -Parent)"
}
else {
throw "OutputPath is not specified for End action."
}
}
}
catch {
throw "Error in Write-AuditLog (end block): $($_.Exception.Message)"
}
}
}

View File

@@ -59,7 +59,6 @@
.LINK
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit
#>
function Invoke-M365SecurityAudit {
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')]
[OutputType([CISAuditResult[]])]
@@ -71,28 +70,28 @@ function Invoke-M365SecurityAudit {
[string]$DomainName,
# E-Level with optional ProfileLevel selection
[Parameter(ParameterSetName = 'ELevelFilter')]
[Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter')]
[ValidateSet('E3', 'E5')]
[string]$ELevel,
[Parameter(ParameterSetName = 'ELevelFilter')]
[Parameter(Mandatory = $true, ParameterSetName = 'ELevelFilter')]
[ValidateSet('L1', 'L2')]
[string]$ProfileLevel,
# IG Filters, one at a time
[Parameter(ParameterSetName = 'IG1Filter')]
[Parameter(Mandatory = $true, ParameterSetName = 'IG1Filter')]
[switch]$IncludeIG1,
[Parameter(ParameterSetName = 'IG2Filter')]
[Parameter(Mandatory = $true, ParameterSetName = 'IG2Filter')]
[switch]$IncludeIG2,
[Parameter(ParameterSetName = 'IG3Filter')]
[Parameter(Mandatory = $true, ParameterSetName = 'IG3Filter')]
[switch]$IncludeIG3,
# Inclusion of specific recommendation numbers
[Parameter(ParameterSetName = 'RecFilter')]
[Parameter(Mandatory = $true, ParameterSetName = 'RecFilter')]
[ValidateSet(
'1.1.1','1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', `
'1.1.1', '1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', `
'2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', `
'5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', `
'6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', `
@@ -103,9 +102,9 @@ function Invoke-M365SecurityAudit {
[string[]]$IncludeRecommendation,
# Exclusion of specific recommendation numbers
[Parameter(ParameterSetName = 'SkipRecFilter')]
[Parameter(Mandatory = $true, ParameterSetName = 'SkipRecFilter')]
[ValidateSet(
'1.1.1','1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', `
'1.1.1', '1.1.3', '1.2.1', '1.2.2', '1.3.1', '1.3.3', '1.3.6', '2.1.1', '2.1.2', `
'2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.1.7', '2.1.9', '3.1.1', '5.1.2.3', `
'5.1.8.1', '6.1.1', '6.1.2', '6.1.3', '6.2.1', '6.2.2', '6.2.3', '6.3.1', `
'6.5.1', '6.5.2', '6.5.3', '7.2.1', '7.2.10', '7.2.2', '7.2.3', '7.2.4', `
@@ -152,11 +151,6 @@ function Invoke-M365SecurityAudit {
# Loop through each required module and assert its availability
# Establishing connections
#if (!($DoNotConnect -or $DoNotTest)) {
# Establishing connections
if (!($DoNotConnect)) {
Connect-M365Suite -TenantAdminUrl $TenantAdminUrl
}
# Load test definitions from CSV
$testDefinitionsPath = Join-Path -Path $PSScriptRoot -ChildPath "helper\TestDefinitions.csv"
@@ -164,42 +158,23 @@ function Invoke-M365SecurityAudit {
# Load the Test Definitions into the script scope for use in other functions
$script:TestDefinitionsObject = $testDefinitions
# Apply filters based on parameter sets
switch ($PSCmdlet.ParameterSetName) {
'ELevelFilter' {
if ($null -ne $ELevel -and $null -ne $ProfileLevel) {
$testDefinitions = $testDefinitions | Where-Object {
$_.ELevel -eq $ELevel -and $_.ProfileLevel -eq $ProfileLevel
}
}
elseif ($null -ne $ELevel) {
$testDefinitions = $testDefinitions | Where-Object {
$_.ELevel -eq $ELevel
}
}
elseif ($null -ne $ProfileLevel) {
$testDefinitions = $testDefinitions | Where-Object {
$_.ProfileLevel -eq $ProfileLevel
}
}
}
'IG1Filter' {
$testDefinitions = $testDefinitions | Where-Object { $_.IG1 -eq 'TRUE' }
}
'IG2Filter' {
$testDefinitions = $testDefinitions | Where-Object { $_.IG2 -eq 'TRUE' }
}
'IG3Filter' {
$testDefinitions = $testDefinitions | Where-Object { $_.IG3 -eq 'TRUE' }
}
'RecFilter' {
$testDefinitions = $testDefinitions | Where-Object { $IncludeRecommendation -contains $_.Rec }
}
'SkipRecFilter' {
$testDefinitions = $testDefinitions | Where-Object { $SkipRecommendation -notcontains $_.Rec }
}
$params = @{
TestDefinitions = $testDefinitions
ParameterSetName = $PSCmdlet.ParameterSetName
ELevel = $ELevel
ProfileLevel = $ProfileLevel
IncludeRecommendation = $IncludeRecommendation
SkipRecommendation = $SkipRecommendation
}
$testDefinitions = Get-TestDefinitionsObject @params
# End switch ($PSCmdlet.ParameterSetName)
# Extract unique connections needed
$requiredConnections = $testDefinitions.Connection | Sort-Object -Unique
# Establishing connections if required
if (!($DoNotConnect)) {
Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections
}
# Determine which test files to load based on filtering
$testsToLoad = $testDefinitions.TestFileName | ForEach-Object { $_ -replace '.ps1$', '' }
@@ -251,8 +226,20 @@ function Invoke-M365SecurityAudit {
End {
if (!($DoNotDisconnect)) {
# Clean up sessions
Disconnect-M365Suite
Disconnect-M365Suite -RequiredConnections $requiredConnections
}
# Calculate the total number of tests
$totalTests = $allAuditResults.Count
# Calculate the number of passed tests
$passedTests = $allAuditResults.ToArray() | Where-Object { $_.Result -eq $true } | Measure-Object | Select-Object -ExpandProperty Count
# Calculate the pass percentage
$passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) }
# Display the pass percentage to the user
Write-Host "Audit completed. $passedTests out of $totalTests tests passed." -ForegroundColor Cyan
Write-Host "Your passing percentage is $passPercentage%."
# Return all collected audit results
return $allAuditResults.ToArray()
# Check if the Disconnect switch is present

View File

@@ -1,5 +1,5 @@
Index,TestFileName,Rec,RecDescription,ELevel,ProfileLevel,CISControl,CISDescription,IG1,IG2,IG3,Automated,Connection
1,Test-AdministrativeAccountCompliance.ps1,1.1.1,Ensure Administrative accounts are separate and cloud-only,E3,L1,5.4,Restrict Administrator Privileges to Dedicated Administrator Accounts,TRUE,TRUE,TRUE,FALSE,AzureAD
1,Test-AdministrativeAccountCompliance.ps1,1.1.1,Ensure Administrative accounts are separate and cloud-only,E3,L1,5.4,Restrict Administrator Privileges to Dedicated Administrator Accounts,TRUE,TRUE,TRUE,FALSE,Microsoft Graph
2,Test-GlobalAdminsCount.ps1,1.1.3,Ensure that between two and four global admins are designated,E3,L1,5.1,Establish and Maintain an Inventory of Accounts,TRUE,TRUE,TRUE,TRUE,Microsoft Graph
3,Test-ManagedApprovedPublicGroups.ps1,1.2.1,Ensure that only organizationally managed/approved public groups exist,E3,L2,3.3,Configure Data Access Control Lists,TRUE,TRUE,TRUE,TRUE,Microsoft Graph
4,Test-BlockSharedMailboxSignIn.ps1,1.2.2,Ensure sign-in to shared mailboxes is blocked,E3,L1,0,Explicitly Not Mapped,FALSE,FALSE,FALSE,TRUE,AzureAD | EXO
@@ -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,EXO
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
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
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
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
1 Index TestFileName Rec RecDescription ELevel ProfileLevel CISControl CISDescription IG1 IG2 IG3 Automated Connection
2 1 Test-AdministrativeAccountCompliance.ps1 1.1.1 Ensure Administrative accounts are separate and cloud-only E3 L1 5.4 Restrict Administrator Privileges to Dedicated Administrator Accounts TRUE TRUE TRUE FALSE AzureAD Microsoft Graph
3 2 Test-GlobalAdminsCount.ps1 1.1.3 Ensure that between two and four global admins are designated E3 L1 5.1 Establish and Maintain an Inventory of Accounts TRUE TRUE TRUE TRUE Microsoft Graph
4 3 Test-ManagedApprovedPublicGroups.ps1 1.2.1 Ensure that only organizationally managed/approved public groups exist E3 L2 3.3 Configure Data Access Control Lists TRUE TRUE TRUE TRUE Microsoft Graph
5 4 Test-BlockSharedMailboxSignIn.ps1 1.2.2 Ensure sign-in to shared mailboxes is blocked E3 L1 0 Explicitly Not Mapped FALSE FALSE FALSE TRUE AzureAD | EXO
18 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
19 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
20 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
21 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 AzureAD | EXO
22 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 AzureAD | EXO
23 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
24 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
25 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

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,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,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'
}
}
}
}