6 Commits

Author SHA1 Message Date
Doug Rios
4db0fd3742 Merge pull request #100 from CriticalSolutionsNetwork/Whatif-Bugfix
fix: whatif
2024-06-09 10:42:00 -05:00
DrIOS
83a8e31aa5 docs: Update CHANGELOG 2024-06-09 10:38:56 -05:00
DrIOS
b9de0638bb add: Output type to functions 2024-06-09 10:36:37 -05:00
DrIOS
5a0475c253 docs: update CHANGELOG.md 2024-06-09 09:50:55 -05:00
DrIOS
312aabc81c fix: whatif output and module install 2024-06-09 09:40:18 -05:00
DrIOS
e6da6d9d47 fix: whatif 2024-06-08 20:42:38 -05:00
23 changed files with 219 additions and 74 deletions

View File

@@ -6,6 +6,17 @@ The format is based on and uses the types of changes according to [Keep a Change
### Added
- Added output type to functions.
### Fixed
- Whatif support for `Invoke-M365SecurityAudit`.
- Whatif module output and module install process.
## [0.1.7] - 2024-06-08
### Added
- Added pipeline support to `Sync-CISExcelAndCsvData` function for `[CISAuditResult[]]` input.
### Changed
@@ -16,6 +27,12 @@ The format is based on and uses the types of changes according to [Keep a Change
- Enhanced `Invoke-M365SecurityAudit` to allow flexible inclusion and exclusion of specific recommendations, IG filters, and profile levels.
- SupportsShoudProcess to also bypass connection checks in `Invoke-M365SecurityAudit` as well as Disconnect-M365Suite.
## [0.1.6] - 2024-06-08
### Added
- Added pipeline support to `Sync-CISExcelAndCsvData` function for `[CISAuditResult[]]` input.
## [0.1.5] - 2024-06-08
### Added

View File

@@ -4,7 +4,7 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
<#
$ver = "v0.1.6"
$ver = "v0.1.7"
git checkout main
git pull origin main
git tag -a $ver -m "Release version $ver refactor Update"

View File

@@ -1,29 +1,33 @@
function Assert-ModuleAvailability {
[OutputType([void]) ]
param(
[string]$ModuleName,
[string]$RequiredVersion,
[string]$SubModuleName
[string[]]$SubModules = @()
)
try {
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }
if ($null -eq $module) {$auditResult.Profile
Write-Host "Installing $ModuleName module..."
if ($null -eq $module) {
Write-Information "Installing $ModuleName module..." -InformationAction Continue
Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null
}
elseif ($module.Version -lt [version]$RequiredVersion) {
Write-Host "Updating $ModuleName module to required version..."
Write-Information "Updating $ModuleName module to required version..." -InformationAction Continue
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
}
else {
Write-Host "$ModuleName module is already at required version or newer."
Write-Information "$ModuleName module is already at required version or newer." -InformationAction Continue
}
if ($SubModuleName) {
Import-Module -Name "$ModuleName.$SubModuleName" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
if ($SubModules.Count -gt 0) {
foreach ($subModule in $SubModules) {
Write-Information "Importing submodule $ModuleName.$subModule..." -InformationAction Continue
Import-Module -Name "$ModuleName.$subModule" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
else {
} else {
Write-Information "Importing module $ModuleName..." -InformationAction Continue
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
}

View File

@@ -1,4 +1,5 @@
function Connect-M365Suite {
[OutputType([void])]
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)]

View File

@@ -1,4 +1,5 @@
function Disconnect-M365Suite {
[OutputType([void])]
param (
[Parameter(Mandatory)]
[string[]]$RequiredConnections

View File

@@ -1,5 +1,9 @@
function Format-MissingAction {
param ([array]$missingActions)
[CmdletBinding()]
[OutputType([hashtable])]
param (
[array]$missingActions
)
$actionGroups = @{
"Admin" = @()

View File

@@ -0,0 +1,19 @@
function Format-RequiredModuleList {
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true)]
[System.Object[]]$RequiredModules
)
$requiredModulesFormatted = ""
foreach ($module in $RequiredModules) {
if ($module.SubModules -and $module.SubModules.Count -gt 0) {
$subModulesFormatted = $module.SubModules -join ', '
$requiredModulesFormatted += "$($module.ModuleName) (SubModules: $subModulesFormatted), "
} else {
$requiredModulesFormatted += "$($module.ModuleName), "
}
}
return $requiredModulesFormatted.TrimEnd(", ")
}

View File

@@ -1,4 +1,6 @@
function Get-MostCommonWord {
[CmdletBinding()]
[OutputType([string])]
param (
[Parameter(Mandatory = $true)]
[string[]]$InputStrings

View File

@@ -12,22 +12,16 @@ function Get-RequiredModule {
switch ($PSCmdlet.ParameterSetName) {
'AuditFunction' {
return @(
@{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0" },
@{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Authentication" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Users" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Groups" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "DirectoryObjects" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Domains" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Reports" },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModuleName = "Mail" },
@{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000" },
@{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0" }
@{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0"; SubModules = @() },
@{ ModuleName = "AzureAD"; RequiredVersion = "2.0.2.182"; SubModules = @() },
@{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModules = @("Groups", "DeviceManagement", "Users", "Identity.DirectoryManagement", "Identity.SignIns") },
@{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000"; SubModules = @() },
@{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0"; SubModules = @() }
)
}
'SyncFunction' {
return @(
@{ ModuleName = "ImportExcel"; RequiredVersion = "7.8.9" }
@{ ModuleName = "ImportExcel"; RequiredVersion = "7.8.9"; SubModules = @() }
)
}
default {

View File

@@ -1,4 +1,6 @@
function Get-TestDefinitionsObject {
[CmdletBinding()]
[OutputType([object[]])]
param (
[Parameter(Mandatory = $true)]
[object[]]$TestDefinitions,

View File

@@ -0,0 +1,28 @@
function Get-UniqueConnection {
[CmdletBinding()]
[OutputType([string[]])]
param (
[Parameter(Mandatory = $true)]
[string[]]$Connections
)
$uniqueConnections = @()
if ($Connections -contains "AzureAD" -or $Connections -contains "AzureAD | EXO" -or $Connections -contains "AzureAD | EXO | Microsoft Graph") {
$uniqueConnections += "AzureAD"
}
if ($Connections -contains "Microsoft Graph" -or $Connections -contains "AzureAD | EXO | Microsoft Graph") {
$uniqueConnections += "Microsoft Graph"
}
if ($Connections -contains "EXO" -or $Connections -contains "AzureAD | EXO" -or $Connections -contains "Microsoft Teams | EXO" -or $Connections -contains "AzureAD | EXO | Microsoft Graph") {
$uniqueConnections += "EXO"
}
if ($Connections -contains "SPO") {
$uniqueConnections += "SPO"
}
if ($Connections -contains "Microsoft Teams" -or $Connections -contains "Microsoft Teams | EXO") {
$uniqueConnections += "Microsoft Teams"
}
return $uniqueConnections | Sort-Object -Unique
}

View File

@@ -1,5 +1,6 @@
function Initialize-CISAuditResult {
[CmdletBinding()]
[OutputType([CISAuditResult])]
param (
[Parameter(Mandatory = $true)]
[string]$Rec,

View File

@@ -1,4 +1,5 @@
function Invoke-TestFunction {
[OutputType([CISAuditResult[]])]
param (
[Parameter(Mandatory = $true)]
[PSObject]$FunctionFile,

View File

@@ -1,4 +1,5 @@
function Measure-AuditResult {
[OutputType([void])]
param (
[Parameter(Mandatory = $true)]
[System.Collections.ArrayList]$AllAuditResults,

View File

@@ -1,5 +1,6 @@
function Merge-CISExcelAndCsvData {
[CmdletBinding(DefaultParameterSetName = 'CsvInput')]
[OutputType([PSCustomObject[]])]
param (
[Parameter(Mandatory = $true)]
[string]$ExcelPath,

View File

@@ -1,4 +1,6 @@
function New-MergedObject {
[CmdletBinding()]
[OutputType([PSCustomObject])]
param (
[Parameter(Mandatory = $true)]
[psobject]$ExcelItem,

View File

@@ -1,4 +1,5 @@
function Update-CISExcelWorksheet {
[OutputType([void])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]

View File

@@ -1,4 +1,5 @@
function Update-WorksheetCell {
function Update-WorksheetCell {
[OutputType([void])]
param (
$Worksheet,
$Data,
@@ -25,4 +26,4 @@
}
$rowIndex++
}
}
}

View File

@@ -114,7 +114,6 @@
.LINK
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Invoke-M365SecurityAudit
#>
function Invoke-M365SecurityAudit {
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')]
[OutputType([CISAuditResult[]])]
@@ -183,12 +182,18 @@ function Invoke-M365SecurityAudit {
$script:MaximumFunctionCount = 8192
}
# Ensure required modules are installed
if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Check for required modules", "Check")) {
$requiredModules = Get-RequiredModule -AuditFunction
# Format the required modules list
$requiredModulesFormatted = Format-RequiredModuleList -RequiredModules $requiredModules
# Check and install required modules if necessary
if (!($NoModuleCheck) -and $PSCmdlet.ShouldProcess("Check for required modules: $requiredModulesFormatted", "Check")) {
foreach ($module in $requiredModules) {
Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModuleName $module.SubModuleName
Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModules $module.SubModules
}
}
# Load test definitions from CSV
$testDefinitionsPath = Join-Path -Path $PSScriptRoot -ChildPath "helper\TestDefinitions.csv"
$testDefinitions = Import-Csv -Path $testDefinitionsPath
@@ -207,7 +212,7 @@ function Invoke-M365SecurityAudit {
$testDefinitions = Get-TestDefinitionsObject @params
# Extract unique connections needed
$requiredConnections = $testDefinitions.Connection | Sort-Object -Unique
if ($requiredConnections -contains 'SPO'){
if ($requiredConnections -contains 'SPO') {
if (-not $TenantAdminUrl) {
$requiredConnections = $requiredConnections | Where-Object { $_ -ne 'SPO' }
$testDefinitions = $testDefinitions | Where-Object { $_.Connection -ne 'SPO' }
@@ -235,10 +240,14 @@ function Invoke-M365SecurityAudit {
$currentTestIndex = 0
# Establishing connections if required
if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services", "Connect")) {
$actualUniqueConnections = Get-UniqueConnection -Connections $requiredConnections
if (!($DoNotConnect) -and $PSCmdlet.ShouldProcess("Establish connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Connect")) {
Write-Information "Establishing connections to Microsoft 365 services: $($actualUniqueConnections -join ', ')" -InformationAction Continue
Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections
}
Write-Information "A total of $($totalTests) tests were selected to run..." -InformationAction Continue
# Import the test functions
$testFiles | ForEach-Object {
$currentTestIndex++
@@ -269,11 +278,11 @@ function Invoke-M365SecurityAudit {
}
End {
if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services", "Disconnect")) {
if (!($DoNotDisconnect) -and $PSCmdlet.ShouldProcess("Disconnect from Microsoft 365 services: $($actualUniqueConnections -join ', ')", "Disconnect")) {
# Clean up sessions
Disconnect-M365Suite -RequiredConnections $requiredConnections
}
if ($PSCmdlet.ShouldProcess("Measure and display audit results", "Measure")) {
if ($PSCmdlet.ShouldProcess("Measure and display audit results for $($totalTests) tests", "Measure")) {
# Call the private function to calculate and display results
Measure-AuditResult -AllAuditResults $allAuditResults -FailedTests $script:FailedTests
# Return all collected audit results
@@ -281,3 +290,4 @@ function Invoke-M365SecurityAudit {
}
}
}

View File

@@ -44,6 +44,7 @@ If the SkipUpdate switch is used, the function returns an array of custom object
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Sync-CISExcelAndCsvData
#>
function Sync-CISExcelAndCsvData {
[OutputType([void], [PSCustomObject[]])]
[CmdletBinding(DefaultParameterSetName = 'CsvInput')]
param (
[Parameter(Mandatory = $true)]

View File

@@ -42,7 +42,7 @@ function Test-PasswordNeverExpirePolicy {
$failureReasons = if ($isCompliant) {
"N/A"
} else {
"Password expiration is not set to never expire for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30"
"Password expiration is not set to never expire for domain $domainName. Run the following command to remediate: `nUpdate-MgDomain -DomainId $domainName -PasswordValidityPeriodInDays 2147483647 -PasswordNotificationWindowInDays 30`n"
}
$details = "$domainName|$passwordPolicy days|$isDefault"

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