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:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
63
source/Private/Get-TestDefinitionsObject.ps1
Normal file
63
source/Private/Get-TestDefinitionsObject.ps1
Normal 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
|
||||
}
|
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
22
source/Private/Test-IsAdmin.ps1
Normal file
22
source/Private/Test-IsAdmin.ps1
Normal 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)
|
||||
}
|
212
source/Private/Write-AuditLog.ps1
Normal file
212
source/Private/Write-AuditLog.ps1
Normal 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)"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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
|
||||
|
|
27
tests/Unit/Private/Get-TestDefinitionsObject.tests.ps1
Normal file
27
tests/Unit/Private/Get-TestDefinitionsObject.tests.ps1
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
tests/Unit/Private/Test-IsAdmin.tests.ps1
Normal file
27
tests/Unit/Private/Test-IsAdmin.tests.ps1
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
tests/Unit/Private/Write-AuditLog.tests.ps1
Normal file
27
tests/Unit/Private/Write-AuditLog.tests.ps1
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user