Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4db0fd3742 | ||
|
83a8e31aa5 | ||
|
b9de0638bb | ||
|
5a0475c253 | ||
|
312aabc81c | ||
|
e6da6d9d47 | ||
|
014c42b3fe | ||
|
fbfb5b5986 | ||
|
03b5bb47e2 | ||
|
9dc99636d3 | ||
|
afe657ffc0 | ||
|
702f557579 | ||
|
f855ef7d0b | ||
|
270e980a57 | ||
|
ff90669984 | ||
|
f2e799af2f | ||
|
4a4d200197 | ||
|
9199d97fc2 | ||
|
5d681f3d72 | ||
|
f926c63533 | ||
|
d5044f0bf4 | ||
|
055ab42261 | ||
|
0d97b95c6c | ||
|
c185878674 | ||
|
61063ee63c | ||
|
4115f1e83e |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -6,6 +6,37 @@ 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
|
||||
|
||||
- Updated `Connect-M365Suite` to make `TenantAdminUrl` an optional parameter.
|
||||
- Updated `Invoke-M365SecurityAudit` to make `TenantAdminUrl` an optional parameter.
|
||||
- Improved connection handling and error messaging in `Connect-M365Suite`.
|
||||
- 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
|
||||
|
||||
- Updated test definitions for CIS Microsoft 365 Foundations Benchmark for better error handling and object output when errors occur.
|
||||
- Added a parameter to the `Initialize-CISAuditResult` function to allow for a static failed object to be created when an error occurs.
|
||||
- Refactored `Invoke-M365SecurityAudit` to include a new private function `Invoke-TestFunction` for executing test functions and handling errors.
|
||||
@@ -34,8 +65,6 @@ The format is based on and uses the types of changes according to [Keep a Change
|
||||
- Added step 1 and step 2 in `Test-BlockMailForwarding` details to ensure comprehensive compliance checks.
|
||||
- Fixed the issue with the output in `Test-RestrictCustomScripts` to ensure no extra spaces between table headers and data.
|
||||
|
||||
|
||||
|
||||
## [0.1.4] - 2024-05-30
|
||||
|
||||
### Added
|
||||
|
BIN
docs/index.html
BIN
docs/index.html
Binary file not shown.
@@ -4,7 +4,7 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
|
||||
|
||||
|
||||
<#
|
||||
$ver = "v0.1.5"
|
||||
$ver = "v0.1.7"
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git tag -a $ver -m "Release version $ver refactor Update"
|
||||
@@ -14,4 +14,72 @@ Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
|
||||
# git tag -d $ver
|
||||
#>
|
||||
|
||||
# Refresh authentication to ensure the correct scopes
|
||||
gh auth refresh -s project,read:project,write:project,repo
|
||||
|
||||
# Create the project
|
||||
gh project create --owner CriticalSolutionsNetwork --title "Test Validation Project"
|
||||
|
||||
$repoOwner = "CriticalSolutionsNetwork"
|
||||
$repoName = "M365FoundationsCISReport"
|
||||
$directoryPath = ".\source\tests"
|
||||
$projectName = "Test Validation Project"
|
||||
|
||||
# Function to create GitHub issues
|
||||
function Create-GitHubIssue {
|
||||
param (
|
||||
[string]$title,
|
||||
[string]$body,
|
||||
[string]$project
|
||||
)
|
||||
|
||||
# Create the issue and add it to the specified project
|
||||
$issue = gh issue create --repo "$repoOwner/$repoName" --title "$title" --body "$body" --project "$project"
|
||||
return $issue
|
||||
}
|
||||
|
||||
# Load test definitions from CSV
|
||||
$testDefinitionsPath = ".\source\helper\TestDefinitions.csv"
|
||||
$testDefinitions = Import-Csv -Path $testDefinitionsPath
|
||||
|
||||
# Iterate over each .ps1 file in the directory
|
||||
Get-ChildItem -Path $directoryPath -Filter "*.ps1" | ForEach-Object {
|
||||
$fileName = $_.Name
|
||||
$testDefinition = $testDefinitions | Where-Object { $_.TestFileName -eq $fileName }
|
||||
|
||||
if ($testDefinition) {
|
||||
$rec = $testDefinition.Rec
|
||||
$elevel = $testDefinition.ELevel
|
||||
$profileLevel = $testDefinition.ProfileLevel
|
||||
$ig1 = $testDefinition.IG1
|
||||
$ig2 = $testDefinition.IG2
|
||||
$ig3 = $testDefinition.IG3
|
||||
$connection = $testDefinition.Connection
|
||||
|
||||
$issueTitle = "Rec: $rec - Validate $fileName, ELevel: $elevel, ProfileLevel: $profileLevel, IG1: $ig1, IG2: $ig2, IG3: $ig3, Connection: $connection"
|
||||
$issueBody = @"
|
||||
# Validation for $fileName
|
||||
|
||||
## Tasks
|
||||
- [ ] Validate test for a pass
|
||||
- Description of passing criteria:
|
||||
- [ ] Validate test for a fail
|
||||
- Description of failing criteria:
|
||||
- [ ] Add notes and observations
|
||||
- Placeholder for additional notes:
|
||||
"@
|
||||
|
||||
# Create the issue using GitHub CLI
|
||||
try {
|
||||
Create-GitHubIssue -title "$issueTitle" -body "$issueBody" -project "$projectName"
|
||||
Write-Output "Created issue for $fileName"
|
||||
} catch {
|
||||
Write-Error "Failed to create issue for $fileName : $_"
|
||||
}
|
||||
|
||||
# Introduce a delay of 2 seconds
|
||||
Start-Sleep -Seconds 2
|
||||
} else {
|
||||
Write-Warning "No matching test definition found for $fileName"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
function Connect-M365Suite {
|
||||
[OutputType([void])]
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$TenantAdminUrl,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
|
@@ -1,4 +1,5 @@
|
||||
function Disconnect-M365Suite {
|
||||
[OutputType([void])]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string[]]$RequiredConnections
|
||||
|
@@ -1,5 +1,9 @@
|
||||
function Format-MissingAction {
|
||||
param ([array]$missingActions)
|
||||
[CmdletBinding()]
|
||||
[OutputType([hashtable])]
|
||||
param (
|
||||
[array]$missingActions
|
||||
)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
|
19
source/Private/Format-RequiredModuleList.ps1
Normal file
19
source/Private/Format-RequiredModuleList.ps1
Normal 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(", ")
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
function Get-MostCommonWord {
|
||||
[CmdletBinding()]
|
||||
[OutputType([string])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$InputStrings
|
||||
|
@@ -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 {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
function Get-TestDefinitionsObject {
|
||||
[CmdletBinding()]
|
||||
[OutputType([object[]])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object[]]$TestDefinitions,
|
||||
|
28
source/Private/Get-UniqueConnection.ps1
Normal file
28
source/Private/Get-UniqueConnection.ps1
Normal 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
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
function Initialize-CISAuditResult {
|
||||
[CmdletBinding()]
|
||||
[OutputType([CISAuditResult])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Rec,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
function Invoke-TestFunction {
|
||||
[OutputType([CISAuditResult[]])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PSObject]$FunctionFile,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
function Measure-AuditResult {
|
||||
[OutputType([void])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.ArrayList]$AllAuditResults,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
function Merge-CISExcelAndCsvData {
|
||||
[CmdletBinding(DefaultParameterSetName = 'CsvInput')]
|
||||
[OutputType([PSCustomObject[]])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExcelPath,
|
||||
|
@@ -1,4 +1,6 @@
|
||||
function New-MergedObject {
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$ExcelItem,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
function Update-CISExcelWorksheet {
|
||||
[OutputType([void])]
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
|
@@ -1,4 +1,5 @@
|
||||
function Update-WorksheetCell {
|
||||
[OutputType([void])]
|
||||
param (
|
||||
$Worksheet,
|
||||
$Data,
|
||||
|
@@ -4,7 +4,7 @@
|
||||
.DESCRIPTION
|
||||
The Invoke-M365SecurityAudit cmdlet performs a comprehensive security audit based on the specified parameters. It allows auditing of various configurations and settings within a Microsoft 365 environment, such as compliance with CIS benchmarks.
|
||||
.PARAMETER TenantAdminUrl
|
||||
The URL of the tenant admin. This parameter is mandatory.
|
||||
The URL of the tenant admin. If not specified, none of the SharePoint Online tests will run.
|
||||
.PARAMETER M365DomainForPWPolicyTest
|
||||
The domain name of the Microsoft 365 environment to test. This parameter is not mandatory and by default it will pass/fail all found domains as a group if a specific domain is not specified.
|
||||
.PARAMETER ELevel
|
||||
@@ -28,22 +28,77 @@
|
||||
.PARAMETER NoModuleCheck
|
||||
If specified, the cmdlet will not check for the presence of required modules.
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -ELevel "E5" -ProfileLevel "L1"
|
||||
|
||||
PS> Invoke-M365SecurityAudit
|
||||
Performs a security audit using default parameters.
|
||||
Output:
|
||||
Status : Fail
|
||||
ELevel : E3
|
||||
ProfileLevel: L1
|
||||
Connection : Microsoft Graph
|
||||
Rec : 1.1.1
|
||||
Result : False
|
||||
Details : Non-compliant accounts:
|
||||
Username | Roles | HybridStatus | Missing Licence
|
||||
user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM
|
||||
user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2
|
||||
FailureReason: Non-Compliant Accounts: 2
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -ELevel "E5" -ProfileLevel "L1"
|
||||
Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment.
|
||||
Output:
|
||||
Status : Fail
|
||||
ELevel : E5
|
||||
ProfileLevel: L1
|
||||
Connection : Microsoft Graph
|
||||
Rec : 1.1.1
|
||||
Result : False
|
||||
Details : Non-compliant accounts:
|
||||
Username | Roles | HybridStatus | Missing Licence
|
||||
user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM
|
||||
user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2
|
||||
FailureReason: Non-Compliant Accounts: 2
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1
|
||||
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -IncludeIG1
|
||||
Performs an audit including all tests where IG1 is true.
|
||||
Output:
|
||||
Status : Fail
|
||||
ELevel : E3
|
||||
ProfileLevel: L1
|
||||
Connection : Microsoft Graph
|
||||
Rec : 1.1.1
|
||||
Result : False
|
||||
Details : Non-compliant accounts:
|
||||
Username | Roles | HybridStatus | Missing Licence
|
||||
user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM
|
||||
user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2
|
||||
FailureReason: Non-Compliant Accounts: 2
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -SkipRecommendation '1.1.3', '2.1.1'
|
||||
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com" -SkipRecommendation '1.1.3', '2.1.1'
|
||||
Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1.
|
||||
Output:
|
||||
Status : Fail
|
||||
ELevel : E3
|
||||
ProfileLevel: L1
|
||||
Connection : Microsoft Graph
|
||||
Rec : 1.1.1
|
||||
Result : False
|
||||
Details : Non-compliant accounts:
|
||||
Username | Roles | HybridStatus | Missing Licence
|
||||
user1@domain.com| Global Administrator | Cloud-Only | AAD_PREMIUM
|
||||
user2@domain.com| Global Administrator | Hybrid | AAD_PREMIUM, AAD_PREMIUM_P2
|
||||
FailureReason: Non-Compliant Accounts: 2
|
||||
.EXAMPLE
|
||||
PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com"
|
||||
PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -M365DomainForPWPolicyTest "contoso.com"
|
||||
PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation
|
||||
|
||||
Captures the audit results into a variable and exports them to a CSV file.
|
||||
Output:
|
||||
CISAuditResult[]
|
||||
auditResults.csv
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -WhatIf
|
||||
Displays what would happen if the cmdlet is run without actually performing the audit.
|
||||
Output:
|
||||
What if: Performing the operation "Invoke-M365SecurityAudit" on target "Microsoft 365 environment".
|
||||
.INPUTS
|
||||
None. You cannot pipe objects to Invoke-M365SecurityAudit.
|
||||
.OUTPUTS
|
||||
@@ -63,7 +118,7 @@ function Invoke-M365SecurityAudit {
|
||||
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')]
|
||||
[OutputType([CISAuditResult[]])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'.")]
|
||||
[Parameter(Mandatory = $false, HelpMessage = "The SharePoint tenant admin URL, which should end with '-admin.sharepoint.com'. If not specified none of the Sharepoint Online tests will run.")]
|
||||
[ValidatePattern('^https://[a-zA-Z0-9-]+-admin\.sharepoint\.com$')]
|
||||
[string]$TenantAdminUrl,
|
||||
|
||||
@@ -127,12 +182,18 @@ function Invoke-M365SecurityAudit {
|
||||
$script:MaximumFunctionCount = 8192
|
||||
}
|
||||
# Ensure required modules are installed
|
||||
if (!($NoModuleCheck)) {
|
||||
$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
|
||||
@@ -151,9 +212,14 @@ function Invoke-M365SecurityAudit {
|
||||
$testDefinitions = Get-TestDefinitionsObject @params
|
||||
# Extract unique connections needed
|
||||
$requiredConnections = $testDefinitions.Connection | Sort-Object -Unique
|
||||
# Establishing connections if required
|
||||
if (!($DoNotConnect)) {
|
||||
Connect-M365Suite -TenantAdminUrl $TenantAdminUrl -RequiredConnections $requiredConnections
|
||||
if ($requiredConnections -contains 'SPO') {
|
||||
if (-not $TenantAdminUrl) {
|
||||
$requiredConnections = $requiredConnections | Where-Object { $_ -ne 'SPO' }
|
||||
$testDefinitions = $testDefinitions | Where-Object { $_.Connection -ne 'SPO' }
|
||||
if ($null -eq $testDefinitions) {
|
||||
throw "No tests to run as no SharePoint Online tests are available."
|
||||
}
|
||||
}
|
||||
}
|
||||
# Determine which test files to load based on filtering
|
||||
$testsToLoad = $testDefinitions.TestFileName | ForEach-Object { $_ -replace '.ps1$', '' }
|
||||
@@ -162,6 +228,7 @@ function Invoke-M365SecurityAudit {
|
||||
# Initialize a collection to hold failed test details
|
||||
$script:FailedTests = [System.Collections.ArrayList]::new()
|
||||
} # End Begin
|
||||
|
||||
Process {
|
||||
$allAuditResults = [System.Collections.ArrayList]::new() # Initialize a collection to hold all results
|
||||
# Dynamically dot-source the test scripts
|
||||
@@ -172,6 +239,15 @@ function Invoke-M365SecurityAudit {
|
||||
$totalTests = $testFiles.Count
|
||||
$currentTestIndex = 0
|
||||
|
||||
# Establishing connections if required
|
||||
$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++
|
||||
@@ -202,14 +278,16 @@ function Invoke-M365SecurityAudit {
|
||||
}
|
||||
|
||||
End {
|
||||
if (!($DoNotDisconnect)) {
|
||||
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 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
|
||||
return $allAuditResults.ToArray() | Sort-Object -Property Rec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
.PARAMETER CsvPath
|
||||
The path to the CSV file containing data to be merged with the Excel data. This parameter is mandatory when using the CsvInput parameter set.
|
||||
.PARAMETER AuditResults
|
||||
An array of CISAuditResult objects from Invoke-M365SecurityAudit to be merged with the Excel data. This parameter is mandatory when using the ObjectInput parameter set.
|
||||
An array of CISAuditResult objects from Invoke-M365SecurityAudit to be merged with the Excel data. This parameter is mandatory when using the ObjectInput parameter set. It can also accept pipeline input.
|
||||
.PARAMETER SkipUpdate
|
||||
If specified, the function will return the merged data object without updating the Excel worksheet. This is useful for previewing the merged data.
|
||||
.EXAMPLE
|
||||
@@ -27,8 +27,12 @@
|
||||
PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://tenant-admin.url" -DomainName "example.com"
|
||||
PS> $mergedData = Sync-CISExcelAndCsvData -ExcelPath "path\to\excel.xlsx" -WorksheetName "DataSheet" -AuditResults $auditResults -SkipUpdate
|
||||
Retrieves the merged data object for preview without updating the Excel worksheet.
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://tenant-admin.url" -DomainName "example.com" | Sync-CISExcelAndCsvData -ExcelPath "path\to\excel.xlsx" -WorksheetName "DataSheet"
|
||||
Pipes the audit results into Sync-CISExcelAndCsvData to merge data into 'excel.xlsx' on the 'DataSheet' worksheet and updates the worksheet with the merged data.
|
||||
.INPUTS
|
||||
None. You cannot pipe objects to Sync-CISExcelAndCsvData.
|
||||
System.String, CISAuditResult[]
|
||||
You can pipe CISAuditResult objects to Sync-CISExcelAndCsvData.
|
||||
.OUTPUTS
|
||||
Object[]
|
||||
If the SkipUpdate switch is used, the function returns an array of custom objects representing the merged data.
|
||||
@@ -40,6 +44,7 @@
|
||||
https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Sync-CISExcelAndCsvData
|
||||
#>
|
||||
function Sync-CISExcelAndCsvData {
|
||||
[OutputType([void], [PSCustomObject[]])]
|
||||
[CmdletBinding(DefaultParameterSetName = 'CsvInput')]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -53,7 +58,7 @@ function Sync-CISExcelAndCsvData {
|
||||
[ValidateScript({ Test-Path $_ })]
|
||||
[string]$CsvPath,
|
||||
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'ObjectInput')]
|
||||
[Parameter(Mandatory = $true, ParameterSetName = 'ObjectInput', ValueFromPipeline = $true)]
|
||||
[CISAuditResult[]]$AuditResults,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
@@ -83,5 +88,3 @@ function Sync-CISExcelAndCsvData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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"
|
||||
|
27
tests/Unit/Private/Format-RequiredModuleList.tests.ps1
Normal file
27
tests/Unit/Private/Format-RequiredModuleList.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/Get-UniqueConnection.tests.ps1
Normal file
27
tests/Unit/Private/Get-UniqueConnection.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