Initial Commit
This commit is contained in:
15
.gitattributes
vendored
Normal file
15
.gitattributes
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Needed for publishing of examples, build worker defaults to core.autocrlf=input.
|
||||
* text eol=autocrlf
|
||||
|
||||
*.mof text eol=crlf
|
||||
*.sh text eol=lf
|
||||
*.svg eol=lf
|
||||
|
||||
# Ensure any exe files are treated as binary
|
||||
*.exe binary
|
||||
*.jpg binary
|
||||
*.xl* binary
|
||||
*.pfx binary
|
||||
*.png binary
|
||||
*.dll binary
|
||||
*.so binary
|
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
output/
|
||||
|
||||
**.bak
|
||||
*.local.*
|
||||
!**/README.md
|
||||
.kitchen/
|
||||
|
||||
*.nupkg
|
||||
*.suo
|
||||
*.user
|
||||
*.coverage
|
||||
.vs
|
||||
.psproj
|
||||
.sln
|
||||
markdownissues.txt
|
||||
node_modules
|
||||
package-lock.json
|
125
.vscode/tasks.json
vendored
Normal file
125
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"_runner": "terminal",
|
||||
"windows": {
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"linux": {
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "/usr/bin/pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-Command"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"osx": {
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "/usr/local/bin/pwsh",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-Command"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "&${cwd}/build.ps1",
|
||||
"args": [],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "new",
|
||||
"clear": false
|
||||
},
|
||||
"runOptions": {
|
||||
"runOn": "default"
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "powershell",
|
||||
"fileLocation": [
|
||||
"absolute"
|
||||
],
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "(.*)",
|
||||
"code": 1
|
||||
},
|
||||
{
|
||||
"regexp": ""
|
||||
},
|
||||
{
|
||||
"regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"command": "&${cwd}/build.ps1",
|
||||
"args": ["-AutoRestore","-Tasks","test"],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "powershell",
|
||||
"fileLocation": [
|
||||
"absolute"
|
||||
],
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$",
|
||||
"message": 1
|
||||
},
|
||||
{
|
||||
"regexp": "(.*)",
|
||||
"code": 1
|
||||
},
|
||||
{
|
||||
"regexp": ""
|
||||
},
|
||||
{
|
||||
"regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
17
CHANGELOG.md
Normal file
17
CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Changelog for M365FoundationsCISReport
|
||||
|
||||
The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release of the M365FoundationsCISReport PowerShell module.
|
||||
- Function `Invoke-M365SecurityAudit` for conducting a comprehensive security audit in Microsoft 365 environments.
|
||||
- Support for multiple parameter sets including ELevelFilter, IGFilters, RecFilter, and SkipRecFilter to cater to diverse audit requirements.
|
||||
- Implementation of `-NoModuleCheck`, `-DoNotConnect`, and `-DoNotDisconnect` switches for enhanced control over module behavior.
|
||||
- Integration with required modules like ExchangeOnlineManagement, AzureAD, Microsoft.Graph, Microsoft.Online.SharePoint.PowerShell, and MicrosoftTeams.
|
||||
- A dynamic test loading system based on CSV input for flexibility in defining audit tests.
|
||||
- Comprehensive verbose logging to detail the steps being performed during an audit.
|
||||
- Comment-help documentation for the `Invoke-M365SecurityAudit` function with examples and usage details.
|
||||
- Attribution to CIS and licensing information under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License in the README.
|
40
GitVersion.yml
Normal file
40
GitVersion.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
mode: ContinuousDelivery
|
||||
next-version: 0.0.1
|
||||
major-version-bump-message: '(breaking\schange|breaking|major)\b'
|
||||
minor-version-bump-message: '(adds?|features?|minor)\b'
|
||||
patch-version-bump-message: '\s?(fix|patch)'
|
||||
no-bump-message: '\+semver:\s?(none|skip)'
|
||||
assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}'
|
||||
branches:
|
||||
master:
|
||||
tag: preview
|
||||
regex: ^main$
|
||||
pull-request:
|
||||
tag: PR
|
||||
feature:
|
||||
tag: useBranchName
|
||||
increment: Minor
|
||||
regex: f(eature(s)?)?[\/-]
|
||||
source-branches: ['master']
|
||||
hotfix:
|
||||
tag: fix
|
||||
increment: Patch
|
||||
regex: (hot)?fix(es)?[\/-]
|
||||
source-branches: ['master']
|
||||
|
||||
ignore:
|
||||
sha: []
|
||||
merge-message-formats: {}
|
||||
|
||||
|
||||
# feature:
|
||||
# tag: useBranchName
|
||||
# increment: Minor
|
||||
# regex: f(eature(s)?)?[/-]
|
||||
# source-branches: ['master']
|
||||
# hotfix:
|
||||
# tag: fix
|
||||
# increment: Patch
|
||||
# regex: (hot)?fix(es)?[/-]
|
||||
# source-branches: ['master']
|
||||
|
25
LICENSE.md
Normal file
25
LICENSE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# License
|
||||
|
||||
## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
|
||||
|
||||
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit [CC BY-NC-SA 4.0 license](https://creativecommons.org/licenses/by-nc-sa/4.0/).
|
||||
|
||||
### You are free to:
|
||||
|
||||
- **Share** — copy and redistribute the material in any medium or format
|
||||
- **Adapt** — remix, transform, and build upon the material
|
||||
|
||||
The licensor cannot revoke these freedoms as long as you follow the license terms.
|
||||
|
||||
### Under the following terms:
|
||||
|
||||
- **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
||||
- **NonCommercial** — You may not use the material for commercial purposes.
|
||||
- **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
||||
- **No additional restrictions** — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
||||
|
||||
### Notices:
|
||||
|
||||
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
|
||||
|
||||
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
|
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# M365FoundationsCISReport Module
|
||||
|
||||
## License
|
||||
|
||||
This PowerShell module is based on CIS benchmarks and is distributed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. This means:
|
||||
|
||||
- **Non-commercial**: You may not use the material for commercial purposes.
|
||||
- **ShareAlike**: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
||||
- **Attribution**: Appropriate credit must be given, provide a link to the license, and indicate if changes were made.
|
||||
|
||||
For full license details, please visit [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en).
|
||||
|
||||
## Invoke-M365SecurityAudit
|
||||
### Synopsis
|
||||
Invokes a security audit for Microsoft 365 environments.
|
||||
### Syntax
|
||||
```powershell
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-ELevel <String>] [-ProfileLevel <String>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG1] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG2] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG3] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeRecommendation <String[]>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-SkipRecommendation <String[]>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
### Parameters
|
||||
| Name | Alias | Description | Required? | Pipeline Input | Default Value |
|
||||
| - | - | - | - | - | - |
|
||||
| <nobr>TenantAdminUrl</nobr> | | The URL of the tenant admin. This parameter is mandatory. | true | false | |
|
||||
| <nobr>DomainName</nobr> | | The domain name of the Microsoft 365 environment. This parameter is mandatory. | true | false | |
|
||||
| <nobr>ELevel</nobr> | | Specifies the E-Level \(E3 or E5\) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter. | false | false | |
|
||||
| <nobr>ProfileLevel</nobr> | | Specifies the profile level \(L1 or L2\) for the audit. This parameter is optional and can be combined with the ELevel parameter. | false | false | |
|
||||
| <nobr>IncludeIG1</nobr> | | If specified, includes tests where IG1 is true. | false | false | False |
|
||||
| <nobr>IncludeIG2</nobr> | | If specified, includes tests where IG2 is true. | false | false | False |
|
||||
| <nobr>IncludeIG3</nobr> | | If specified, includes tests where IG3 is true. | false | false | False |
|
||||
| <nobr>IncludeRecommendation</nobr> | | Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers. | false | false | |
|
||||
| <nobr>SkipRecommendation</nobr> | | Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers. | false | false | |
|
||||
| <nobr>DoNotConnect</nobr> | | If specified, the cmdlet will not establish a connection to Microsoft 365 services. | false | false | False |
|
||||
| <nobr>DoNotDisconnect</nobr> | | If specified, the cmdlet will not disconnect from Microsoft 365 services after execution. | false | false | False |
|
||||
| <nobr>NoModuleCheck</nobr> | | If specified, the cmdlet will not check for the presence of required modules. | false | false | False |
|
||||
| <nobr>WhatIf</nobr> | wi | | false | false | |
|
||||
| <nobr>Confirm</nobr> | cf | | false | false | |
|
||||
### Inputs
|
||||
- None. You cannot pipe objects to Invoke-M365SecurityAudit.
|
||||
|
||||
### Outputs
|
||||
- CISAuditResult\\[\] The cmdlet returns an array of CISAuditResult objects representing the results of the security audit.
|
||||
|
||||
### Note
|
||||
This module is based on CIS benchmarks and is governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. For more details, visit: https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en
|
||||
|
||||
### Examples
|
||||
**EXAMPLE 1**
|
||||
```powershell
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -ELevel "E5" -ProfileLevel "L1"
|
||||
```
|
||||
Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment.
|
||||
|
||||
**EXAMPLE 2**
|
||||
```powershell
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1
|
||||
```
|
||||
Performs an audit including all tests where IG1 is true.
|
||||
|
||||
**EXAMPLE 3**
|
||||
```powershell
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -SkipRecommendation '1.1.3', '2.1.1'
|
||||
```
|
||||
Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1.
|
||||
|
||||
**EXAMPLE 4**
|
||||
```powershell
|
||||
$auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com"
|
||||
PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation
|
||||
```
|
||||
Captures the audit results into a variable and exports them to a CSV file.
|
||||
|
||||
### Links
|
||||
|
||||
- [Online Version: [GitHub Repository URL]](#Online Version: [GitHub Repository URL])
|
20
RequiredModules.psd1
Normal file
20
RequiredModules.psd1
Normal file
@@ -0,0 +1,20 @@
|
||||
@{
|
||||
PSDependOptions = @{
|
||||
AddToPath = $true
|
||||
Target = 'output\RequiredModules'
|
||||
Parameters = @{
|
||||
Repository = 'PSGallery'
|
||||
}
|
||||
}
|
||||
|
||||
InvokeBuild = 'latest'
|
||||
PSScriptAnalyzer = 'latest'
|
||||
Pester = 'latest'
|
||||
ModuleBuilder = 'latest'
|
||||
ChangelogManagement = 'latest'
|
||||
Sampler = 'latest'
|
||||
'Sampler.GitHubTasks' = 'latest'
|
||||
|
||||
|
||||
}
|
||||
|
919
Resolve-Dependency.ps1
Normal file
919
Resolve-Dependency.ps1
Normal file
@@ -0,0 +1,919 @@
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Bootstrap script for PSDepend.
|
||||
|
||||
.PARAMETER DependencyFile
|
||||
Specifies the configuration file for the this script. The default value is
|
||||
'RequiredModules.psd1' relative to this script's path.
|
||||
|
||||
.PARAMETER PSDependTarget
|
||||
Path for PSDepend to be bootstrapped and save other dependencies.
|
||||
Can also be CurrentUser or AllUsers if you wish to install the modules in
|
||||
such scope. The default value is 'output/RequiredModules' relative to
|
||||
this script's path.
|
||||
|
||||
.PARAMETER Proxy
|
||||
Specifies the URI to use for Proxy when attempting to bootstrap
|
||||
PackageProvider and PowerShellGet.
|
||||
|
||||
.PARAMETER ProxyCredential
|
||||
Specifies the credential to contact the Proxy when provided.
|
||||
|
||||
.PARAMETER Scope
|
||||
Specifies the scope to bootstrap the PackageProvider and PSGet if not available.
|
||||
THe default value is 'CurrentUser'.
|
||||
|
||||
.PARAMETER Gallery
|
||||
Specifies the gallery to use when bootstrapping PackageProvider, PSGet and
|
||||
when calling PSDepend (can be overridden in Dependency files). The default
|
||||
value is 'PSGallery'.
|
||||
|
||||
.PARAMETER GalleryCredential
|
||||
Specifies the credentials to use with the Gallery specified above.
|
||||
|
||||
.PARAMETER AllowOldPowerShellGetModule
|
||||
Allow you to use a locally installed version of PowerShellGet older than
|
||||
1.6.0 (not recommended). Default it will install the latest PowerShellGet
|
||||
if an older version than 2.0 is detected.
|
||||
|
||||
.PARAMETER MinimumPSDependVersion
|
||||
Allow you to specify a minimum version fo PSDepend, if you're after specific
|
||||
features.
|
||||
|
||||
.PARAMETER AllowPrerelease
|
||||
Not yet written.
|
||||
|
||||
.PARAMETER WithYAML
|
||||
Not yet written.
|
||||
|
||||
.PARAMETER UseModuleFast
|
||||
Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies
|
||||
faster.
|
||||
|
||||
.PARAMETER PSResourceGet
|
||||
Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies
|
||||
faster.
|
||||
|
||||
.NOTES
|
||||
Load defaults for parameters values from Resolve-Dependency.psd1 if not
|
||||
provided as parameter.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param
|
||||
(
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$DependencyFile = 'RequiredModules.psd1',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$PSDependTarget = (Join-Path -Path $PSScriptRoot -ChildPath 'output/RequiredModules'),
|
||||
|
||||
[Parameter()]
|
||||
[System.Uri]
|
||||
$Proxy,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.PSCredential]
|
||||
$ProxyCredential,
|
||||
|
||||
[Parameter()]
|
||||
[ValidateSet('CurrentUser', 'AllUsers')]
|
||||
[System.String]
|
||||
$Scope = 'CurrentUser',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$Gallery = 'PSGallery',
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.PSCredential]
|
||||
$GalleryCredential,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$AllowOldPowerShellGetModule,
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$MinimumPSDependVersion,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$AllowPrerelease,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$WithYAML,
|
||||
|
||||
[Parameter()]
|
||||
[System.Collections.Hashtable]
|
||||
$RegisterGallery,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$UseModuleFast,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$UsePSResourceGet,
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$PSResourceGetVersion
|
||||
)
|
||||
|
||||
try
|
||||
{
|
||||
if ($PSVersionTable.PSVersion.Major -le 5)
|
||||
{
|
||||
if (-not (Get-Command -Name 'Import-PowerShellDataFile' -ErrorAction 'SilentlyContinue'))
|
||||
{
|
||||
Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion '3.1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message 'Importing Bootstrap default parameters from ''$PSScriptRoot/Resolve-Dependency.psd1''.'
|
||||
|
||||
$resolveDependencyConfigPath = Join-Path -Path $PSScriptRoot -ChildPath '.\Resolve-Dependency.psd1' -Resolve -ErrorAction 'Stop'
|
||||
|
||||
$resolveDependencyDefaults = Import-PowerShellDataFile -Path $resolveDependencyConfigPath
|
||||
|
||||
$parameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys
|
||||
|
||||
if ($parameterToDefault.Count -eq 0)
|
||||
{
|
||||
$parameterToDefault = $MyInvocation.MyCommand.Parameters.Keys
|
||||
}
|
||||
|
||||
# Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option.
|
||||
foreach ($parameterName in $parameterToDefault)
|
||||
{
|
||||
if (-not $PSBoundParameters.Keys.Contains($parameterName) -and $resolveDependencyDefaults.ContainsKey($parameterName))
|
||||
{
|
||||
Write-Verbose -Message "Setting parameter '$parameterName' to value '$($resolveDependencyDefaults[$parameterName])'."
|
||||
|
||||
try
|
||||
{
|
||||
$variableValue = $resolveDependencyDefaults[$parameterName]
|
||||
|
||||
if ($variableValue -is [System.String])
|
||||
{
|
||||
$variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue)
|
||||
}
|
||||
|
||||
$PSBoundParameters.Add($parameterName, $variableValue)
|
||||
|
||||
Set-Variable -Name $parameterName -Value $variableValue -Force -ErrorAction 'SilentlyContinue'
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Verbose -Message "Error adding default for $parameterName : $($_.Exception.Message)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)."
|
||||
}
|
||||
|
||||
# Handles when both ModuleFast and PSResourceGet is configured or/and passed as parameter.
|
||||
if ($UseModuleFast -and $UsePSResourceGet)
|
||||
{
|
||||
Write-Information -MessageData 'Both ModuleFast and PSResourceGet is configured or/and passed as parameter.' -InformationAction 'Continue'
|
||||
|
||||
if ($PSVersionTable.PSVersion -ge '7.2')
|
||||
{
|
||||
$UsePSResourceGet = $false
|
||||
|
||||
Write-Information -MessageData 'PowerShell 7.2 or higher being used, prefer ModuleFast over PSResourceGet.' -InformationAction 'Continue'
|
||||
}
|
||||
else
|
||||
{
|
||||
$UseModuleFast = $false
|
||||
|
||||
Write-Information -MessageData 'Older PowerShell or Windows PowerShell being used, prefer PSResourceGet since ModuleFast is not supported on this version of PowerShell.' -InformationAction 'Continue'
|
||||
}
|
||||
}
|
||||
|
||||
if ($UseModuleFast)
|
||||
{
|
||||
try
|
||||
{
|
||||
$invokeWebRequestParameters = @{
|
||||
Uri = 'bit.ly/modulefast' # cSpell: disable-line
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
|
||||
$moduleFastBootstrapScript = Invoke-WebRequest @invokeWebRequestParameters
|
||||
|
||||
<#
|
||||
Using this method instead of the one mentioned in the instructions from
|
||||
https://github.com/JustinGrote/ModuleFast to avoid the PSScriptAnalyzer
|
||||
rule PSAvoidUsingInvokeExpression.
|
||||
#>
|
||||
$moduleFastBootstrapScriptBlock = [ScriptBlock]::Create($moduleFastBootstrapScript)
|
||||
|
||||
<#
|
||||
We could pass parameters to the bootstrap script when calling Invoke().
|
||||
But currently the default parameter values works just fine.
|
||||
#>
|
||||
$moduleFastBootstrapScriptBlock.Invoke()
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Warning -Message ('ModuleFast could not be bootstrapped. Reverting to PowerShellGet. Error: {0}' -f $_.Exception.Message)
|
||||
|
||||
$UseModuleFast = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($UsePSResourceGet)
|
||||
{
|
||||
$psResourceGetModuleName = 'Microsoft.PowerShell.PSResourceGet'
|
||||
|
||||
# If PSResourceGet was used prior it will be locked and we can't replace it.
|
||||
if ((Test-Path -Path "$PSDependTarget/$psResourceGetModuleName" -PathType 'Container') -and (Get-Module -Name $psResourceGetModuleName))
|
||||
{
|
||||
Write-Information -MessageData ('{0} is already saved and loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $psResourceGetModuleName) -InformationAction 'Continue'
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Debug -Message ('{0} do not exist, saving the module to RequiredModules.' -f $psResourceGetModuleName)
|
||||
|
||||
$psResourceGetDownloaded = $false
|
||||
|
||||
try
|
||||
{
|
||||
if (-not $PSResourceGetVersion)
|
||||
{
|
||||
# Default version to use if non is specified in parameter or in configuration.
|
||||
$PSResourceGetVersion = '0.9.0-rc1'
|
||||
}
|
||||
|
||||
$invokeWebRequestParameters = @{
|
||||
# TODO: This should be hardcoded to a stable release in the future.
|
||||
# TODO: Should support proxy parameters passed to the script.
|
||||
Uri = "https://www.powershellgallery.com/api/v2/package/$psResourceGetModuleName/$PSResourceGetVersion"
|
||||
OutFile = "$PSDependTarget/$psResourceGetModuleName.nupkg" # cSpell: ignore nupkg
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
|
||||
$previousProgressPreference = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# Bootstrapping Microsoft.PowerShell.PSResourceGet.
|
||||
Invoke-WebRequest @invokeWebRequestParameters
|
||||
|
||||
$ProgressPreference = $previousProgressPreference
|
||||
|
||||
$psResourceGetDownloaded = $true
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Warning -Message ('{0} could not be bootstrapped. Reverting to PowerShellGet. Error: {1}' -f $psResourceGetModuleName, $_.Exception.Message)
|
||||
}
|
||||
|
||||
$UsePSResourceGet = $false
|
||||
|
||||
if ($psResourceGetDownloaded)
|
||||
{
|
||||
# On Windows PowerShell the command Expand-Archive do not like .nupkg as a zip archive extension.
|
||||
$zipFileName = ((Split-Path -Path $invokeWebRequestParameters.OutFile -Leaf) -replace 'nupkg', 'zip')
|
||||
|
||||
$renameItemParameters = @{
|
||||
Path = $invokeWebRequestParameters.OutFile
|
||||
NewName = $zipFileName
|
||||
Force = $true
|
||||
}
|
||||
|
||||
Rename-Item @renameItemParameters
|
||||
|
||||
$psResourceGetZipArchivePath = Join-Path -Path (Split-Path -Path $invokeWebRequestParameters.OutFile -Parent) -ChildPath $zipFileName
|
||||
|
||||
$expandArchiveParameters = @{
|
||||
Path = $psResourceGetZipArchivePath
|
||||
DestinationPath = "$PSDependTarget/$psResourceGetModuleName"
|
||||
Force = $true
|
||||
}
|
||||
|
||||
Expand-Archive @expandArchiveParameters
|
||||
|
||||
Remove-Item -Path $psResourceGetZipArchivePath
|
||||
|
||||
Import-Module -Name $expandArchiveParameters.DestinationPath -Force
|
||||
|
||||
# Successfully bootstrapped PSResourceGet, so let's use it.
|
||||
$UsePSResourceGet = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($UsePSResourceGet)
|
||||
{
|
||||
$psResourceGetModule = Get-Module -Name $psResourceGetModuleName
|
||||
|
||||
$psResourceGetModuleVersion = $psResourceGetModule.Version.ToString()
|
||||
|
||||
if ($psResourceGetModule.PrivateData.PSData.Prerelease)
|
||||
{
|
||||
$psResourceGetModuleVersion += '-{0}' -f $psResourceGetModule.PrivateData.PSData.Prerelease
|
||||
}
|
||||
|
||||
Write-Information -MessageData ('Using {0} v{1}.' -f $psResourceGetModuleName, $psResourceGetModuleVersion) -InformationAction 'Continue'
|
||||
|
||||
if ($UsePowerShellGetCompatibilityModule)
|
||||
{
|
||||
$savePowerShellGetParameters = @{
|
||||
Name = 'PowerShellGet'
|
||||
Path = $PSDependTarget
|
||||
Repository = 'PSGallery'
|
||||
TrustRepository = $true
|
||||
}
|
||||
|
||||
if ($UsePowerShellGetCompatibilityModuleVersion)
|
||||
{
|
||||
$savePowerShellGetParameters.Version = $UsePowerShellGetCompatibilityModuleVersion
|
||||
|
||||
# Check if the version is a prerelease.
|
||||
if ($UsePowerShellGetCompatibilityModuleVersion -match '\d+\.\d+\.\d+-.*')
|
||||
{
|
||||
$savePowerShellGetParameters.Prerelease = $true
|
||||
}
|
||||
}
|
||||
|
||||
Save-PSResource @savePowerShellGetParameters
|
||||
|
||||
Import-Module -Name "$PSDependTarget/PowerShellGet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not ($UseModuleFast -or $UsePSResourceGet))
|
||||
{
|
||||
if ($PSVersionTable.PSVersion.Major -le 5)
|
||||
{
|
||||
<#
|
||||
Making sure the imported PackageManagement module is not from PS7 module
|
||||
path. The VSCode PS extension is changing the $env:PSModulePath and
|
||||
prioritize the PS7 path. This is an issue with PowerShellGet because
|
||||
it loads an old version if available (or fail to load latest).
|
||||
#>
|
||||
Get-Module -ListAvailable PackageManagement |
|
||||
Where-Object -Property 'ModuleBase' -NotMatch 'powershell.7' |
|
||||
Select-Object -First 1 |
|
||||
Import-Module -Force
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 0 -CurrentOperation 'NuGet Bootstrap'
|
||||
|
||||
$importModuleParameters = @{
|
||||
Name = 'PowerShellGet'
|
||||
MinimumVersion = '2.0'
|
||||
MaximumVersion = '2.8.999'
|
||||
ErrorAction = 'SilentlyContinue'
|
||||
PassThru = $true
|
||||
}
|
||||
|
||||
if ($AllowOldPowerShellGetModule)
|
||||
{
|
||||
$importModuleParameters.Remove('MinimumVersion')
|
||||
}
|
||||
|
||||
$powerShellGetModule = Import-Module @importModuleParameters
|
||||
|
||||
# Install the package provider if it is not available.
|
||||
$nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable -ErrorAction 'SilentlyContinue' |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $powerShellGetModule -and -not $nuGetProvider)
|
||||
{
|
||||
$providerBootstrapParameters = @{
|
||||
Name = 'NuGet'
|
||||
Force = $true
|
||||
ForceBootstrap = $true
|
||||
ErrorAction = 'Stop'
|
||||
Scope = $Scope
|
||||
}
|
||||
|
||||
switch ($PSBoundParameters.Keys)
|
||||
{
|
||||
'Proxy'
|
||||
{
|
||||
$providerBootstrapParameters.Add('Proxy', $Proxy)
|
||||
}
|
||||
|
||||
'ProxyCredential'
|
||||
{
|
||||
$providerBootstrapParameters.Add('ProxyCredential', $ProxyCredential)
|
||||
}
|
||||
|
||||
'AllowPrerelease'
|
||||
{
|
||||
$providerBootstrapParameters.Add('AllowPrerelease', $AllowPrerelease)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Information -MessageData 'Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed).'
|
||||
|
||||
$null = Install-PackageProvider @providerBootstrapParameters
|
||||
|
||||
$nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1
|
||||
|
||||
$nuGetProviderVersion = $nuGetProvider.Version.ToString()
|
||||
|
||||
Write-Information -MessageData "Bootstrap: Importing NuGet Package Provider version $nuGetProviderVersion to current session."
|
||||
|
||||
$Null = Import-PackageProvider -Name 'NuGet' -RequiredVersion $nuGetProviderVersion -Force
|
||||
}
|
||||
|
||||
if ($RegisterGallery)
|
||||
{
|
||||
if ($RegisterGallery.ContainsKey('Name') -and -not [System.String]::IsNullOrEmpty($RegisterGallery.Name))
|
||||
{
|
||||
$Gallery = $RegisterGallery.Name
|
||||
}
|
||||
else
|
||||
{
|
||||
$RegisterGallery.Name = $Gallery
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 7 -CurrentOperation "Verifying private package repository '$Gallery'" -Completed
|
||||
|
||||
$previousRegisteredRepository = Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue'
|
||||
|
||||
if ($previousRegisteredRepository.SourceLocation -ne $RegisterGallery.SourceLocation)
|
||||
{
|
||||
if ($previousRegisteredRepository)
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Re-registrering private package repository '$Gallery'" -Completed
|
||||
|
||||
Unregister-PSRepository -Name $Gallery
|
||||
|
||||
$unregisteredPreviousRepository = $true
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Registering private package repository '$Gallery'" -Completed
|
||||
}
|
||||
|
||||
Register-PSRepository @RegisterGallery
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted"
|
||||
|
||||
# Fail if the given PSGallery is not registered.
|
||||
$previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').Trusted
|
||||
|
||||
if ($previousGalleryInstallationPolicy -ne $true)
|
||||
{
|
||||
# Only change policy if the repository is not trusted
|
||||
Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore'
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (-not ($UseModuleFast -or $UsePSResourceGet))
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet'
|
||||
|
||||
# Ensure the module is loaded and retrieve the version you have.
|
||||
$powerShellGetVersion = (Import-Module -Name 'PowerShellGet' -PassThru -ErrorAction 'SilentlyContinue').Version
|
||||
|
||||
Write-Verbose -Message "Bootstrap: The PowerShellGet version is $powerShellGetVersion"
|
||||
|
||||
# Versions below 2.0 are considered old, unreliable & not recommended
|
||||
if (-not $powerShellGetVersion -or ($powerShellGetVersion -lt [System.Version] '2.0' -and -not $AllowOldPowerShellGetModule))
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 40 -CurrentOperation 'Fetching newer version of PowerShellGet'
|
||||
|
||||
# PowerShellGet module not found, installing or saving it.
|
||||
if ($PSDependTarget -in 'CurrentUser', 'AllUsers')
|
||||
{
|
||||
Write-Debug -Message "PowerShellGet module not found. Attempting to install from Gallery $Gallery."
|
||||
|
||||
Write-Warning -Message "Installing PowerShellGet in $PSDependTarget Scope."
|
||||
|
||||
$installPowerShellGetParameters = @{
|
||||
Name = 'PowerShellGet'
|
||||
Force = $true
|
||||
SkipPublisherCheck = $true
|
||||
AllowClobber = $true
|
||||
Scope = $Scope
|
||||
Repository = $Gallery
|
||||
MaximumVersion = '2.8.999'
|
||||
}
|
||||
|
||||
switch ($PSBoundParameters.Keys)
|
||||
{
|
||||
'Proxy'
|
||||
{
|
||||
$installPowerShellGetParameters.Add('Proxy', $Proxy)
|
||||
}
|
||||
|
||||
'ProxyCredential'
|
||||
{
|
||||
$installPowerShellGetParameters.Add('ProxyCredential', $ProxyCredential)
|
||||
}
|
||||
|
||||
'GalleryCredential'
|
||||
{
|
||||
$installPowerShellGetParameters.Add('Credential', $GalleryCredential)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation 'Installing newer version of PowerShellGet'
|
||||
|
||||
Install-Module @installPowerShellGetParameters
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Debug -Message "PowerShellGet module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget"
|
||||
|
||||
$saveModuleParameters = @{
|
||||
Name = 'PowerShellGet'
|
||||
Repository = $Gallery
|
||||
Path = $PSDependTarget
|
||||
Force = $true
|
||||
MaximumVersion = '2.8.999'
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation "Saving PowerShellGet from $Gallery to $Scope"
|
||||
|
||||
Save-Module @saveModuleParameters
|
||||
}
|
||||
|
||||
Write-Debug -Message 'Removing previous versions of PowerShellGet and PackageManagement from session'
|
||||
|
||||
Get-Module -Name 'PowerShellGet' -All | Remove-Module -Force -ErrorAction 'SilentlyContinue'
|
||||
Get-Module -Name 'PackageManagement' -All | Remove-Module -Force
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 65 -CurrentOperation 'Loading latest version of PowerShellGet'
|
||||
|
||||
Write-Debug -Message 'Importing latest PowerShellGet and PackageManagement versions into session'
|
||||
|
||||
if ($AllowOldPowerShellGetModule)
|
||||
{
|
||||
$powerShellGetModule = Import-Module -Name 'PowerShellGet' -Force -PassThru
|
||||
}
|
||||
else
|
||||
{
|
||||
Import-Module -Name 'PackageManagement' -MinimumVersion '1.4.8.1' -Force
|
||||
|
||||
$powerShellGetModule = Import-Module -Name 'PowerShellGet' -MinimumVersion '2.2.5' -Force -PassThru
|
||||
}
|
||||
|
||||
$powerShellGetVersion = $powerShellGetModule.Version.ToString()
|
||||
|
||||
Write-Information -MessageData "Bootstrap: PowerShellGet version loaded is $powerShellGetVersion"
|
||||
}
|
||||
|
||||
# Try to import the PSDepend module from the available modules.
|
||||
$getModuleParameters = @{
|
||||
Name = 'PSDepend'
|
||||
ListAvailable = $true
|
||||
}
|
||||
|
||||
$psDependModule = Get-Module @getModuleParameters
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion'))
|
||||
{
|
||||
try
|
||||
{
|
||||
$psDependModule = $psDependModule | Where-Object -FilterScript { $_.Version -ge $MinimumPSDependVersion }
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw ('There was a problem finding the minimum version of PSDepend. Error: {0}' -f $_)
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $psDependModule)
|
||||
{
|
||||
Write-Debug -Message 'PSDepend module not found.'
|
||||
|
||||
# PSDepend module not found, installing or saving it.
|
||||
if ($PSDependTarget -in 'CurrentUser', 'AllUsers')
|
||||
{
|
||||
Write-Debug -Message "Attempting to install from Gallery '$Gallery'."
|
||||
|
||||
Write-Warning -Message "Installing PSDepend in $PSDependTarget Scope."
|
||||
|
||||
$installPSDependParameters = @{
|
||||
Name = 'PSDepend'
|
||||
Repository = $Gallery
|
||||
Force = $true
|
||||
Scope = $PSDependTarget
|
||||
SkipPublisherCheck = $true
|
||||
AllowClobber = $true
|
||||
}
|
||||
|
||||
if ($MinimumPSDependVersion)
|
||||
{
|
||||
$installPSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion)
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery"
|
||||
|
||||
Install-Module @installPSDependParameters
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Debug -Message "Attempting to Save from Gallery $Gallery to $PSDependTarget"
|
||||
|
||||
$saveModuleParameters = @{
|
||||
Name = 'PSDepend'
|
||||
Repository = $Gallery
|
||||
Path = $PSDependTarget
|
||||
Force = $true
|
||||
}
|
||||
|
||||
if ($MinimumPSDependVersion)
|
||||
{
|
||||
$saveModuleParameters.add('MinimumVersion', $MinimumPSDependVersion)
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Saving PSDepend from $Gallery to $PSDependTarget"
|
||||
|
||||
Save-Module @saveModuleParameters
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 80 -CurrentOperation 'Importing PSDepend'
|
||||
|
||||
$importModulePSDependParameters = @{
|
||||
Name = 'PSDepend'
|
||||
ErrorAction = 'Stop'
|
||||
Force = $true
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion'))
|
||||
{
|
||||
$importModulePSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion)
|
||||
}
|
||||
|
||||
# We should have successfully bootstrapped PSDepend. Fail if not available.
|
||||
$null = Import-Module @importModulePSDependParameters
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 81 -CurrentOperation 'Invoke PSDepend'
|
||||
|
||||
if ($WithYAML)
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 82 -CurrentOperation 'Verifying PowerShell module PowerShell-Yaml'
|
||||
|
||||
if (-not (Get-Module -ListAvailable -Name 'PowerShell-Yaml'))
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 85 -CurrentOperation 'Installing PowerShell module PowerShell-Yaml'
|
||||
|
||||
Write-Verbose -Message "PowerShell-Yaml module not found. Attempting to Save from Gallery '$Gallery' to '$PSDependTarget'."
|
||||
|
||||
$SaveModuleParam = @{
|
||||
Name = 'PowerShell-Yaml'
|
||||
Repository = $Gallery
|
||||
Path = $PSDependTarget
|
||||
Force = $true
|
||||
}
|
||||
|
||||
Save-Module @SaveModuleParam
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose -Message 'PowerShell-Yaml is already available'
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 88 -CurrentOperation 'Importing PowerShell module PowerShell-Yaml'
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path -Path $DependencyFile)
|
||||
{
|
||||
if ($UseModuleFast -or $UsePSResourceGet)
|
||||
{
|
||||
$requiredModules = Import-PowerShellDataFile -Path $DependencyFile
|
||||
|
||||
$requiredModules = $requiredModules.GetEnumerator() |
|
||||
Where-Object -FilterScript { $_.Name -ne 'PSDependOptions' }
|
||||
|
||||
$modulesToSave = @(
|
||||
'PSDepend' # Always include PSDepend for backward compatibility.
|
||||
)
|
||||
|
||||
foreach ($requiredModule in $requiredModules)
|
||||
{
|
||||
# If the RequiredModules.psd1 entry is an Hashtable then special handling is needed.
|
||||
if ($requiredModule.Value -is [System.Collections.Hashtable])
|
||||
{
|
||||
$saveModuleHashtable = @{
|
||||
ModuleName = $requiredModule.Name
|
||||
}
|
||||
|
||||
if ($requiredModule.Value.Version -and $requiredModule.Value.Version -ne 'latest')
|
||||
{
|
||||
$saveModuleHashtable.RequiredVersion = $requiredModule.Value.Version
|
||||
}
|
||||
|
||||
# ModuleFast does no support preview releases yet.
|
||||
if ($UsePSResourceGet)
|
||||
{
|
||||
if ($requiredModule.Value.Parameters.AllowPrerelease -eq $true)
|
||||
{
|
||||
$saveModuleHashtable.Prerelease = $true
|
||||
}
|
||||
}
|
||||
|
||||
$modulesToSave += $saveModuleHashtable
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($requiredModule.Value -eq 'latest')
|
||||
{
|
||||
$modulesToSave += $requiredModule.Name
|
||||
}
|
||||
else
|
||||
{
|
||||
$modulesToSave += @{
|
||||
ModuleName = $requiredModule.Name
|
||||
RequiredVersion = $requiredModule.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($WithYAML)
|
||||
{
|
||||
$modulesToSave += 'PowerShell-Yaml'
|
||||
}
|
||||
|
||||
if ($UseModuleFast)
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking ModuleFast'
|
||||
|
||||
Write-Progress -Activity 'ModuleFast:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies'
|
||||
|
||||
$moduleFastPlan = $modulesToSave | Get-ModuleFastPlan
|
||||
|
||||
if ($moduleFastPlan)
|
||||
{
|
||||
# Clear all modules in plan from the current session so they can be fetched again.
|
||||
$moduleFastPlan.Name | Get-Module | Remove-Module -Force
|
||||
|
||||
$installModuleFastParameters = @{
|
||||
ModulesToInstall = $moduleFastPlan
|
||||
Destination = $PSDependTarget
|
||||
NoPSModulePathUpdate = $true
|
||||
NoProfileUpdate = $true
|
||||
Update = $true
|
||||
Confirm = $false
|
||||
}
|
||||
|
||||
Install-ModuleFast @installModuleFastParameters
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose -Message 'All modules were already up to date'
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'ModuleFast:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed
|
||||
}
|
||||
|
||||
if ($UsePSResourceGet)
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking PSResourceGet'
|
||||
|
||||
$progressPercentage = 0
|
||||
|
||||
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies'
|
||||
|
||||
$percentagePerModule = [System.Math]::Floor(100 / $modulesToSave.Length)
|
||||
|
||||
foreach ($currentModule in $modulesToSave)
|
||||
{
|
||||
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Saving module {0}' -f $savePSResourceParameters.Name)
|
||||
|
||||
$savePSResourceParameters = @{
|
||||
Path = $PSDependTarget
|
||||
TrustRepository = $true
|
||||
Confirm = $false
|
||||
}
|
||||
|
||||
if ($currentModule -is [System.Collections.Hashtable])
|
||||
{
|
||||
$savePSResourceParameters.Name = $currentModule.ModuleName
|
||||
|
||||
if ($currentModule.RequiredVersion)
|
||||
{
|
||||
$savePSResourceParameters.Version = $currentModule.RequiredVersion
|
||||
}
|
||||
|
||||
if ($currentModule.Prerelease)
|
||||
{
|
||||
$savePSResourceParameters.Prerelease = $currentModule.Prerelease
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$savePSResourceParameters.Name = $currentModule
|
||||
}
|
||||
|
||||
# Modules that Sampler depend on that cannot be refreshed without a new session.
|
||||
$skipModule = @('powershell-yaml')
|
||||
|
||||
if ($savePSResourceParameters.Name -in $skipModule -and (Get-Module -Name 'powershell-yaml'))
|
||||
{
|
||||
Write-Progress -Activity 'PSResourceGet:' -PercentComplete $progressPercentage -CurrentOperation 'Restoring Build Dependencies' -Status ('Skipping module {0}' -f $savePSResourceParameters.Name)
|
||||
|
||||
Write-Information -MessageData ('Skipping the module {0} since it cannot be refresh while loaded into the session. To refresh the module open a new session and resolve dependencies again.' -f $savePSResourceParameters.Name) -InformationAction 'Continue'
|
||||
}
|
||||
else
|
||||
{
|
||||
# Clear all module from the current session so any new version fetched will be re-imported.
|
||||
Get-Module -Name $savePSResourceParameters.Name | Remove-Module -Force
|
||||
|
||||
Save-PSResource @savePSResourceParameters -ErrorVariable 'savePSResourceError'
|
||||
|
||||
if ($savePSResourceError)
|
||||
{
|
||||
Write-Warning -Message 'Save-PSResource could not save (replace) one or more dependencies. This can be due to the module is loaded into the session (and referencing assemblies). Close the current session and open a new session and try again.'
|
||||
}
|
||||
}
|
||||
|
||||
$progressPercentage += $percentagePerModule
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'PSResourceGet:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoking PSDepend'
|
||||
|
||||
Write-Progress -Activity 'PSDepend:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies'
|
||||
|
||||
$psDependParameters = @{
|
||||
Force = $true
|
||||
Path = $DependencyFile
|
||||
}
|
||||
|
||||
# TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified.
|
||||
Invoke-PSDepend @psDependParameters
|
||||
|
||||
Write-Progress -Activity 'PSDepend:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation 'Bootstrap complete' -Completed
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ($RegisterGallery)
|
||||
{
|
||||
Write-Verbose -Message "Removing private package repository '$Gallery'."
|
||||
Unregister-PSRepository -Name $Gallery
|
||||
}
|
||||
|
||||
if ($unregisteredPreviousRepository)
|
||||
{
|
||||
Write-Verbose -Message "Reverting private package repository '$Gallery' to previous location URI:s."
|
||||
|
||||
$registerPSRepositoryParameters = @{
|
||||
Name = $previousRegisteredRepository.Name
|
||||
InstallationPolicy = $previousRegisteredRepository.InstallationPolicy
|
||||
}
|
||||
|
||||
if ($previousRegisteredRepository.SourceLocation)
|
||||
{
|
||||
$registerPSRepositoryParameters.SourceLocation = $previousRegisteredRepository.SourceLocation
|
||||
}
|
||||
|
||||
if ($previousRegisteredRepository.PublishLocation)
|
||||
{
|
||||
$registerPSRepositoryParameters.PublishLocation = $previousRegisteredRepository.PublishLocation
|
||||
}
|
||||
|
||||
if ($previousRegisteredRepository.ScriptSourceLocation)
|
||||
{
|
||||
$registerPSRepositoryParameters.ScriptSourceLocation = $previousRegisteredRepository.ScriptSourceLocation
|
||||
}
|
||||
|
||||
if ($previousRegisteredRepository.ScriptPublishLocation)
|
||||
{
|
||||
$registerPSRepositoryParameters.ScriptPublishLocation = $previousRegisteredRepository.ScriptPublishLocation
|
||||
}
|
||||
|
||||
Register-PSRepository @registerPSRepositoryParameters
|
||||
}
|
||||
|
||||
# Only try to revert installation policy if the repository exist
|
||||
if ((Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue'))
|
||||
{
|
||||
if ($previousGalleryInstallationPolicy -ne $true)
|
||||
{
|
||||
# Reverting the Installation Policy for the given gallery if it was not already trusted
|
||||
Set-PSRepository -Name $Gallery -InstallationPolicy 'Untrusted'
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose -Message 'Project Bootstrapped, returning to Invoke-Build.'
|
||||
}
|
49
Resolve-Dependency.psd1
Normal file
49
Resolve-Dependency.psd1
Normal file
@@ -0,0 +1,49 @@
|
||||
@{
|
||||
<#
|
||||
Default parameter values to be loaded by the Resolve-Dependency.ps1 script (unless set in bound parameters
|
||||
when calling the script).
|
||||
#>
|
||||
|
||||
#PSDependTarget = './output/modules'
|
||||
#Proxy = ''
|
||||
#ProxyCredential = '$MyCredentialVariable' #TODO: find a way to support credentials in build (resolve variable)
|
||||
|
||||
Gallery = 'PSGallery'
|
||||
|
||||
# To use a private nuget repository change the following to your own feed. The locations must be a Nuget v2 feed due
|
||||
# to limitation in PowerShellGet v2.x. Example below is for a Azure DevOps Server project-scoped feed. While resolving
|
||||
# dependencies it will be registered as a trusted repository with the name specified in the property 'Gallery' above,
|
||||
# unless property 'Name' is provided in the hashtable below, if so it will override the property 'Gallery' above. The
|
||||
# registered repository will be removed when dependencies has been resolved, unless it was already registered to begin
|
||||
# with. If repository is registered already but with different URL:s the repository will be re-registered and reverted
|
||||
# after dependencies has been resolved. Currently only Windows integrated security works with private Nuget v2 feeds
|
||||
# (or if it is a public feed with no security), it is not possible yet to securely provide other credentials for the feed.
|
||||
# Private repositories will currently only work using PowerShellGet.
|
||||
#RegisterGallery = @{
|
||||
# #Name = 'MyPrivateFeedName'
|
||||
# GallerySourceLocation = 'https://azdoserver.company.local/<org_name>/<project_name>/_packaging/<feed_name>/nuget/v2'
|
||||
# GalleryPublishLocation = 'https://azdoserver.company.local/<org_name>/<project_name>/_packaging/<feed_name>/nuget/v2'
|
||||
# GalleryScriptSourceLocation = 'https://azdoserver.company.local/<org_name>/<project_name>/_packaging/<feed_name>/nuget/v2'
|
||||
# GalleryScriptPublishLocation = 'https://azdoserver.company.local/<org_name>/<project_name>/_packaging/<feed_name>/nuget/v2'
|
||||
# #InstallationPolicy = 'Trusted'
|
||||
#}
|
||||
|
||||
#AllowOldPowerShellGetModule = $true
|
||||
#MinimumPSDependVersion = '0.3.0'
|
||||
AllowPrerelease = $false
|
||||
WithYAML = $true # Will also bootstrap PowerShell-Yaml to read other config files
|
||||
|
||||
# Enable ModuleFast to resolve dependencies. Requires PowerShell 7.2 or higher.
|
||||
# If this is not configured or set to $false then PowerShellGet and PackageManagement
|
||||
# will be used to resolve dependencies.
|
||||
#UseModuleFast = $true
|
||||
|
||||
# Enable PSResourceGet to resolve dependencies. Requires PowerShell 7.2 or higher.
|
||||
# If this is not configured or set to $false then PowerShellGet and PackageManagement
|
||||
# will be used to resolve dependencies.
|
||||
#UsePSResourceGet = $true
|
||||
#PSResourceGetVersion = '1.0.0'
|
||||
#UsePowerShellGetCompatibilityModule = $true
|
||||
#UsePowerShellGetCompatibilityModuleVersion = '3.0.22-beta22'
|
||||
}
|
||||
|
538
build.ps1
Normal file
538
build.ps1
Normal file
@@ -0,0 +1,538 @@
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Bootstrap and build script for PowerShell module CI/CD pipeline.
|
||||
|
||||
.PARAMETER Tasks
|
||||
The task or tasks to run. The default value is '.' (runs the default task).
|
||||
|
||||
.PARAMETER CodeCoverageThreshold
|
||||
The code coverage target threshold to uphold. Set to 0 to disable.
|
||||
The default value is '' (empty string).
|
||||
|
||||
.PARAMETER BuildConfig
|
||||
Not yet written.
|
||||
|
||||
.PARAMETER OutputDirectory
|
||||
Specifies the folder to build the artefact into. The default value is 'output'.
|
||||
|
||||
.PARAMETER BuiltModuleSubdirectory
|
||||
Subdirectory name to build the module (under $OutputDirectory). The default
|
||||
value is '' (empty string).
|
||||
|
||||
.PARAMETER RequiredModulesDirectory
|
||||
Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency
|
||||
and PSDepend where to save the required modules. It is also possible to use
|
||||
'CurrentUser' och 'AllUsers' to install missing dependencies. You can override
|
||||
the value for PSDepend in the Build.psd1 build manifest. The default value is
|
||||
'output/RequiredModules'.
|
||||
|
||||
.PARAMETER PesterScript
|
||||
One or more paths that will override the Pester configuration in build
|
||||
configuration file when running the build task Invoke_Pester_Tests.
|
||||
|
||||
If running Pester 5 test, use the alias PesterPath to be future-proof.
|
||||
|
||||
.PARAMETER PesterTag
|
||||
Filter which tags to run when invoking Pester tests. This is used in the
|
||||
Invoke-Pester.pester.build.ps1 tasks.
|
||||
|
||||
.PARAMETER PesterExcludeTag
|
||||
Filter which tags to exclude when invoking Pester tests. This is used in
|
||||
the Invoke-Pester.pester.build.ps1 tasks.
|
||||
|
||||
.PARAMETER DscTestTag
|
||||
Filter which tags to run when invoking DSC Resource tests. This is used
|
||||
in the DscResource.Test.build.ps1 tasks.
|
||||
|
||||
.PARAMETER DscTestExcludeTag
|
||||
Filter which tags to exclude when invoking DSC Resource tests. This is
|
||||
used in the DscResource.Test.build.ps1 tasks.
|
||||
|
||||
.PARAMETER ResolveDependency
|
||||
Not yet written.
|
||||
|
||||
.PARAMETER BuildInfo
|
||||
The build info object from ModuleBuilder. Defaults to an empty hashtable.
|
||||
|
||||
.PARAMETER AutoRestore
|
||||
Not yet written.
|
||||
|
||||
.PARAMETER UseModuleFast
|
||||
Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies
|
||||
faster.
|
||||
|
||||
.PARAMETER UsePSResourceGet
|
||||
Specifies to use PSResourceGet instead of PowerShellGet to resolve dependencies
|
||||
faster. This can also be configured in Resolve-Dependency.psd1.
|
||||
|
||||
.PARAMETER UsePowerShellGetCompatibilityModule
|
||||
Specifies to use the compatibility module PowerShellGet. This parameter
|
||||
only works then the method of downloading dependencies is PSResourceGet.
|
||||
This can also be configured in Resolve-Dependency.psd1.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param
|
||||
(
|
||||
[Parameter(Position = 0)]
|
||||
[System.String[]]
|
||||
$Tasks = '.',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$CodeCoverageThreshold = '',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
[ValidateScript(
|
||||
{ Test-Path -Path $_ }
|
||||
)]
|
||||
$BuildConfig,
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$OutputDirectory = 'output',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$BuiltModuleSubdirectory = '',
|
||||
|
||||
[Parameter()]
|
||||
[System.String]
|
||||
$RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'),
|
||||
|
||||
[Parameter()]
|
||||
# This alias is to prepare for the rename of this parameter to PesterPath when Pester 4 support is removed
|
||||
[Alias('PesterPath')]
|
||||
[System.Object[]]
|
||||
$PesterScript,
|
||||
|
||||
[Parameter()]
|
||||
[System.String[]]
|
||||
$PesterTag,
|
||||
|
||||
[Parameter()]
|
||||
[System.String[]]
|
||||
$PesterExcludeTag,
|
||||
|
||||
[Parameter()]
|
||||
[System.String[]]
|
||||
$DscTestTag,
|
||||
|
||||
[Parameter()]
|
||||
[System.String[]]
|
||||
$DscTestExcludeTag,
|
||||
|
||||
[Parameter()]
|
||||
[Alias('bootstrap')]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$ResolveDependency,
|
||||
|
||||
[Parameter(DontShow)]
|
||||
[AllowNull()]
|
||||
[System.Collections.Hashtable]
|
||||
$BuildInfo,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$AutoRestore,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$UseModuleFast,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$UsePSResourceGet,
|
||||
|
||||
[Parameter()]
|
||||
[System.Management.Automation.SwitchParameter]
|
||||
$UsePowerShellGetCompatibilityModule
|
||||
)
|
||||
|
||||
<#
|
||||
The BEGIN block (at the end of this file) handles the Bootstrap of the Environment
|
||||
before Invoke-Build can run the tasks if the parameter ResolveDependency (or
|
||||
parameter alias Bootstrap) is specified.
|
||||
#>
|
||||
|
||||
process
|
||||
{
|
||||
if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1')
|
||||
{
|
||||
# Only run the process block through InvokeBuild (look at the Begin block at the bottom of this script).
|
||||
return
|
||||
}
|
||||
|
||||
# Execute the Build process from the .build.ps1 path.
|
||||
Push-Location -Path $PSScriptRoot -StackName 'BeforeBuild'
|
||||
|
||||
try
|
||||
{
|
||||
Write-Host -Object "[build] Parsing defined tasks" -ForeGroundColor Magenta
|
||||
|
||||
# Load the default BuildInfo if the parameter BuildInfo is not set.
|
||||
if (-not $PSBoundParameters.ContainsKey('BuildInfo'))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Test-Path -Path $BuildConfig)
|
||||
{
|
||||
$configFile = Get-Item -Path $BuildConfig
|
||||
|
||||
Write-Host -Object "[build] Loading Configuration from $configFile"
|
||||
|
||||
$BuildInfo = switch -Regex ($configFile.Extension)
|
||||
{
|
||||
# Native Support for PSD1
|
||||
'\.psd1'
|
||||
{
|
||||
if (-not (Get-Command -Name Import-PowerShellDataFile -ErrorAction SilentlyContinue))
|
||||
{
|
||||
Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion 3.1.0.0
|
||||
}
|
||||
|
||||
Import-PowerShellDataFile -Path $BuildConfig
|
||||
}
|
||||
|
||||
# Support for yaml when module PowerShell-Yaml is available
|
||||
'\.[yaml|yml]'
|
||||
{
|
||||
Import-Module -Name 'powershell-yaml' -ErrorAction Stop
|
||||
|
||||
ConvertFrom-Yaml -Yaml (Get-Content -Raw $configFile)
|
||||
}
|
||||
|
||||
# Support for JSON and JSONC (by Removing comments) when module PowerShell-Yaml is available
|
||||
'\.[json|jsonc]'
|
||||
{
|
||||
$jsonFile = Get-Content -Raw -Path $configFile
|
||||
|
||||
$jsonContent = $jsonFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/'
|
||||
|
||||
# Yaml is superset of JSON.
|
||||
ConvertFrom-Yaml -Yaml $jsonContent
|
||||
}
|
||||
|
||||
# Unknown extension, return empty hashtable.
|
||||
default
|
||||
{
|
||||
Write-Error -Message "Extension '$_' not supported. using @{}"
|
||||
|
||||
@{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host -Object "Configuration file '$($BuildConfig.FullName)' not found" -ForegroundColor Red
|
||||
|
||||
# No config file was found, return empty hashtable.
|
||||
$BuildInfo = @{ }
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
$logMessage = "Error loading Config '$($BuildConfig.FullName)'.`r`nAre you missing dependencies?`r`nMake sure you run './build.ps1 -ResolveDependency -tasks noop' before running build to restore the required modules."
|
||||
|
||||
Write-Host -Object $logMessage -ForegroundColor Yellow
|
||||
|
||||
$BuildInfo = @{ }
|
||||
|
||||
Write-Error -Message $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
# If the Invoke-Build Task Header is specified in the Build Info, set it.
|
||||
if ($BuildInfo.TaskHeader)
|
||||
{
|
||||
Set-BuildHeader -Script ([scriptblock]::Create($BuildInfo.TaskHeader))
|
||||
}
|
||||
|
||||
<#
|
||||
Add BuildModuleOutput to PSModule Path environment variable.
|
||||
Moved here (not in begin block) because build file can contains BuiltSubModuleDirectory value.
|
||||
#>
|
||||
if ($BuiltModuleSubdirectory)
|
||||
{
|
||||
if (-not (Split-Path -IsAbsolute -Path $BuiltModuleSubdirectory))
|
||||
{
|
||||
$BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuiltModuleSubdirectory
|
||||
}
|
||||
else
|
||||
{
|
||||
$BuildModuleOutput = $BuiltModuleSubdirectory
|
||||
}
|
||||
} # test if BuiltModuleSubDirectory set in build config file
|
||||
elseif ($BuildInfo.ContainsKey('BuiltModuleSubDirectory'))
|
||||
{
|
||||
$BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuildInfo['BuiltModuleSubdirectory']
|
||||
}
|
||||
else
|
||||
{
|
||||
$BuildModuleOutput = $OutputDirectory
|
||||
}
|
||||
|
||||
# Pre-pending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder.
|
||||
if ($powerShellModulePaths -notcontains $BuildModuleOutput)
|
||||
{
|
||||
Write-Host -Object "[build] Pre-pending '$BuildModuleOutput' folder to PSModulePath" -ForegroundColor Green
|
||||
|
||||
$env:PSModulePath = $BuildModuleOutput + [System.IO.Path]::PathSeparator + $env:PSModulePath
|
||||
}
|
||||
|
||||
<#
|
||||
Import Tasks from modules via their exported aliases when defined in Build Manifest.
|
||||
https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks
|
||||
#>
|
||||
if ($BuildInfo.ContainsKey('ModuleBuildTasks'))
|
||||
{
|
||||
foreach ($module in $BuildInfo['ModuleBuildTasks'].Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
Write-Host -Object "Importing tasks from module $module" -ForegroundColor DarkGray
|
||||
|
||||
$loadedModule = Import-Module -Name $module -PassThru -ErrorAction Stop
|
||||
|
||||
foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($module))
|
||||
{
|
||||
$loadedModule.ExportedAliases.GetEnumerator().Where{
|
||||
Write-Host -Object "`t Loading $($_.Key)..." -ForegroundColor DarkGray
|
||||
|
||||
# Using -like to support wildcard.
|
||||
$_.Key -like $TaskToExport
|
||||
}.ForEach{
|
||||
# Dot-sourcing the Tasks via their exported aliases.
|
||||
. (Get-Alias $_.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host -Object "Could not load tasks for module $module." -ForegroundColor Red
|
||||
|
||||
Write-Error -Message $_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name).
|
||||
Get-ChildItem -Path '.build/' -Recurse -Include '*.ps1' -ErrorAction Ignore |
|
||||
ForEach-Object {
|
||||
"Importing file $($_.BaseName)" | Write-Verbose
|
||||
|
||||
. $_.FullName
|
||||
}
|
||||
|
||||
# Synopsis: Empty task, useful to test the bootstrap process.
|
||||
task noop { }
|
||||
|
||||
# Define default task sequence ("."), can be overridden in the $BuildInfo.
|
||||
task . {
|
||||
Write-Build -Object 'No sequence currently defined for the default task' -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host -Object 'Adding Workflow from configuration:' -ForegroundColor DarkGray
|
||||
|
||||
# Load Invoke-Build task sequences/workflows from $BuildInfo.
|
||||
foreach ($workflow in $BuildInfo.BuildWorkflow.keys)
|
||||
{
|
||||
Write-Verbose -Message "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')."
|
||||
|
||||
$workflowItem = $BuildInfo.BuildWorkflow.($workflow)
|
||||
|
||||
if ($workflowItem.Trim() -match '^\{(?<sb>[\w\W]*)\}$')
|
||||
{
|
||||
$workflowItem = [ScriptBlock]::Create($Matches['sb'])
|
||||
}
|
||||
|
||||
Write-Host -Object " +-> $workflow" -ForegroundColor DarkGray
|
||||
|
||||
task $workflow $workflowItem
|
||||
}
|
||||
|
||||
Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
Pop-Location -StackName 'BeforeBuild'
|
||||
}
|
||||
}
|
||||
|
||||
begin
|
||||
{
|
||||
# Find build config if not specified.
|
||||
if (-not $BuildConfig)
|
||||
{
|
||||
$config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction Ignore
|
||||
|
||||
if (-not $config -or ($config -is [System.Array] -and $config.Length -le 0))
|
||||
{
|
||||
throw 'No build configuration found. Specify path via parameter BuildConfig.'
|
||||
}
|
||||
elseif ($config -is [System.Array])
|
||||
{
|
||||
if ($config.Length -gt 1)
|
||||
{
|
||||
throw 'More than one build configuration found. Specify which path to use via parameter BuildConfig.'
|
||||
}
|
||||
|
||||
$BuildConfig = $config[0]
|
||||
}
|
||||
else
|
||||
{
|
||||
$BuildConfig = $config
|
||||
}
|
||||
}
|
||||
|
||||
# Bootstrapping the environment before using Invoke-Build as task runner
|
||||
|
||||
if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1')
|
||||
{
|
||||
Write-Host -Object "[pre-build] Starting Build Init" -ForegroundColor Green
|
||||
|
||||
Push-Location $PSScriptRoot -StackName 'BuildModule'
|
||||
}
|
||||
|
||||
if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers'))
|
||||
{
|
||||
# Installing modules instead of saving them.
|
||||
Write-Host -Object "[pre-build] Required Modules will be installed to the PowerShell module path that is used for $RequiredModulesDirectory." -ForegroundColor Green
|
||||
|
||||
<#
|
||||
The variable $PSDependTarget will be used below when building the splatting
|
||||
variable before calling Resolve-Dependency.ps1, unless overridden in the
|
||||
file Resolve-Dependency.psd1.
|
||||
#>
|
||||
$PSDependTarget = $RequiredModulesDirectory
|
||||
}
|
||||
else
|
||||
{
|
||||
if (-not (Split-Path -IsAbsolute -Path $OutputDirectory))
|
||||
{
|
||||
$OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory
|
||||
}
|
||||
|
||||
# Resolving the absolute path to save the required modules to.
|
||||
if (-not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory))
|
||||
{
|
||||
$RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory
|
||||
}
|
||||
|
||||
# Create the output/modules folder if not exists, or resolve the Absolute path otherwise.
|
||||
if (Resolve-Path -Path $RequiredModulesDirectory -ErrorAction SilentlyContinue)
|
||||
{
|
||||
Write-Debug -Message "[pre-build] Required Modules path already exist at $RequiredModulesDirectory"
|
||||
|
||||
$requiredModulesPath = Convert-Path -Path $RequiredModulesDirectory
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host -Object "[pre-build] Creating required modules directory $RequiredModulesDirectory." -ForegroundColor Green
|
||||
|
||||
$requiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName
|
||||
}
|
||||
|
||||
$powerShellModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator
|
||||
|
||||
# Pre-pending $requiredModulesPath folder to PSModulePath to resolve from this folder FIRST.
|
||||
if ($RequiredModulesDirectory -notin @('CurrentUser', 'AllUsers') -and
|
||||
($powerShellModulePaths -notcontains $RequiredModulesDirectory))
|
||||
{
|
||||
Write-Host -Object "[pre-build] Pre-pending '$RequiredModulesDirectory' folder to PSModulePath" -ForegroundColor Green
|
||||
|
||||
$env:PSModulePath = $RequiredModulesDirectory + [System.IO.Path]::PathSeparator + $env:PSModulePath
|
||||
}
|
||||
|
||||
$powerShellYamlModule = Get-Module -Name 'powershell-yaml' -ListAvailable
|
||||
$invokeBuildModule = Get-Module -Name 'InvokeBuild' -ListAvailable
|
||||
$psDependModule = Get-Module -Name 'PSDepend' -ListAvailable
|
||||
|
||||
# Checking if the user should -ResolveDependency.
|
||||
if (-not ($powerShellYamlModule -and $invokeBuildModule -and $psDependModule) -and -not $ResolveDependency)
|
||||
{
|
||||
if ($AutoRestore -or -not $PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build')
|
||||
{
|
||||
Write-Host -Object "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" -ForegroundColor Yellow
|
||||
|
||||
$ResolveDependency = $true
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Warning -Message "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter. Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task."
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
The variable $PSDependTarget will be used below when building the splatting
|
||||
variable before calling Resolve-Dependency.ps1, unless overridden in the
|
||||
file Resolve-Dependency.psd1.
|
||||
#>
|
||||
$PSDependTarget = $requiredModulesPath
|
||||
}
|
||||
|
||||
if ($ResolveDependency)
|
||||
{
|
||||
Write-Host -Object "[pre-build] Resolving dependencies using preferred method." -ForegroundColor Green
|
||||
|
||||
$resolveDependencyParams = @{ }
|
||||
|
||||
# If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency.
|
||||
if ($BuildConfig -match '\.[yaml|yml]$')
|
||||
{
|
||||
$resolveDependencyParams.Add('WithYaml', $true)
|
||||
}
|
||||
|
||||
$resolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').Parameters.Keys
|
||||
|
||||
foreach ($cmdParameter in $resolveDependencyAvailableParams)
|
||||
{
|
||||
# The parameter has been explicitly used for calling the .build.ps1
|
||||
if ($MyInvocation.BoundParameters.ContainsKey($cmdParameter))
|
||||
{
|
||||
$paramValue = $MyInvocation.BoundParameters.Item($cmdParameter)
|
||||
|
||||
Write-Debug " adding $cmdParameter :: $paramValue [from user-provided parameters to Build.ps1]"
|
||||
|
||||
$resolveDependencyParams.Add($cmdParameter, $paramValue)
|
||||
}
|
||||
# Use defaults parameter value from Build.ps1, if any
|
||||
else
|
||||
{
|
||||
$paramValue = Get-Variable -Name $cmdParameter -ValueOnly -ErrorAction Ignore
|
||||
|
||||
if ($paramValue)
|
||||
{
|
||||
Write-Debug " adding $cmdParameter :: $paramValue [from default Build.ps1 variable]"
|
||||
|
||||
$resolveDependencyParams.Add($cmdParameter, $paramValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host -Object "[pre-build] Starting bootstrap process." -ForegroundColor Green
|
||||
|
||||
.\Resolve-Dependency.ps1 @resolveDependencyParams
|
||||
}
|
||||
|
||||
if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1')
|
||||
{
|
||||
Write-Verbose -Message "Bootstrap completed. Handing back to InvokeBuild."
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('ResolveDependency'))
|
||||
{
|
||||
Write-Verbose -Message "Dependency already resolved. Removing task."
|
||||
|
||||
$null = $PSBoundParameters.Remove('ResolveDependency')
|
||||
}
|
||||
|
||||
Write-Host -Object "[build] Starting build with InvokeBuild." -ForegroundColor Green
|
||||
|
||||
Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path
|
||||
|
||||
Pop-Location -StackName 'BuildModule'
|
||||
|
||||
return
|
||||
}
|
||||
}
|
151
build.yaml
Normal file
151
build.yaml
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
####################################################
|
||||
# ModuleBuilder Configuration #
|
||||
####################################################
|
||||
|
||||
BuiltModuleSubdirectory: module
|
||||
CopyPaths:
|
||||
- en-US
|
||||
- tests
|
||||
- helper
|
||||
# - DSCResources
|
||||
# - Modules
|
||||
Encoding: UTF8
|
||||
# Can be used to manually specify module's semantic version if the preferred method of
|
||||
# using GitVersion is not available, and it is not possible to set the session environment
|
||||
# variable `$env:ModuleVersion`, nor setting the variable `$ModuleVersion`, in the
|
||||
# PowerShell session (parent scope) before running the task `build`.
|
||||
#SemVer: '99.0.0-preview1'
|
||||
|
||||
# Suffix to add to Root module PSM1 after merge (here, the Set-Alias exporting IB tasks)
|
||||
# suffix: suffix.ps1
|
||||
# prefix: prefix.ps1
|
||||
VersionedOutputDirectory: true
|
||||
|
||||
####################################################
|
||||
# ModuleBuilder Submodules Configuration #
|
||||
####################################################
|
||||
|
||||
NestedModule:
|
||||
# HelperSubmodule: # This is the first submodule to build into the output
|
||||
# Path: ./*/Modules/HelperSubmodule/HelperSubmodule.psd1
|
||||
# # is trimmed (remove metadata & Prerelease tag) and OutputDirectory expanded (the only one)
|
||||
# OutputDirectory: ///Modules/HelperSubmodule
|
||||
# VersionedOutputDirectory: false
|
||||
# AddToManifest: false
|
||||
# SemVer:
|
||||
# # suffix:
|
||||
# # prefix:
|
||||
|
||||
####################################################
|
||||
# Sampler Pipeline Configuration #
|
||||
####################################################
|
||||
# Defining 'Workflows' (suite of InvokeBuild tasks) to be run using their alias
|
||||
BuildWorkflow:
|
||||
'.': # "." is the default Invoke-Build workflow. It is called when no -Tasks is specified to the build.ps1
|
||||
- build
|
||||
- test
|
||||
|
||||
build:
|
||||
- Clean
|
||||
- Build_Module_ModuleBuilder
|
||||
- Build_NestedModules_ModuleBuilder
|
||||
- Create_changelog_release_output
|
||||
|
||||
|
||||
pack:
|
||||
- build
|
||||
- package_module_nupkg
|
||||
|
||||
|
||||
|
||||
# Defining test task to be run when invoking `./build.ps1 -Tasks test`
|
||||
test:
|
||||
# Uncomment to modify the PSModulePath in the test pipeline (also requires the build configuration section SetPSModulePath).
|
||||
#- Set_PSModulePath
|
||||
- Pester_Tests_Stop_On_Fail
|
||||
# Use this task if pipeline uses code coverage and the module is using the
|
||||
# pattern of Public, Private, Enum, Classes.
|
||||
#- Convert_Pester_Coverage
|
||||
- Pester_if_Code_Coverage_Under_Threshold
|
||||
|
||||
# Use this task when you have multiple parallel tests, which produce multiple
|
||||
# code coverage files and needs to get merged into one file.
|
||||
#merge:
|
||||
#- Merge_CodeCoverage_Files
|
||||
|
||||
publish:
|
||||
- publish_module_to_gallery
|
||||
|
||||
- Publish_Release_To_GitHub
|
||||
|
||||
|
||||
####################################################
|
||||
# PESTER Configuration #
|
||||
####################################################
|
||||
|
||||
Pester:
|
||||
OutputFormat: NUnitXML
|
||||
# Excludes one or more paths from being used to calculate code coverage.
|
||||
ExcludeFromCodeCoverage:
|
||||
|
||||
# If no scripts are defined the default is to use all the tests under the project's
|
||||
# tests folder or source folder (if present). Test script paths can be defined to
|
||||
# only run tests in certain folders, or run specific test files, or can be use to
|
||||
# specify the order tests are run.
|
||||
Script:
|
||||
# - tests/QA/module.tests.ps1
|
||||
# - tests/QA
|
||||
# - tests/Unit
|
||||
# - tests/Integration
|
||||
ExcludeTag:
|
||||
# - helpQuality
|
||||
# - FunctionalQuality
|
||||
# - TestQuality
|
||||
Tag:
|
||||
CodeCoverageThreshold: 85 # Set to 0 to bypass
|
||||
#CodeCoverageOutputFile: JaCoCo_$OsShortName.xml
|
||||
#CodeCoverageOutputFileEncoding: ascii
|
||||
# Use this if code coverage should be merged from several pipeline test jobs.
|
||||
# Any existing keys above should be replaced. See also CodeCoverage below.
|
||||
# CodeCoverageOutputFile is the file that is created for each pipeline test job.
|
||||
#CodeCoverageOutputFile: JaCoCo_Merge.xml
|
||||
|
||||
# Use this to merged code coverage from several pipeline test jobs.
|
||||
# CodeCoverageFilePattern - the pattern used to search all pipeline test job artifacts
|
||||
# after the file specified in CodeCoverageOutputFile.
|
||||
# CodeCoverageMergedOutputFile - the file that is created by the merge build task and
|
||||
# is the file that should be uploaded to code coverage services.
|
||||
#CodeCoverage:
|
||||
#CodeCoverageFilePattern: JaCoCo_Merge.xml # the pattern used to search all pipeline test job artifacts
|
||||
#CodeCoverageMergedOutputFile: JaCoCo_coverage.xml # the file that is created for the merged code coverage
|
||||
|
||||
|
||||
# Import ModuleBuilder tasks from a specific PowerShell module using the build
|
||||
# task's alias. Wildcard * can be used to specify all tasks that has a similar
|
||||
# prefix and or suffix. The module contain the task must be added as a required
|
||||
# module in the file RequiredModules.psd1.
|
||||
ModuleBuildTasks:
|
||||
Sampler:
|
||||
- '*.build.Sampler.ib.tasks'
|
||||
Sampler.GitHubTasks:
|
||||
- '*.ib.tasks'
|
||||
|
||||
|
||||
# Invoke-Build Header to be used to 'decorate' the terminal output of the tasks.
|
||||
TaskHeader: |
|
||||
param($Path)
|
||||
""
|
||||
"=" * 79
|
||||
Write-Build Cyan "`t`t`t$($Task.Name.replace("_"," ").ToUpper())"
|
||||
Write-Build DarkGray "$(Get-BuildSynopsis $Task)"
|
||||
"-" * 79
|
||||
Write-Build DarkGray " $Path"
|
||||
Write-Build DarkGray " $($Task.InvocationInfo.ScriptName):$($Task.InvocationInfo.ScriptLineNumber)"
|
||||
""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
378
docs/index.html
Normal file
378
docs/index.html
Normal file
@@ -0,0 +1,378 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
<auto-generated>
|
||||
<synopsis>
|
||||
This code was generated by a tool. on: 03/25/2024 08:16:49
|
||||
</synopsis>
|
||||
<description>
|
||||
If you'd like to regenerate the documentation, please open up powershell and run
|
||||
|
||||
> .\psDoc.ps1 -moduleName NameOfYourModule
|
||||
|
||||
If the documentation is incomplete, or eronious,
|
||||
please edit the comments at the top of the module method within it's respecive .ps1 file.
|
||||
</description>
|
||||
</auto-generated>
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>M365FoundationsCISReport Documentation</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCoreDefault.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" charset="utf-8">
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
.syntaxhighlighter {
|
||||
overflow-y: hidden !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
pre {
|
||||
min-height: 30px;
|
||||
}
|
||||
.navbar-nav {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.form-group {
|
||||
padding-top: 12px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.sidebar-nav .navbar-header {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar li a {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-nav .navbar .navbar-collapse {
|
||||
padding: 0;
|
||||
max-height: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul:not {
|
||||
display: block;
|
||||
}
|
||||
.sidebar-nav .navbar li {
|
||||
float: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div><h1>M365FoundationsCISReport</h1></div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="col-lg-3 col-md-4 col-sm-5 col-xs-12">
|
||||
<div class="sidebar-nav">
|
||||
<div class="navbar navbar-default" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-navbar-collapse">
|
||||
<span class="sr-only">Toggle</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<span class="visible-xs navbar-brand">click menu to open</span>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse sidebar-navbar-collapse">
|
||||
|
||||
<div class="form-group">
|
||||
<input class="form-control" id="searchinput" type="search" placeholder="Filter..." />
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav list-group" id="searchList">
|
||||
<li class="nav-menu list-group-item"><a href="#Invoke-M365SecurityAudit">Invoke-M365SecurityAudit</a></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8 col-sm-7 col-xs-12">
|
||||
<div id="Invoke-M365SecurityAudit" class="toggle_container">
|
||||
<div class="page-header">
|
||||
<h2> Invoke-M365SecurityAudit </h2>
|
||||
<p>Invokes a security audit for Microsoft 365 environments.</p>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3> Syntax </h3>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class='panel-body'>
|
||||
<pre class="brush: ps">Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-ELevel <String>] [-ProfileLevel <String>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG1] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG2] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeIG3] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-IncludeRecommendation <String[]>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]
|
||||
|
||||
Invoke-M365SecurityAudit -TenantAdminUrl <String> -DomainName <String> [-SkipRecommendation <String[]>] [-DoNotConnect] [-DoNotDisconnect] [-NoModuleCheck] [-WhatIf] [-Confirm] [<CommonParameters>]</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3> Parameters </h3>
|
||||
<table class="table table-striped table-bordered table-condensed visible-on">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="visible-lg visible-md">Alias</th>
|
||||
<th>Description</th>
|
||||
<th class="visible-lg visible-md">Required?</th>
|
||||
<th class="visible-lg">Pipeline Input</th>
|
||||
<th class="visible-lg">Default Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><nobr>-TenantAdminUrl</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>The URL of the tenant admin. This parameter is mandatory.</td>
|
||||
<td class="visible-lg visible-md">true</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-DomainName</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>The domain name of the Microsoft 365 environment. This parameter is mandatory.</td>
|
||||
<td class="visible-lg visible-md">true</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-ELevel</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-ProfileLevel</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-IncludeIG1</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, includes tests where IG1 is true.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-IncludeIG2</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, includes tests where IG2 is true.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-IncludeIG3</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, includes tests where IG3 is true.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-IncludeRecommendation</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-SkipRecommendation</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-DoNotConnect</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, the cmdlet will not establish a connection to Microsoft 365 services.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-DoNotDisconnect</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, the cmdlet will not disconnect from Microsoft 365 services after execution.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-NoModuleCheck</nobr></td>
|
||||
<td class="visible-lg visible-md"></td>
|
||||
<td>If specified, the cmdlet will not check for the presence of required modules.</td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg">False</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-WhatIf</nobr></td>
|
||||
<td class="visible-lg visible-md">wi</td>
|
||||
<td></td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><nobr>-Confirm</nobr></td>
|
||||
<td class="visible-lg visible-md">cf</td>
|
||||
<td></td>
|
||||
<td class="visible-lg visible-md">false</td>
|
||||
<td class="visible-lg">false</td>
|
||||
<td class="visible-lg"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<h3> Inputs </h3>
|
||||
<p>The input type is the type of the objects that you can pipe to the cmdlet.</p>
|
||||
<ul><li>None. You cannot pipe objects to Invoke-M365SecurityAudit.</li></ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3> Outputs </h3>
|
||||
<p>The output type is the type of the objects that the cmdlet emits.</p>
|
||||
<ul><li>CISAuditResult[]
|
||||
The cmdlet returns an array of CISAuditResult objects representing the results of the security audit.</li></ul>
|
||||
</div>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'>
|
||||
<h3 class='panel-title'> Note </h3>
|
||||
</div>
|
||||
<div class='panel-body'>This module is based on CIS benchmarks and is governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. For more details, visit:
|
||||
https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3> Examples </h3>
|
||||
</div>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-body'>
|
||||
<strong>EXAMPLE 1</strong>
|
||||
<pre class="brush: ps">Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -ELevel "E5" -ProfileLevel "L1"</pre>
|
||||
<div>Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment.</div>
|
||||
<strong>EXAMPLE 2</strong>
|
||||
<pre class="brush: ps">Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1</pre>
|
||||
<div>Performs an audit including all tests where IG1 is true.</div>
|
||||
<strong>EXAMPLE 3</strong>
|
||||
<pre class="brush: ps">Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -SkipRecommendation '1.1.3', '2.1.1'</pre>
|
||||
<div>Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1.</div>
|
||||
<strong>EXAMPLE 4</strong>
|
||||
<pre class="brush: ps">$auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com"
|
||||
PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation</pre>
|
||||
<div>Captures the audit results into a variable and exports them to a CSV file.</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class='pull-right'><a onclick='document.body.scrollTop = document.documentElement.scrollTop = 0;' style='cursor: pointer;'>Top of page</a>
|
||||
<div>
|
||||
<h3> Links </h3>
|
||||
<div>
|
||||
<ul>
|
||||
<li class='psLink'><a href='#Online Version: [GitHub Repository URL]' target='_top'>Online Version: [GitHub Repository URL]</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPowerShell.min.js" charset="utf-8"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(".toggle_container").hide();
|
||||
var previousId;
|
||||
if(location.hash) {
|
||||
var id = location.hash.slice(1); //Get rid of the # mark
|
||||
var elementToShow = $("#" + id); //Save local reference
|
||||
if(elementToShow.length) { //Check if the element exists
|
||||
elementToShow.slideToggle('fast'); //Show the element
|
||||
elementToShow.addClass("check_list_selected"); //Add class to element (the link)
|
||||
}
|
||||
previousId = id;
|
||||
}
|
||||
|
||||
$('.nav-menu a, .psLink a').click(function() {
|
||||
$(".sidebar-navbar-collapse").collapse('hide');
|
||||
$('.toggle_container').hide(); // Hide all
|
||||
var elem = $(this).prop("hash");
|
||||
$(elem).toggle('fast'); // Show HREF/to/ID one
|
||||
history.pushState({}, '', $(this).attr("href"));
|
||||
window.scrollTo(0, 0);
|
||||
return false;
|
||||
});
|
||||
SyntaxHighlighter.defaults['toolbar'] = false;
|
||||
SyntaxHighlighter.defaults['gutter'] = false;
|
||||
SyntaxHighlighter.all();
|
||||
|
||||
$('#searchList').btsListFilter('#searchinput', {itemChild: 'a', initial: false, resetOnBlur: true});
|
||||
|
||||
$(document).keyup(function( e ) {
|
||||
if(!$('#searchinput').is(':focus') && e.which >= 65 && e.which <= 90 ){
|
||||
$('#searchinput').val(String.fromCharCode(e.keyCode));
|
||||
$('#searchinput').focus();
|
||||
}
|
||||
});
|
||||
$(document).click(function() {
|
||||
$('#searchinput').blur();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- bootstrap-list-filter.min.js - removed as external resource and added as content -->
|
||||
<script>
|
||||
/*
|
||||
* bootstrap-list-filter v0.1.7 - 2015-03-30
|
||||
*
|
||||
* Copyright 2015 Stefano Cudini
|
||||
* stefano.cudini@gmail.com
|
||||
* http://labs.easyblog.it/
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Demos:
|
||||
* http://labs.easyblog.it/bootstrap-list-filter/
|
||||
*
|
||||
* Source:
|
||||
* git@github.com:stefanocudini/bootstrap-list-filter.git
|
||||
*
|
||||
*/
|
||||
!function(a){a.fn.btsListFilter=function(b,c){function d(a,b){return a.replace(/\{ *([\w_]+) *\}/g,function(a,c){return b[c]||""})}function e(a,b){var c;return b=b||300,function(){var d=this,e=arguments;clearTimeout(c),c=setTimeout(function(){a.apply(d,Array.prototype.slice.call(e))},b)}}var f,g=this,h=a(this),i=a(b),j=h;return c=a.extend({delay:300,minLength:1,initial:!0,eventKey:"keyup",resetOnBlur:!0,sourceData:null,sourceTmpl:'<a class="list-group-item" href="#"><span>{title}</span></a>',sourceNode:function(a){return d(c.sourceTmpl,a)},emptyNode:function(){return'<a class="list-group-item well" href="#"><span>No Results</span></a>'},itemEl:".list-group-item",itemChild:null,itemFilter:function(b,d){d=d&&d.replace(new RegExp("[({[^.$*+?\\]})]","g"),"");var e=a(b).text(),f=c.initial?"^":"",g=new RegExp(f+d,"i");return g.test(e)}},c),i.on(c.eventKey,e(function(){var b=a(this).val();c.itemEl&&(j=h.find(c.itemEl)),c.itemChild&&(j=j.find(c.itemChild));var d=j.filter(function(){return c.itemFilter.call(g,this,b)}),e=j.not(d);c.itemChild&&(d=d.parents(c.itemEl),e=e.parents(c.itemEl).hide()),""!==b&&b.length>=c.minLength?(d.show(),e.hide(),"function"===a.type(c.sourceData)?(d.hide(),e.hide(),f&&(a.isFunction(f.abort)?f.abort():a.isFunction(f.stop)&&f.stop()),f=c.sourceData.call(g,b,function(b){if(f=null,d.hide(),e.hide(),h.find(".bts-dynamic-item").remove(),b&&0!==b.length)for(var i in b)a(c.sourceNode.call(g,b[i])).addClass("bts-dynamic-item").appendTo(h);else a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)})):0===d.length&&a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)):(d.show(),e.show(),h.find(".bts-dynamic-item").remove())},c.delay)),c.resetOnBlur&&i.on("blur",function(){a(this).val("").trigger(c.eventKey)}),h}}(jQuery);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3
helpers/Build-Help.ps1
Normal file
3
helpers/Build-Help.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
|
||||
.\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir docs -template ".\helpers\psDoc-master\src\out-html-template.ps1"
|
||||
.\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir ".\" -template ".\helpers\psDoc-master\src\out-markdown-template.ps1" -fileName ".\README.md"
|
@@ -0,0 +1,59 @@
|
||||
function Test-AntiPhishingPolicy_2.1.7_E5L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
|
||||
# Retrieve and validate the anti-phishing policies
|
||||
$antiPhishPolicies = Get-AntiPhishPolicy
|
||||
$validatedPolicies = $antiPhishPolicies | Where-Object {
|
||||
$_.Enabled -eq $true -and
|
||||
$_.PhishThresholdLevel -ge 2 -and
|
||||
$_.EnableMailboxIntelligenceProtection -eq $true -and
|
||||
$_.EnableMailboxIntelligence -eq $true -and
|
||||
$_.EnableSpoofIntelligence -eq $true
|
||||
}
|
||||
|
||||
# Check if there is at least one policy that meets the requirements
|
||||
$isCompliant = $validatedPolicies.Count -gt 0
|
||||
|
||||
# Prepare failure details if policies are not compliant
|
||||
$failureDetails = if (-not $isCompliant) {
|
||||
"No anti-phishing policy is fully compliant with CIS benchmark requirements."
|
||||
} else {
|
||||
"Compliant Anti-Phish Policy Names: " + ($validatedPolicies.Name -join ', ')
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.7"
|
||||
$auditResult.RecDescription = "Ensure that an anti-phishing policy has been created"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = $failureDetails
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "Anti-phishing policies do not meet CIS benchmark requirements." } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-AuditDisabledFalse_6.1.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False'
|
||||
# Pass if AuditDisabled is False. Fail otherwise.
|
||||
$auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled
|
||||
$auditNotDisabled = -not $auditDisabledConfig.AuditDisabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditNotDisabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.1"
|
||||
$auditResult.RecDescription = "Ensure 'AuditDisabled' organizationally is set to 'False'"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditNotDisabled
|
||||
$auditResult.Details = if ($auditNotDisabled) { "Audit is not disabled organizationally" } else { "Audit is disabled organizationally" }
|
||||
$auditResult.FailureReason = if (-not $auditNotDisabled) { "AuditDisabled is set to True" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-AuditLogSearch_3.1.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled
|
||||
# Pass if UnifiedAuditLogIngestionEnabled is True. Fail otherwise.
|
||||
$auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled
|
||||
$auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditLogResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "3.1.1"
|
||||
$auditResult.RecDescription = "Ensure Microsoft 365 audit log search is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditLogResult
|
||||
$auditResult.Details = "UnifiedAuditLogIngestionEnabled: $($auditLogConfig.UnifiedAuditLogIngestionEnabled)"
|
||||
$auditResult.FailureReason = if (-not $auditLogResult) { "Audit log search is not enabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-BlockChannelEmails_8.1.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.1.2 (L1) Ensure users can't send emails to a channel email address
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global
|
||||
$allowEmailIntoChannel = $teamsClientConfig.AllowEmailIntoChannel
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # This control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.1.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the benchmark
|
||||
$auditResult.IG2 = $false # Set based on the benchmark
|
||||
$auditResult.IG3 = $false # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure users can't send emails to a channel email address"
|
||||
$auditResult.Result = -not $allowEmailIntoChannel
|
||||
$auditResult.Details = "AllowEmailIntoChannel is set to $allowEmailIntoChannel"
|
||||
$auditResult.FailureReason = if ($allowEmailIntoChannel) { "Emails can be sent to a channel email address" } else { "N/A" }
|
||||
$auditResult.Status = if (-not $allowEmailIntoChannel) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
function Test-BlockMailForwarding_6.2.1_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "6.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure all forms of mail forwarding are blocked and/or disabled"
|
||||
}
|
||||
|
||||
process {
|
||||
# Verify that no rules are forwarding the email to external domains
|
||||
$transportRules = Get-TransportRule | Where-Object { $_.RedirectMessageTo -ne $null }
|
||||
$forwardingBlocked = $transportRules.Count -eq 0
|
||||
|
||||
$auditResult.Result = $forwardingBlocked
|
||||
$auditResult.Details = if ($transportRules.Count -gt 0) {
|
||||
$transportRules | ForEach-Object {
|
||||
"$($_.Name) redirects to $($_.RedirectMessageTo)"
|
||||
} -join " | "
|
||||
} else {
|
||||
"Step 1: No forwarding rules found. Please proceed with Step 2 described in CIS Benchmark."
|
||||
}
|
||||
$auditResult.FailureReason = if (-not $forwardingBlocked) {
|
||||
"Mail forwarding rules found: $($transportRules.Name -join ', ')"
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
$auditResult.Status = if ($forwardingBlocked) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return the result object
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,47 @@
|
||||
function Test-BlockSharedMailboxSignIn_1.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked
|
||||
# Pass if all shared mailboxes have AccountEnabled set to False.
|
||||
# Fail if any shared mailbox has AccountEnabled set to True.
|
||||
|
||||
$MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox
|
||||
$sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId }
|
||||
$enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName }
|
||||
$allBlocked = $enabledMailboxes.Count -eq 0
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Control is explicitly not mapped
|
||||
$auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8"
|
||||
$auditResult.Rec = "1.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Control is not mapped, hence IG1 is false
|
||||
$auditResult.IG2 = $false # Control is not mapped, hence IG2 is false
|
||||
$auditResult.IG3 = $false # Control is not mapped, hence IG3 is false
|
||||
$auditResult.RecDescription = "Ensure sign-in to shared mailboxes is blocked"
|
||||
$auditResult.Result = $allBlocked
|
||||
$auditResult.Details = "Enabled Mailboxes: $($enabledMailboxes -join ', ')"
|
||||
$auditResult.FailureReason = if ($allBlocked) { "N/A" } else { "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" }
|
||||
$auditResult.Status = if ($allBlocked) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-CommonAttachmentFilter_2.1.2_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled
|
||||
# Pass if EnableFileFilter is set to True. Fail otherwise.
|
||||
|
||||
$attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter
|
||||
$result = $attachmentFilter.EnableFileFilter
|
||||
$details = "File Filter Enabled: $($attachmentFilter.EnableFileFilter)"
|
||||
$failureReason = if ($result) { "N/A" } else { "Common Attachment Types Filter is disabled" }
|
||||
$status = if ($result) { "Pass" } else { "Fail" }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = $status
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.2"
|
||||
$auditResult.RecDescription = "Ensure the Common Attachment Types Filter is enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.6"
|
||||
$auditResult.CISDescription = "Block Unnecessary File Types"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
function Test-CustomerLockbox_1.3.6_E5L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.6 (L2) Ensure the customer lockbox feature is enabled
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled
|
||||
$customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($customerLockboxEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "1.3.6"
|
||||
$auditResult.RecDescription = "Ensure the customer lockbox feature is enabled"
|
||||
$auditResult.CISControlVer = 'v8'
|
||||
$auditResult.CISControl = "0.0" # As per the snapshot provided, this is explicitly not mapped
|
||||
$auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $customerLockboxEnabled
|
||||
$auditResult.Details = "Customer Lockbox Enabled: $customerLockboxEnabled"
|
||||
$auditResult.FailureReason = if ($customerLockboxEnabled) { "N/A" } else { "Customer lockbox feature is not enabled." }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-DialInBypassLobby_8.5.4_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.4 (L1) Ensure users dialing in can't bypass the lobby
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby
|
||||
$PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure users dialing in can't bypass the lobby"
|
||||
$auditResult.Result = $PSTNBypassDisabled
|
||||
$auditResult.Details = "AllowPSTNUsersToBypassLobby is set to $($CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby)"
|
||||
$auditResult.FailureReason = if ($PSTNBypassDisabled) { "N/A" } else { "Users dialing in can bypass the lobby" }
|
||||
$auditResult.Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-DisallowInfectedFilesDownload_7.3.1_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
|
||||
$SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload
|
||||
$isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software"
|
||||
|
||||
$auditResult.Rec = "7.3.1"
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure Office 365 SharePoint infected files are disallowed for download"
|
||||
|
||||
$auditResult.Result = $isDisallowInfectedFileDownloadEnabled
|
||||
$auditResult.Details = "DisallowInfectedFileDownload: $($SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload)"
|
||||
$auditResult.FailureReason = if (-not $isDisallowInfectedFileDownloadEnabled) { "Downloading infected files is not disallowed." } else { "N/A" }
|
||||
$auditResult.Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-EnableDKIM_2.1.9_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains
|
||||
# Pass if Enabled is True for all domains. Fail if any domain has Enabled set to False.
|
||||
$dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled
|
||||
$dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false
|
||||
$dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($dkimResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.9"
|
||||
$auditResult.RecDescription = "Ensure that DKIM is enabled for all Exchange Online Domains"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.5"
|
||||
$auditResult.CISDescription = "Implement DMARC"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $dkimResult
|
||||
$auditResult.Details = if (-not $dkimResult) { "DKIM not enabled for: $($dkimFailedDomains -join ', ')" } else { "All domains have DKIM enabled" }
|
||||
$auditResult.FailureReason = if (-not $dkimResult) { "DKIM is not enabled for some domains" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-ExternalNoControl_8.5.7_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.7 (L1) Ensure external participants can't give or request control
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl
|
||||
$externalControlRestricted = -not $CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure external participants can't give or request control"
|
||||
$auditResult.Result = $externalControlRestricted
|
||||
$auditResult.Details = "AllowExternalParticipantGiveRequestControl is set to $($CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl)"
|
||||
$auditResult.FailureReason = if ($externalControlRestricted) { "N/A" } else { "External participants can give or request control" }
|
||||
$auditResult.Status = if ($externalControlRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
function Test-ExternalSharingCalendars_1.3.3_E3L2_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated)
|
||||
$sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' }
|
||||
|
||||
# Check if calendar sharing is disabled in all applicable policies
|
||||
$isExternalSharingDisabled = $true
|
||||
$sharingPolicyDetails = @()
|
||||
foreach ($policy in $sharingPolicies) {
|
||||
if ($policy.Enabled -eq $true) {
|
||||
$isExternalSharingDisabled = $false
|
||||
$sharingPolicyDetails += "$($policy.Name): Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "1.3.3"
|
||||
$auditResult.RecDescription = "Ensure 'External sharing' of calendars is not available"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
# The following IG values are placeholders. Replace with actual values when known.
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.CISControlVer = "v8"
|
||||
# Placeholder for CIS Control, to be replaced with the actual value when available
|
||||
$auditResult.CISControl = "4.8"
|
||||
$auditResult.CISDescription = "Uninstall or Disable Unnecessary Services on Enterprise Assets and Software"
|
||||
$auditResult.Result = $isExternalSharingDisabled
|
||||
$auditResult.Details = if ($isExternalSharingDisabled) { "Calendar sharing with external users is disabled." } else { "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" }
|
||||
$auditResult.FailureReason = if ($isExternalSharingDisabled) { "N/A" } else { "Calendar sharing with external users is enabled in one or more policies." }
|
||||
$auditResult.Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-GlobalAdminsCount_1.1.3_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.1.3 (L1) Ensure that between two and four global admins are designated
|
||||
# Pass if the count of global admins is between 2 and 4. Fail otherwise.
|
||||
|
||||
$globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'"
|
||||
$globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id
|
||||
$globalAdminCount = $globalAdmins.AdditionalProperties.Count
|
||||
$globalAdminUsernames = ($globalAdmins | ForEach-Object { $_.AdditionalProperties["displayName"] }) -join ', '
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "5.1"
|
||||
$auditResult.CISDescription = "Establish and Maintain an Inventory of Accounts"
|
||||
$auditResult.Rec = "1.1.3"
|
||||
$auditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.)
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true # Set based on the benchmark
|
||||
$auditResult.IG2 = $true # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure that between two and four global admins are designated"
|
||||
$auditResult.Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4
|
||||
$auditResult.Details = "Count: $globalAdminCount; Users: $globalAdminUsernames"
|
||||
$auditResult.FailureReason = if ($globalAdminCount -lt 2) { "Less than 2 global admins: $globalAdminUsernames" } elseif ($globalAdminCount -gt 4) { "More than 4 global admins: $globalAdminUsernames" } else { "N/A" }
|
||||
$auditResult.Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-GuestAccessExpiration_7.2.9_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
|
||||
$SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays
|
||||
$isGuestAccessExpirationConfiguredCorrectly = $SPOTenantGuestAccess.ExternalUserExpirationRequired -and $SPOTenantGuestAccess.ExternalUserExpireInDays -le 30
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.9"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure guest access to a site or OneDrive will expire automatically"
|
||||
|
||||
$auditResult.Result = $isGuestAccessExpirationConfiguredCorrectly
|
||||
$auditResult.Details = "ExternalUserExpirationRequired: $($SPOTenantGuestAccess.ExternalUserExpirationRequired); ExternalUserExpireInDays: $($SPOTenantGuestAccess.ExternalUserExpireInDays)"
|
||||
$auditResult.FailureReason = if (-not $isGuestAccessExpirationConfiguredCorrectly) { "Guest access expiration is not configured to automatically expire within 30 days or less." } else { "N/A" }
|
||||
$auditResult.Status = if ($isGuestAccessExpirationConfiguredCorrectly) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-IdentifyExternalEmail_6.2.3_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.2.3 (L1) Ensure email from external senders is identified
|
||||
# Requirement is to have external sender tagging enabled
|
||||
|
||||
$externalInOutlook = Get-ExternalInOutlook
|
||||
$externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.2.3"
|
||||
$auditResult.RecDescription = "Ensure email from external senders is identified"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $externalTaggingEnabled
|
||||
$auditResult.Details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)"
|
||||
$auditResult.FailureReason = if (-not $externalTaggingEnabled) { "External sender tagging is disabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
function Test-LinkSharingRestrictions_7.2.7_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
# Test behavior in prod
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
|
||||
$SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType
|
||||
$isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure link sharing is restricted in SharePoint and OneDrive"
|
||||
|
||||
$auditResult.Result = $isLinkSharingRestricted
|
||||
$auditResult.Details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)"
|
||||
$auditResult.FailureReason = if (-not $isLinkSharingRestricted) { "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-MailTipsEnabled_6.5.2_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.5.2 (L2) Ensure MailTips are enabled for end users
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold
|
||||
$allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25
|
||||
$externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled
|
||||
|
||||
# Since there is no direct CIS Control mapping, the control will be set as not applicable.
|
||||
$auditResult.CISControl = "N/A"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "6.5.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure MailTips are enabled for end users"
|
||||
|
||||
$auditResult.Result = $allTipsEnabled -and $externalRecipientsTipsEnabled
|
||||
$auditResult.Details = "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)"
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "One or more MailTips settings are not configured as required." } else { "N/A" }
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,111 @@
|
||||
function Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$e3SkuPartNumbers = @("ENTERPRISEPACK", "OFFICESUBSCRIPTION")
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.2"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E3 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)"
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license."
|
||||
|
||||
if ($hasOfficeE3) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
# Mark the user as processed
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E3 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
function Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.3"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E5 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
$e5SkuPartNumbers = @("SPE_E5", "ENTERPRISEPREMIUM", "OFFICEE5")
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license."
|
||||
if ($hasOfficeE5) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "User $($user.UserPrincipalName) passed the mailbox audit checks."
|
||||
}
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
else {
|
||||
# Adding verbose output to indicate the user does not have an E5 license
|
||||
Write-Verbose "User $($user.UserPrincipalName) does not have an Office E5 license."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
if ($allFailures.Count -eq 0) {
|
||||
Write-Verbose "All evaluated E5 users have correct mailbox audit settings."
|
||||
}
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E5 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
function Test-ManagedApprovedPublicGroups_1.2.1_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated)
|
||||
|
||||
$allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility
|
||||
|
||||
# Check if there are public groups and if they are organizationally managed/approved
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "1.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true # Based on the provided CIS Control image, IG3 is not applicable
|
||||
$auditResult.RecDescription = "Ensure that only organizationally managed/approved public groups exist"
|
||||
|
||||
if ($null -eq $allGroups -or $allGroups.Count -eq 0) {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Details = "No public groups found."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
$auditResult.Status = "Pass"
|
||||
}
|
||||
else {
|
||||
$groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" }
|
||||
$detailsString = $groupDetails -join ', '
|
||||
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Details = "Public groups found: $detailsString"
|
||||
$auditResult.FailureReason = "There are public groups present that are not organizationally managed/approved."
|
||||
$auditResult.Status = "Fail"
|
||||
}
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-MeetingChatNoAnonymous_8.5.5_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.5 (L2) Ensure meeting chat does not allow anonymous users
|
||||
# Name doesn't match profile level in benchmarks either.
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType
|
||||
$chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.5"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure meeting chat does not allow anonymous users"
|
||||
$auditResult.Result = $chatAnonDisabled
|
||||
$auditResult.Details = "MeetingChatEnabledType is set to $($CsTeamsMeetingPolicyChat.MeetingChatEnabledType)"
|
||||
$auditResult.FailureReason = if ($chatAnonDisabled) { "N/A" } else { "Meeting chat allows anonymous users" }
|
||||
$auditResult.Status = if ($chatAnonDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
function Test-ModernAuthExchangeOnline_6.5.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$CISAuditResult = [CISAuditResult]::new()
|
||||
# Initialization code
|
||||
}
|
||||
|
||||
process {
|
||||
try {
|
||||
# Ensuring the ExchangeOnlineManagement module is available
|
||||
|
||||
|
||||
# 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
|
||||
$CISAuditResult.CISControlVer = "v8"
|
||||
$CISAuditResult.CISControl = "3.10"
|
||||
$CISAuditResult.CISDescription = "Encrypt Sensitive Data in Transit"
|
||||
$CISAuditResult.IG1 = $false # As per CIS Control v8 mapping for IG1
|
||||
$CISAuditResult.IG2 = $true # As per CIS Control v8 mapping for IG2
|
||||
$CISAuditResult.IG3 = $true # As per CIS Control v8 mapping for IG3
|
||||
$CISAuditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.)
|
||||
$CISAuditResult.Profile = "L1"
|
||||
$CISAuditResult.Rec = "6.5.1"
|
||||
$CISAuditResult.RecDescription = "Ensure modern authentication for Exchange Online is enabled (Automated)"
|
||||
$CISAuditResult.Result = $orgConfig.OAuth2ClientProfileEnabled
|
||||
$CISAuditResult.Details = $orgConfig | Out-String
|
||||
$CISAuditResult.FailureReason = if (-not $orgConfig.OAuth2ClientProfileEnabled) { "Modern authentication is disabled" } else { "N/A" }
|
||||
$CISAuditResult.Status = if ($orgConfig.OAuth2ClientProfileEnabled) { "Pass" } else { "Fail" }
|
||||
|
||||
|
||||
}
|
||||
catch {
|
||||
Write-Error "An error occurred while testing modern authentication for Exchange Online: $_"
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $CISAuditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,39 @@
|
||||
function Test-ModernAuthSharePoint_7.2.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required
|
||||
$SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled
|
||||
$modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.10"
|
||||
$auditResult.CISDescription = "Encrypt Sensitive Data in Transit"
|
||||
$auditResult.Rec = "7.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Modern Authentication for SharePoint Applications"
|
||||
$auditResult.Result = $modernAuthForSPRequired
|
||||
$auditResult.Details = "LegacyAuthProtocolsEnabled: $($SPOTenant.LegacyAuthProtocolsEnabled)"
|
||||
$auditResult.FailureReason = if (-not $modernAuthForSPRequired) { "Legacy authentication protocols are enabled" } else { "N/A" }
|
||||
$auditResult.Status = if ($modernAuthForSPRequired) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-NoAnonymousMeetingJoin_8.5.1_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.1 (L2) Ensure anonymous users can't join a meeting
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global
|
||||
$allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure anonymous users can't join a meeting"
|
||||
$auditResult.Result = -not $allowAnonymousUsersToJoinMeeting
|
||||
$auditResult.Details = "AllowAnonymousUsersToJoinMeeting is set to $allowAnonymousUsersToJoinMeeting"
|
||||
$auditResult.FailureReason = if ($allowAnonymousUsersToJoinMeeting) { "Anonymous users are allowed to join meetings" } else { "N/A" }
|
||||
$auditResult.Status = if (-not $allowAnonymousUsersToJoinMeeting) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-NoAnonymousMeetingStart_8.5.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting
|
||||
$anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure anonymous users and dial-in callers can't start a meeting"
|
||||
$auditResult.Result = $anonymousStartDisabled
|
||||
$auditResult.Details = "AllowAnonymousUsersToStartMeeting is set to $($CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting)"
|
||||
$auditResult.FailureReason = if ($anonymousStartDisabled) { "N/A" } else { "Anonymous users and dial-in callers can start a meeting" }
|
||||
$auditResult.Status = if ($anonymousStartDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
function Test-NoWhitelistDomains_6.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains
|
||||
$whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $_.SenderDomainIs -ne $null }
|
||||
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "6.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure mail transport rules do not whitelist specific domains"
|
||||
|
||||
if ($whitelistedRules) {
|
||||
$ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') }
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Details = "Whitelisted Rules: $($ruleDetails -join '; ')"
|
||||
$auditResult.FailureReason = "There are transport rules whitelisting specific domains."
|
||||
$auditResult.Status = "Fail"
|
||||
} else {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Details = "No transport rules whitelisting specific domains found."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
$auditResult.Status = "Pass"
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
function Test-NotifyMalwareInternal_2.1.3_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all 'Custom' malware filter policies and check notification settings
|
||||
$malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' }
|
||||
$policiesToReport = @()
|
||||
|
||||
foreach ($policy in $malwareNotifications) {
|
||||
if ($policy.EnableInternalSenderAdminNotifications -ne $true) {
|
||||
$policiesToReport += "$($policy.Identity): Notifications Disabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Determine the result based on the presence of custom policies without notifications
|
||||
$result = $policiesToReport.Count -eq 0
|
||||
$details = if ($result) { "All custom malware policies have notifications enabled." } else { "Misconfigured Policies: $($policiesToReport -join ', ')" }
|
||||
$failureReason = if ($result) { "N/A" } else { "Some custom policies do not have notifications for internal users sending malware enabled." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.3"
|
||||
$auditResult.RecDescription = "Ensure notifications for internal users sending malware is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "17.5"
|
||||
$auditResult.CISDescription = "Assign Key Roles and Responsibilities"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-OneDriveContentRestrictions_7.2.4_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.4 (L2) Ensure OneDrive content sharing is restricted
|
||||
$SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability
|
||||
$isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled'
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure OneDrive content sharing is restricted"
|
||||
|
||||
$auditResult.Result = $isOneDriveSharingRestricted
|
||||
$auditResult.Details = "OneDriveSharingCapability: $($SPOTenant.OneDriveSharingCapability)"
|
||||
$auditResult.FailureReason = if (-not $isOneDriveSharingRestricted) { "OneDrive content sharing is not restricted to 'Disabled'. Current setting: $($SPOTenant.OneDriveSharingCapability)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isOneDriveSharingRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-OneDriveSyncRestrictions_7.3.2_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.3.2 (L2) Ensure OneDrive sync is restricted for unmanaged devices
|
||||
$SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList
|
||||
$isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.3.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure OneDrive sync is restricted for unmanaged devices"
|
||||
|
||||
$auditResult.Result = $isSyncRestricted
|
||||
$auditResult.Details = "TenantRestrictionEnabled: $($SPOTenantSyncClientRestriction.TenantRestrictionEnabled); AllowedDomainList: $($SPOTenantSyncClientRestriction.AllowedDomainList -join ', ')"
|
||||
$auditResult.FailureReason = if (-not $isSyncRestricted) { "OneDrive sync is not restricted to managed devices. TenantRestrictionEnabled should be True and AllowedDomainList should contain trusted domains GUIDs." } else { "N/A" }
|
||||
$auditResult.Status = if ($isSyncRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-OrgOnlyBypassLobby_8.5.3_E3L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.3 (L1) Ensure only people in my org can bypass the lobby
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyLobby = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AutoAdmittedUsers
|
||||
$lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "6.8"
|
||||
$auditResult.CISDescription = "Define and Maintain Role-Based Access Control"
|
||||
$auditResult.Rec = "8.5.3"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $true # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure only people in my org can bypass the lobby"
|
||||
$auditResult.Result = $lobbyBypassRestricted
|
||||
$auditResult.Details = "AutoAdmittedUsers is set to $($CsTeamsMeetingPolicyLobby.AutoAdmittedUsers)"
|
||||
$auditResult.FailureReason = if ($lobbyBypassRestricted) { "N/A" } else { "External participants can bypass the lobby" }
|
||||
$auditResult.Status = if ($lobbyBypassRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-OrganizersPresent_8.5.6_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.6 (L2) Ensure only organizers and co-organizers can present
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode
|
||||
$presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.6"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure only organizers and co-organizers can present"
|
||||
$auditResult.Result = $presenterRoleRestricted
|
||||
$auditResult.Details = "DesignatedPresenterRoleMode is set to $($CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode)"
|
||||
$auditResult.FailureReason = if ($presenterRoleRestricted) { "N/A" } else { "Others besides organizers and co-organizers can present" }
|
||||
$auditResult.Status = if ($presenterRoleRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-PasswordHashSync_5.1.8.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments
|
||||
# Pass if OnPremisesSyncEnabled is True. Fail otherwise.
|
||||
$passwordHashSync = Get-MgOrganization | Select-Object OnPremisesSyncEnabled
|
||||
$hashSyncResult = $passwordHashSync.OnPremisesSyncEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($hashSyncResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "5.1.8.1"
|
||||
$auditResult.RecDescription = "Ensure password hash sync is enabled for hybrid deployments"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "6.7"
|
||||
$auditResult.CISDescription = "Centralize Access Control"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $hashSyncResult
|
||||
$auditResult.Details = "OnPremisesSyncEnabled: $($passwordHashSync.OnPremisesSyncEnabled)"
|
||||
$auditResult.FailureReason = if (-not $hashSyncResult) { "Password hash sync for hybrid deployments is not enabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-PasswordNeverExpirePolicy_1.3.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$DomainName # DomainName parameter is now mandatory
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire'
|
||||
# Pass if PasswordValidityPeriodInDays is 0.
|
||||
# Fail otherwise.
|
||||
|
||||
$passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object PasswordValidityPeriodInDays
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "1.3.1"
|
||||
$auditResult.RecDescription = "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire'"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "5.2"
|
||||
$auditResult.CISDescription = "Use Unique Passwords"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true # All are true
|
||||
$auditResult.Result = $passwordPolicy.PasswordValidityPeriodInDays -eq 0
|
||||
$auditResult.Details = "Validity Period: $($passwordPolicy.PasswordValidityPeriodInDays) days"
|
||||
$auditResult.FailureReason = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "N/A" } else { "Password expiration is not set to never expire" }
|
||||
$auditResult.Status = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-ReauthWithCode_7.2.10_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.10 (L1) Ensure reauthentication with verification code is restricted
|
||||
$SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays
|
||||
$isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.10"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure reauthentication with verification code is restricted"
|
||||
|
||||
$auditResult.Result = $isReauthenticationRestricted
|
||||
$auditResult.Details = "EmailAttestationRequired: $($SPOTenantReauthentication.EmailAttestationRequired); EmailAttestationReAuthDays: $($SPOTenantReauthentication.EmailAttestationReAuthDays)"
|
||||
$auditResult.FailureReason = if (-not $isReauthenticationRestricted) { "Reauthentication with verification code does not require reauthentication within 15 days or less." } else { "N/A" }
|
||||
$auditResult.Status = if ($isReauthenticationRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-ReportSecurityInTeams_8.6.1_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.6.1 (L1) Ensure users can report security concerns in Teams
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
# Connect to Exchange Online PowerShell using Connect-ExchangeOnline
|
||||
|
||||
$CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting
|
||||
$ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled
|
||||
|
||||
$securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and
|
||||
$ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportPhishToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.6.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure users can report security concerns in Teams"
|
||||
$auditResult.Result = $securityReportEnabled
|
||||
$auditResult.Details = "AllowSecurityEndUserReporting: $($CsTeamsMessagingPolicy.AllowSecurityEndUserReporting); " +
|
||||
"ReportJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportJunkToCustomizedAddress); " +
|
||||
"ReportNotJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress); " +
|
||||
"ReportPhishToCustomizedAddress: $($ReportSubmissionPolicy.ReportPhishToCustomizedAddress); " +
|
||||
"ReportChatMessageToCustomizedAddressEnabled: $($ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled)"
|
||||
$auditResult.FailureReason = if (-not $securityReportEnabled) { "Users cannot report security concerns in Teams due to one or more incorrect settings" } else { "N/A" }
|
||||
$auditResult.Status = if ($securityReportEnabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
function Test-RestrictCustomScripts_7.3.4_E3L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# .TODO Test behavior in Prod
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# CIS 2.7 Ensure custom script execution is restricted on site collections
|
||||
# Pass if DenyAddAndCustomizePages is set to true. Fail otherwise.
|
||||
|
||||
# Connect to SharePoint Online using Connect-SPOService
|
||||
|
||||
$SPOSitesCustomScript = Get-SPOSite | Select-Object Title, Url, DenyAddAndCustomizePages
|
||||
$customScriptDisabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -eq 'Enabled' }
|
||||
$customScriptEnabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -ne 'Enabled' }
|
||||
$customScriptDisabledResult = $customScriptEnabledSites.Count -eq 0
|
||||
|
||||
# Correctly gathering details for sites with custom scripts enabled
|
||||
$customScriptEnabledDetails = $customScriptEnabledSites | ForEach-Object { "$($_.Title) ($($_.Url)): Custom Script - $($_.DenyAddAndCustomizePages)" }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "2.7"
|
||||
$auditResult.CISDescription = "Allowlist Authorized Scripts"
|
||||
$auditResult.Rec = "7.3.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the benchmark
|
||||
$auditResult.IG2 = $false # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure custom script execution is restricted on site collections"
|
||||
$auditResult.Result = $customScriptDisabledResult
|
||||
$auditResult.Details = if (-not $customScriptDisabledResult) { $customScriptEnabledDetails -join "; " } else { "All site collections have custom script execution restricted" }
|
||||
$auditResult.FailureReason = if (-not $customScriptDisabledResult) { "The following site collections have custom script execution enabled: " + ($customScriptEnabledDetails -join "; ") } else { "N/A" }
|
||||
$auditResult.Status = if ($customScriptDisabledResult) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-RestrictExternalSharing_7.2.3_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.3 (L1) Ensure external content sharing is restricted
|
||||
$SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability
|
||||
$isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled')
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.3"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure external content sharing is restricted"
|
||||
|
||||
$auditResult.Result = $isRestricted
|
||||
$auditResult.Details = "SharingCapability: $($SPOTenantSharingCapability.SharingCapability)"
|
||||
$auditResult.FailureReason = if (-not $isRestricted) { "External content sharing is not adequately restricted. Current setting: $($SPOTenantSharingCapability.SharingCapability)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
function Test-RestrictOutlookAddins_6.3.1_E3L2_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters could include credentials or other necessary data
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$customPolicyFailures = @()
|
||||
$defaultPolicyFailureDetails = @()
|
||||
$relevantRoles = @('My Custom Apps', 'My Marketplace Apps', 'My ReadWriteMailbox Apps')
|
||||
}
|
||||
|
||||
process {
|
||||
# Main functionality
|
||||
# 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed
|
||||
|
||||
# Check all mailboxes for custom policies with unallowed add-ins
|
||||
$roleAssignmentPolicies = Get-EXOMailbox | Select-Object -Unique RoleAssignmentPolicy
|
||||
|
||||
if ($roleAssignmentPolicies.RoleAssignmentPolicy) {
|
||||
foreach ($policy in $roleAssignmentPolicies) {
|
||||
if ($policy.RoleAssignmentPolicy) {
|
||||
$rolePolicyDetails = Get-RoleAssignmentPolicy -Identity $policy.RoleAssignmentPolicy
|
||||
$foundRoles = $rolePolicyDetails.AssignedRoles | Where-Object { $_ -in $relevantRoles }
|
||||
if ($foundRoles) {
|
||||
$customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check Default Role Assignment Policy
|
||||
$defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy"
|
||||
$defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles }
|
||||
if ($defaultPolicyRoles) {
|
||||
$defaultPolicyFailureDetails = $defaultPolicyRoles
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Prepare result object
|
||||
$auditResult.Rec = "6.3.1"
|
||||
$auditResult.CISControl = "9.4"
|
||||
$auditResult.CISDescription = "Restrict Unnecessary or Unauthorized Browser and Email Client Extensions"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure users installing Outlook add-ins is not allowed"
|
||||
|
||||
$detailsString = ""
|
||||
if ($customPolicyFailures) {
|
||||
$detailsString += "Custom Policy Failures: `n"
|
||||
foreach ($failure in $customPolicyFailures) {
|
||||
$detailsString += "`t$failure`n"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$detailsString += "Custom Policy Failures: None`n"
|
||||
}
|
||||
|
||||
$detailsString += "Default Role Assignment Policy: "
|
||||
if ($defaultPolicyFailureDetails) {
|
||||
$detailsString += "$($defaultPolicyFailureDetails -join ', ')"
|
||||
}
|
||||
else {
|
||||
$detailsString += "Compliant"
|
||||
}
|
||||
|
||||
if ($customPolicyFailures -or $defaultPolicyFailureDetails) {
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Status = "Fail"
|
||||
$auditResult.Details = $detailsString
|
||||
$auditResult.FailureReason = "Unauthorized Outlook add-ins found in custom or default policies."
|
||||
}
|
||||
else {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Status = "Pass"
|
||||
$auditResult.Details = "No unauthorized Outlook add-ins found in custom or default policies."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
}
|
||||
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
function Test-RestrictStorageProvidersOutlook_6.5.3_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web
|
||||
$owaPolicies = Get-OwaMailboxPolicy
|
||||
$allPoliciesRestricted = $owaPolicies | ForEach-Object { $_.AdditionalStorageProvidersAvailable } | ForEach-Object { -not $_ }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "6.5.3"
|
||||
$auditResult.ELevel = "E3" # Based on your environment
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure additional storage providers are restricted in Outlook on the web"
|
||||
$auditResult.Result = $allPoliciesRestricted
|
||||
$auditResult.Details = if($allPoliciesRestricted) {
|
||||
"All OwaMailbox policies restrict AdditionalStorageProvidersAvailable"
|
||||
} else {
|
||||
$nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } | Select-Object -ExpandProperty Name
|
||||
"Non-compliant OwaMailbox policies: $($nonCompliantPolicies -join ', ')"
|
||||
}
|
||||
$auditResult.FailureReason = if(-not $allPoliciesRestricted) { "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." } else { "N/A" }
|
||||
$auditResult.Status = if($allPoliciesRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Additional helper functions (if any)
|
||||
|
@@ -0,0 +1,43 @@
|
||||
function Test-RestrictTenantCreation_5.1.2.3_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'
|
||||
# Pass if AllowedToCreateTenants is False. Fail otherwise.
|
||||
$tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants
|
||||
$tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($tenantCreationResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "5.1.2.3"
|
||||
$auditResult.RecDescription = "Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $tenantCreationResult
|
||||
$auditResult.Details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)"
|
||||
$auditResult.FailureReason = if (-not $tenantCreationResult) { "Non-admin users can create tenants" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
function Test-SafeAttachmentsPolicy_2.1.4_E5L2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all Safe Attachment policies where Enable is set to True
|
||||
$safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true }
|
||||
|
||||
# If there are any enabled policies, the result is Pass. If not, it's Fail.
|
||||
$result = $safeAttachmentPolicies -ne $null -and $safeAttachmentPolicies.Count -gt 0
|
||||
$details = if ($result) {
|
||||
"Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')"
|
||||
} else {
|
||||
"No Safe Attachments Policies are enabled."
|
||||
}
|
||||
$failureReason = if ($result) { "N/A" } else { "Safe Attachments policy is not enabled." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.4"
|
||||
$auditResult.RecDescription = "Ensure Safe Attachments policy is enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-SafeAttachmentsTeams_2.1.5_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve the ATP policies for Office 365 and check Safe Attachments settings
|
||||
$atpPolicies = Get-AtpPolicyForO365
|
||||
|
||||
# Check if the required ATP policies are enabled
|
||||
$atpPolicyResult = $atpPolicies | Where-Object {
|
||||
$_.EnableATPForSPOTeamsODB -eq $true -and
|
||||
$_.EnableSafeDocs -eq $true -and
|
||||
$_.AllowSafeDocsOpen -eq $false
|
||||
}
|
||||
|
||||
# Determine the result based on the ATP policy settings
|
||||
$result = $null -ne $atpPolicyResult
|
||||
$details = if ($result) {
|
||||
"ATP for SharePoint, OneDrive, and Teams is enabled with correct settings."
|
||||
} else {
|
||||
"ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings."
|
||||
}
|
||||
$failureReason = if ($result) { "N/A" } else { "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.5"
|
||||
$auditResult.RecDescription = "Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7, 10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections, Deploy and Maintain Anti-Malware Software"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
function Test-SafeLinksOfficeApps_2.1.1_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all Safe Links policies
|
||||
$policies = Get-SafeLinksPolicy
|
||||
|
||||
# Initialize the details collection
|
||||
$misconfiguredDetails = @()
|
||||
|
||||
foreach ($policy in $policies) {
|
||||
# Get the detailed configuration of each policy
|
||||
$policyDetails = Get-SafeLinksPolicy -Identity $policy.Name
|
||||
|
||||
# Check each required property and record failures
|
||||
$failures = @()
|
||||
if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" }
|
||||
if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" }
|
||||
if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" }
|
||||
if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" }
|
||||
if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" }
|
||||
if ($policyDetails.ScanUrls -ne $true) { $failures += "ScanUrls: False" }
|
||||
if ($policyDetails.EnableForInternalSenders -ne $true) { $failures += "EnableForInternalSenders: False" }
|
||||
if ($policyDetails.DeliverMessageAfterScan -ne $true) { $failures += "DeliverMessageAfterScan: False" }
|
||||
if ($policyDetails.DisableUrlRewrite -ne $false) { $failures += "DisableUrlRewrite: True" }
|
||||
|
||||
# Only add details for policies that have misconfigurations
|
||||
if ($failures.Count -gt 0) {
|
||||
$misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
# Prepare the final result
|
||||
$result = $misconfiguredDetails.Count -eq 0
|
||||
$details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' }
|
||||
|
||||
# Create the audit result object
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.1"
|
||||
$auditResult.RecDescription = "Ensure Safe Links for Office Applications is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
function Test-SharePointAADB2B_7.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
|
||||
$SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled"
|
||||
|
||||
$auditResult.Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration
|
||||
$auditResult.Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)"
|
||||
$auditResult.FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled" } else { "N/A" }
|
||||
$auditResult.Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-SharePointExternalSharingDomains_7.2.6_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists
|
||||
$SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList
|
||||
$isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList'
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.6"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure SharePoint external sharing is managed through domain whitelist/blacklists"
|
||||
|
||||
$auditResult.Result = $isDomainRestrictionConfigured
|
||||
$auditResult.Details = "SharingDomainRestrictionMode: $($SPOTenant.SharingDomainRestrictionMode); SharingAllowedDomainList: $($SPOTenant.SharingAllowedDomainList)"
|
||||
$auditResult.FailureReason = if (-not $isDomainRestrictionConfigured) { "Domain restrictions for SharePoint external sharing are not configured to 'AllowList'. Current setting: $($SPOTenant.SharingDomainRestrictionMode)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isDomainRestrictionConfigured) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-SharePointGuestsItemSharing_7.2.5_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own
|
||||
$SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing
|
||||
$isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.5"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure that SharePoint guest users cannot share items they don't own"
|
||||
|
||||
$auditResult.Result = $isGuestResharingPrevented
|
||||
$auditResult.Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented"
|
||||
$auditResult.FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own." } else { "N/A" }
|
||||
$auditResult.Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-SpamPolicyAdminNotify_2.1.6_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Get the default hosted outbound spam filter policy
|
||||
$hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true }
|
||||
|
||||
# Check if both settings are enabled
|
||||
$bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail
|
||||
$notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam
|
||||
$areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled
|
||||
|
||||
# Prepare failure details if any setting is not enabled
|
||||
$failureDetails = @()
|
||||
if (-not $bccSuspiciousOutboundMailEnabled) {
|
||||
$failureDetails += "BccSuspiciousOutboundMail is not enabled."
|
||||
}
|
||||
if (-not $notifyOutboundSpamEnabled) {
|
||||
$failureDetails += "NotifyOutboundSpam is not enabled."
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.6"
|
||||
$auditResult.RecDescription = "Ensure Exchange Online Spam Policies are set to notify administrators"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "17.5"
|
||||
$auditResult.CISDescription = "Assign Key Roles and Responsibilities"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $areSettingsEnabled
|
||||
$auditResult.Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' }
|
||||
$auditResult.FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,52 @@
|
||||
function Test-TeamsExternalAccess_8.2.1_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$externalAccessConfig = Get-CsTenantFederationConfiguration
|
||||
|
||||
$allowedDomainsLimited = $false
|
||||
if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) {
|
||||
$allowedDomainsLimited = $true
|
||||
}
|
||||
|
||||
# Check if the configurations are as recommended
|
||||
$isCompliant = -not $externalAccessConfig.AllowTeamsConsumer -and -not $externalAccessConfig.AllowPublicUsers -and (-not $externalAccessConfig.AllowFederatedUsers -or $allowedDomainsLimited)
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure 'external access' is restricted in the Teams admin center"
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = "AllowTeamsConsumer: $($externalAccessConfig.AllowTeamsConsumer); AllowPublicUsers: $($externalAccessConfig.AllowPublicUsers); AllowFederatedUsers: $($externalAccessConfig.AllowFederatedUsers); AllowedDomains limited: $allowedDomainsLimited"
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "One or more external access configurations are not compliant." } else { "N/A" }
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-TeamsExternalFileSharing_8.1.1_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.1.1 (L2) Ensure external file sharing in Teams is enabled for only approved cloud storage services
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
# Assuming that 'approvedProviders' is a list of approved cloud storage service names
|
||||
# This list must be defined according to your organization's approved cloud storage services
|
||||
$approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte")
|
||||
$clientConfig = Get-CsTeamsClientConfiguration
|
||||
|
||||
$isCompliant = $true
|
||||
$nonCompliantProviders = @()
|
||||
|
||||
foreach ($provider in $approvedProviders) {
|
||||
if (-not $clientConfig.$provider) {
|
||||
$isCompliant = $false
|
||||
$nonCompliantProviders += $provider
|
||||
}
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "8.1.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true # Set based on the benchmark
|
||||
$auditResult.IG2 = $true # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure external file sharing in Teams is enabled for only approved cloud storage services"
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = if (-not $isCompliant) { "Non-approved providers enabled: $($nonCompliantProviders -join ', ')" } else { "All cloud storage services are approved providers" }
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "The following non-approved providers are enabled: $($nonCompliantProviders -join ', ')" } else { "N/A" }
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
60
helpers/psDoc-master/ADAuditTasks.md
Normal file
60
helpers/psDoc-master/ADAuditTasks.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
Module Name: ADAuditTasks
|
||||
Module Guid: '7ddb359a-e07f-4be0-b63a-a81f44c61fff'
|
||||
Download Help Link: https://audittaskshelpfiles.blob.core.windows.net/helpfiles/
|
||||
Help Version: 1.0.0.5
|
||||
Locale: en-US
|
||||
---
|
||||
|
||||
# ADAuditTasks Module
|
||||
## Description
|
||||
{{ Fill in the Description }}
|
||||
|
||||
## ADAuditTasks Cmdlets
|
||||
### [Convert-NmapXMLToCSV](Convert-NmapXMLToCSV)
|
||||
Converts an Nmap XML scan output file to a CSV file.
|
||||
|
||||
### [Get-ADActiveUserAudit](Get-ADActiveUserAudit)
|
||||
Gets active but stale AD User accounts that haven't logged in within the last 90 days by default.
|
||||
|
||||
### [Get-ADHostAudit](Get-ADHostAudit)
|
||||
Active Directory Server and Workstation Audit with Report export option (Can also be piped to CSV if Report isn't specified).
|
||||
|
||||
### [Get-ADUserLogonAudit](Get-ADUserLogonAudit)
|
||||
Retrieves the most recent LastLogon timestamp for a specified Active Directory user
|
||||
account from all domain controllers and outputs it as a DateTime object.
|
||||
|
||||
### [Get-ADUserPrivilegeAudit](Get-ADUserPrivilegeAudit)
|
||||
Produces three object outputs: PrivilegedGroups, AdExtendedRights, and possible service accounts.
|
||||
|
||||
### [Get-ADUserWildCardAudit](Get-ADUserWildCardAudit)
|
||||
Takes a search string to find commonly named accounts.
|
||||
|
||||
### [Get-HostTag](Get-HostTag)
|
||||
Creates a host name or tag based on predetermined criteria for as many as 999 hosts at a time.
|
||||
|
||||
### [Get-NetworkAudit](Get-NetworkAudit)
|
||||
Discovers the local network and runs port scans on all hosts found for specific or default sets of ports, displaying MAC ID vendor info.
|
||||
|
||||
### [Get-QuickPing](Get-QuickPing)
|
||||
Performs a quick ping on a range of IP addresses and returns an array of IP addresses
|
||||
that responded to the ping and an array of IP addresses that failed to respond.
|
||||
|
||||
### [Get-WebCertAudit](Get-WebCertAudit)
|
||||
Retrieves the certificate information for a web server.
|
||||
|
||||
### [Join-CSVFile](Join-CSVFile)
|
||||
Joins multiple CSV files with the same headers into a single CSV file.
|
||||
|
||||
### [Merge-ADAuditZip](Merge-ADAuditZip)
|
||||
Combines multiple audit report files into a single compressed ZIP file.
|
||||
|
||||
### [Merge-NmapToADHostAudit](Merge-NmapToADHostAudit)
|
||||
Merges Nmap network audit data with Active Directory host audit data.
|
||||
|
||||
### [Send-AuditEmail](Send-AuditEmail)
|
||||
This is a wrapper function for Send-MailKitMessage and takes string arrays as input.
|
||||
|
||||
### [Submit-FTPUpload](Submit-FTPUpload)
|
||||
Uploads a file to an FTP server using the WinSCP module.
|
||||
|
21
helpers/psDoc-master/LICENSE
Normal file
21
helpers/psDoc-master/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Microsoft Public License (MS-PL)
|
||||
This license governs use of the accompanying software. If you use the software, you
|
||||
accept this license. If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
20
helpers/psDoc-master/readme.md
Normal file
20
helpers/psDoc-master/readme.md
Normal file
@@ -0,0 +1,20 @@
|
||||
psDoc is a Powershell Help Document generator.
|
||||
|
||||
----
|
||||
### Using psDoc ###
|
||||
|
||||
To generate documentation off of your module, simply import your module
|
||||
|
||||
```
|
||||
Import-Module MySpecialModule
|
||||
```
|
||||
|
||||
And generate the documentation
|
||||
|
||||
```
|
||||
.\psDoc.ps1 -moduleName MySpecialModule
|
||||
```
|
||||
|
||||
### License ###
|
||||
|
||||
[Microsoft Public License (Ms-PL)](https://opensource.org/licenses/MS-PL)
|
194
helpers/psDoc-master/src/out-confluence-markup-template.ps1
Normal file
194
helpers/psDoc-master/src/out-confluence-markup-template.ps1
Normal file
@@ -0,0 +1,194 @@
|
||||
function TrimAllLines([string] $str) {
|
||||
$lines = $str -split "`n"
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] = $lines[$i].Trim()
|
||||
}
|
||||
|
||||
# Trim EOL.
|
||||
($lines | Out-String).Trim()
|
||||
}
|
||||
|
||||
function FixMarkdownString([string] $in = '', [bool] $includeBreaks = $false, [bool]$BlankStringToSpace = $False) {
|
||||
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
if($in -eq "" -and $BlankStringToSpace ) { return " " }
|
||||
|
||||
$replacements = @{
|
||||
'\' = '\\'
|
||||
'`' = '\`'
|
||||
'*' = '\*'
|
||||
'_' = '\_'
|
||||
'{' = '\{'
|
||||
'}' = '\}'
|
||||
'[' = '\['
|
||||
']' = '\]'
|
||||
'(' = '\('
|
||||
')' = '\)'
|
||||
'#' = '\#'
|
||||
'+' = '\+'
|
||||
'!' = '\!'
|
||||
}
|
||||
|
||||
$rtn = $in.Trim()
|
||||
foreach ($key in $replacements.Keys) {
|
||||
$rtn = $rtn.Replace($key, $replacements[$key])
|
||||
}
|
||||
|
||||
$rtn = TrimAllLines $rtn
|
||||
|
||||
if ($includeBreaks) {
|
||||
$crlf = [Environment]::NewLine
|
||||
$rtn = $rtn.Replace($crlf, " $crlf")
|
||||
}
|
||||
$rtn
|
||||
}
|
||||
|
||||
function FixMarkdownCodeString([string] $in) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
TrimAllLines $in
|
||||
}
|
||||
|
||||
function IncludeTableOfContents {
|
||||
|
||||
return "{toc:printable=true|style=square|maxLevel=2|indent=5px|minLevel=2|class=bigpink|exclude=[1//2]|type=list|outline=true|include=.*}"
|
||||
}
|
||||
|
||||
|
||||
@"
|
||||
h1. $moduleName
|
||||
|
||||
$(IncludeTableOfContents)
|
||||
|
||||
\\
|
||||
\\
|
||||
"@
|
||||
|
||||
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
h2. $(FixMarkdownString($_.Name))
|
||||
"@
|
||||
$synopsis = $_.synopsis.Trim()
|
||||
$syntax = $_.syntax | out-string
|
||||
if(-not ($synopsis -ilike "$($_.Name.Trim())*")){
|
||||
$tmp = $synopsis
|
||||
$synopsis = $syntax
|
||||
$syntax = $tmp
|
||||
@"
|
||||
h3. Synopsis
|
||||
$(FixMarkdownString($syntax))
|
||||
"@
|
||||
}
|
||||
|
||||
|
||||
@"
|
||||
h3. Description
|
||||
$(FixMarkdownString $(($_.Description | out-string).Trim()) $true)
|
||||
"@
|
||||
|
||||
@"
|
||||
h3. Syntax
|
||||
{code:theme=Confluence|linenumbers=false|language=Powershell|firstline=0001|collapse=false}
|
||||
$(TrimAllLines $synopsis)
|
||||
{code}
|
||||
"@
|
||||
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
h3. $($_.Name) Aliases
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
- $($_.Name)
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if($_.parameters){
|
||||
@"
|
||||
h3. Parameters
|
||||
|
||||
||Name||Alias||Description||Required?||Pipeline Input||Default Value||
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
|$(FixMarkdownString $_.Name $false $true)|$(FixMarkdownString $_.Aliases $false $true)|$(FixMarkdownString $($_.Description | out-string).Trim() $true $true)|$(FixMarkdownString $_.Required $false $true)|$(FixMarkdownString $_.PipelineInput $false $true)|$(FixMarkdownString $_.DefaultValue $false $true)|
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
|
||||
"@
|
||||
}
|
||||
$inputTypes = $(FixMarkdownString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
h3. Inputs
|
||||
- $inputTypes
|
||||
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixMarkdownString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
h3. Outputs
|
||||
- $returnValues
|
||||
|
||||
"@
|
||||
}
|
||||
$notes = $(FixMarkdownString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Note
|
||||
$notes
|
||||
|
||||
"@
|
||||
}
|
||||
if(($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Examples
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
{code:title=$(FixMarkdownString($_.title.Trim(('-',' '))))|theme=Confluence|linenumbers=true|language=Powershell|firstline=0001|collapse=false}
|
||||
$(FixMarkdownCodeString($_.code | out-string ))
|
||||
{code}
|
||||
|
||||
$(FixMarkdownString($_.remarks | out-string ) $true)
|
||||
"@
|
||||
}
|
||||
}
|
||||
if(($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Links
|
||||
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
- [$($_.name)|$($_.link)]
|
||||
"@
|
||||
}
|
||||
}
|
||||
|
||||
@"
|
||||
|
||||
\\
|
||||
\\
|
||||
\\
|
||||
----
|
||||
\\
|
||||
\\
|
||||
\\
|
||||
|
||||
"@
|
||||
|
||||
}
|
346
helpers/psDoc-master/src/out-html-template.ps1
Normal file
346
helpers/psDoc-master/src/out-html-template.ps1
Normal file
@@ -0,0 +1,346 @@
|
||||
@"
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
<auto-generated>
|
||||
<synopsis>
|
||||
This code was generated by a tool. on: $(Get-Date)
|
||||
</synopsis>
|
||||
<description>
|
||||
If you'd like to regenerate the documentation, please open up powershell and run
|
||||
|
||||
> .\psDoc.ps1 -moduleName NameOfYourModule
|
||||
|
||||
If the documentation is incomplete, or eronious,
|
||||
please edit the comments at the top of the module method within it's respecive .ps1 file.
|
||||
</description>
|
||||
</auto-generated>
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>$moduleName Documentation</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCoreDefault.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" charset="utf-8">
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
.syntaxhighlighter {
|
||||
overflow-y: hidden !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
pre {
|
||||
min-height: 30px;
|
||||
}
|
||||
.navbar-nav {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.form-group {
|
||||
padding-top: 12px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.sidebar-nav .navbar-header {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar li a {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-nav .navbar .navbar-collapse {
|
||||
padding: 0;
|
||||
max-height: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul:not {
|
||||
display: block;
|
||||
}
|
||||
.sidebar-nav .navbar li {
|
||||
float: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div><h1>$moduleName</h1></div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="col-lg-3 col-md-4 col-sm-5 col-xs-12">
|
||||
<div class="sidebar-nav">
|
||||
<div class="navbar navbar-default" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-navbar-collapse">
|
||||
<span class="sr-only">Toggle</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<span class="visible-xs navbar-brand">click menu to open</span>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse sidebar-navbar-collapse">
|
||||
|
||||
<div class="form-group">
|
||||
<input class="form-control" id="searchinput" type="search" placeholder="Filter..." />
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav list-group" id="searchList">
|
||||
"@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Navigation'
|
||||
$progress++
|
||||
" <li class=`"nav-menu list-group-item`"><a href=`"#$($_.Name)`">$($_.Name)</a></li>"
|
||||
}
|
||||
@'
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8 col-sm-7 col-xs-12">
|
||||
'@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
<div id=`"$(FixString($_.Name))`" class="toggle_container">
|
||||
<div class="page-header">
|
||||
<h2> $(FixString($_.Name)) </h2>
|
||||
"@
|
||||
$syn = FixString($_.synopsis)
|
||||
if(!($syn).StartsWith($(FixString($_.Name)))){
|
||||
@"
|
||||
<p>$syn</p>
|
||||
<p>$(FixString(($_.Description | out-string).Trim()) $true)</p>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
"@
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'>
|
||||
<h3 class='panel-title'> $($_.Name) Aliases </h3>
|
||||
</div>
|
||||
<div class='panel-body'>
|
||||
<ul>
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
<li>$($_.Name)</li>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if (!($_.syntax | Out-String ).Trim().Contains('syntaxItem')) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Syntax </h3>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class='panel-body'>
|
||||
<pre class="brush: ps">$(FixString($_.syntax | out-string))</pre>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if($_.parameters){
|
||||
@"
|
||||
<div>
|
||||
<h3> Parameters </h3>
|
||||
<table class="table table-striped table-bordered table-condensed visible-on">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="visible-lg visible-md">Alias</th>
|
||||
<th>Description</th>
|
||||
<th class="visible-lg visible-md">Required?</th>
|
||||
<th class="visible-lg">Pipeline Input</th>
|
||||
<th class="visible-lg">Default Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
<tr>
|
||||
<td><nobr>-$(FixString($_.Name))</nobr></td>
|
||||
<td class="visible-lg visible-md">$(FixString($_.Aliases))</td>
|
||||
<td>$(FixString(($_.Description | out-string).Trim()) $true)</td>
|
||||
<td class="visible-lg visible-md">$(FixString($_.Required))</td>
|
||||
<td class="visible-lg">$(FixString($_.PipelineInput))</td>
|
||||
<td class="visible-lg">$(FixString($_.DefaultValue))</td>
|
||||
</tr>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$inputTypes = $(FixString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Inputs </h3>
|
||||
<p>The input type is the type of the objects that you can pipe to the cmdlet.</p>
|
||||
<ul><li>$inputTypes</li></ul>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Outputs </h3>
|
||||
<p>The output type is the type of the objects that the cmdlet emits.</p>
|
||||
<ul><li>$returnValues</li></ul>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$notes = $(FixString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'>
|
||||
<h3 class='panel-title'> Note </h3>
|
||||
</div>
|
||||
<div class='panel-body'>$notes</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if(($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Examples </h3>
|
||||
</div>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-body'>
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
<strong>$(FixString($_.title.Trim(('-',' '))))</strong>
|
||||
<pre class="brush: ps">$(FixString($_.code | out-string ).Trim())</pre>
|
||||
<div>$(FixString($_.remarks | out-string ).Trim())</div>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
</div>
|
||||
<p class='pull-right'><a onclick='document.body.scrollTop = document.documentElement.scrollTop = 0;' style='cursor: pointer;'>Top of page</a>
|
||||
"@
|
||||
}
|
||||
if(($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Links </h3>
|
||||
<div>
|
||||
<ul>
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
<li class='$($_.cssClass)'><a href='$($_.link)' target='$($_.target)'>$($_.name)</a></li>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
@'
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPowerShell.min.js" charset="utf-8"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(".toggle_container").hide();
|
||||
var previousId;
|
||||
if(location.hash) {
|
||||
var id = location.hash.slice(1); //Get rid of the # mark
|
||||
var elementToShow = $("#" + id); //Save local reference
|
||||
if(elementToShow.length) { //Check if the element exists
|
||||
elementToShow.slideToggle('fast'); //Show the element
|
||||
elementToShow.addClass("check_list_selected"); //Add class to element (the link)
|
||||
}
|
||||
previousId = id;
|
||||
}
|
||||
|
||||
$('.nav-menu a, .psLink a').click(function() {
|
||||
$(".sidebar-navbar-collapse").collapse('hide');
|
||||
$('.toggle_container').hide(); // Hide all
|
||||
var elem = $(this).prop("hash");
|
||||
$(elem).toggle('fast'); // Show HREF/to/ID one
|
||||
history.pushState({}, '', $(this).attr("href"));
|
||||
window.scrollTo(0, 0);
|
||||
return false;
|
||||
});
|
||||
SyntaxHighlighter.defaults['toolbar'] = false;
|
||||
SyntaxHighlighter.defaults['gutter'] = false;
|
||||
SyntaxHighlighter.all();
|
||||
|
||||
$('#searchList').btsListFilter('#searchinput', {itemChild: 'a', initial: false, resetOnBlur: true});
|
||||
|
||||
$(document).keyup(function( e ) {
|
||||
if(!$('#searchinput').is(':focus') && e.which >= 65 && e.which <= 90 ){
|
||||
$('#searchinput').val(String.fromCharCode(e.keyCode));
|
||||
$('#searchinput').focus();
|
||||
}
|
||||
});
|
||||
$(document).click(function() {
|
||||
$('#searchinput').blur();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- bootstrap-list-filter.min.js - removed as external resource and added as content -->
|
||||
<script>
|
||||
/*
|
||||
* bootstrap-list-filter v0.1.7 - 2015-03-30
|
||||
*
|
||||
* Copyright 2015 Stefano Cudini
|
||||
* stefano.cudini@gmail.com
|
||||
* http://labs.easyblog.it/
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Demos:
|
||||
* http://labs.easyblog.it/bootstrap-list-filter/
|
||||
*
|
||||
* Source:
|
||||
* git@github.com:stefanocudini/bootstrap-list-filter.git
|
||||
*
|
||||
*/
|
||||
!function(a){a.fn.btsListFilter=function(b,c){function d(a,b){return a.replace(/\{ *([\w_]+) *\}/g,function(a,c){return b[c]||""})}function e(a,b){var c;return b=b||300,function(){var d=this,e=arguments;clearTimeout(c),c=setTimeout(function(){a.apply(d,Array.prototype.slice.call(e))},b)}}var f,g=this,h=a(this),i=a(b),j=h;return c=a.extend({delay:300,minLength:1,initial:!0,eventKey:"keyup",resetOnBlur:!0,sourceData:null,sourceTmpl:'<a class="list-group-item" href="#"><span>{title}</span></a>',sourceNode:function(a){return d(c.sourceTmpl,a)},emptyNode:function(){return'<a class="list-group-item well" href="#"><span>No Results</span></a>'},itemEl:".list-group-item",itemChild:null,itemFilter:function(b,d){d=d&&d.replace(new RegExp("[({[^.$*+?\\]})]","g"),"");var e=a(b).text(),f=c.initial?"^":"",g=new RegExp(f+d,"i");return g.test(e)}},c),i.on(c.eventKey,e(function(){var b=a(this).val();c.itemEl&&(j=h.find(c.itemEl)),c.itemChild&&(j=j.find(c.itemChild));var d=j.filter(function(){return c.itemFilter.call(g,this,b)}),e=j.not(d);c.itemChild&&(d=d.parents(c.itemEl),e=e.parents(c.itemEl).hide()),""!==b&&b.length>=c.minLength?(d.show(),e.hide(),"function"===a.type(c.sourceData)?(d.hide(),e.hide(),f&&(a.isFunction(f.abort)?f.abort():a.isFunction(f.stop)&&f.stop()),f=c.sourceData.call(g,b,function(b){if(f=null,d.hide(),e.hide(),h.find(".bts-dynamic-item").remove(),b&&0!==b.length)for(var i in b)a(c.sourceNode.call(g,b[i])).addClass("bts-dynamic-item").appendTo(h);else a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)})):0===d.length&&a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)):(d.show(),e.show(),h.find(".bts-dynamic-item").remove())},c.delay)),c.resetOnBlur&&i.on("blur",function(){a(this).val("").trigger(c.eventKey)}),h}}(jQuery);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'@
|
159
helpers/psDoc-master/src/out-markdown-template.ps1
Normal file
159
helpers/psDoc-master/src/out-markdown-template.ps1
Normal file
@@ -0,0 +1,159 @@
|
||||
function TrimAllLines([string] $str) {
|
||||
$lines = $str -split "`n"
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] = $lines[$i].Trim()
|
||||
}
|
||||
|
||||
# Trim EOL.
|
||||
($lines | Out-String).Trim()
|
||||
}
|
||||
|
||||
function FixMarkdownString([string] $in = '', [bool] $includeBreaks = $false) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
$replacements = @{
|
||||
'\' = '\\'
|
||||
'`' = '\`'
|
||||
'*' = '\*'
|
||||
'_' = '\_'
|
||||
'{' = '\{'
|
||||
'}' = '\}'
|
||||
'[' = '\['
|
||||
']' = '\]'
|
||||
'(' = '\('
|
||||
')' = '\)'
|
||||
'#' = '\#'
|
||||
'+' = '\+'
|
||||
'!' = '\!'
|
||||
'<' = '\<'
|
||||
'>' = '\>'
|
||||
}
|
||||
|
||||
$rtn = $in.Trim()
|
||||
foreach ($key in $replacements.Keys) {
|
||||
$rtn = $rtn.Replace($key, $replacements[$key])
|
||||
}
|
||||
|
||||
$rtn = TrimAllLines $rtn
|
||||
$crlf = [Environment]::NewLine
|
||||
if ($includeBreaks) {
|
||||
$rtn = $rtn.Replace($crlf, " $crlf")
|
||||
}
|
||||
else {
|
||||
$rtn = $rtn.Replace($crlf, " ").Trim()
|
||||
}
|
||||
$rtn
|
||||
}
|
||||
|
||||
function FixMarkdownCodeString([string] $in) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
TrimAllLines $in
|
||||
}
|
||||
|
||||
@"
|
||||
# $moduleName Module
|
||||
"@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
## $(FixMarkdownString($_.Name))
|
||||
"@
|
||||
$synopsis = $_.synopsis.Trim()
|
||||
$syntax = $_.syntax | out-string
|
||||
if (-not ($synopsis -ilike "$($_.Name.Trim())*")) {
|
||||
$tmp = $synopsis
|
||||
$synopsis = $syntax
|
||||
$syntax = $tmp
|
||||
@"
|
||||
### Synopsis
|
||||
$(FixMarkdownString($syntax))
|
||||
"@
|
||||
}
|
||||
@"
|
||||
### Syntax
|
||||
``````powershell
|
||||
$($synopsis)
|
||||
``````
|
||||
"@
|
||||
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
### $($_.Name) Aliases
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
- $($_.Name)
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if ($_.parameters) {
|
||||
@"
|
||||
### Parameters
|
||||
| Name | Alias | Description | Required? | Pipeline Input | Default Value |
|
||||
| - | - | - | - | - | - |
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
| <nobr>$(FixMarkdownString($_.Name))</nobr> | $(FixMarkdownString($_.Aliases)) | $(FixMarkdownString(($_.Description | out-string).Trim())) | $(FixMarkdownString($_.Required)) | $(FixMarkdownString($_.PipelineInput)) | $(FixMarkdownString($_.DefaultValue)) |
|
||||
"@
|
||||
}
|
||||
}
|
||||
$inputTypes = $(FixMarkdownString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
### Inputs
|
||||
- $inputTypes
|
||||
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixMarkdownString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
### Outputs
|
||||
- $returnValues
|
||||
|
||||
"@
|
||||
}
|
||||
$notes = $(FixMarkdownString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
### Note
|
||||
$notes
|
||||
|
||||
"@
|
||||
}
|
||||
if (($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
### Examples
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
**$(FixMarkdownString($_.title.Trim(('-',' '))))**
|
||||
``````powershell
|
||||
$(FixMarkdownCodeString($_.code | out-string ))
|
||||
``````
|
||||
$(FixMarkdownString($_.remarks | out-string ) $true)
|
||||
|
||||
"@
|
||||
}
|
||||
}
|
||||
if (($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
### Links
|
||||
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
- [$($_.name)]($($_.link))
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
63
helpers/psDoc-master/src/psDoc.ps1
Normal file
63
helpers/psDoc-master/src/psDoc.ps1
Normal file
@@ -0,0 +1,63 @@
|
||||
param(
|
||||
[parameter(Mandatory=$true, Position=0)] [string] $moduleName,
|
||||
[parameter(Mandatory=$false, Position=1)] [string] $template = "./out-html-template.ps1",
|
||||
[parameter(Mandatory=$false, Position=2)] [string] $outputDir = './help',
|
||||
[parameter(Mandatory=$false, Position=3)] [string] $fileName = 'index.html'
|
||||
)
|
||||
|
||||
function FixString ($in = '', [bool]$includeBreaks = $false){
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
$rtn = $in.Replace('&', '&').Replace('<', '<').Replace('>', '>').Trim()
|
||||
|
||||
if($includeBreaks){
|
||||
$rtn = $rtn.Replace([Environment]::NewLine, '<br>')
|
||||
}
|
||||
return $rtn
|
||||
}
|
||||
|
||||
function Update-Progress($name, $action){
|
||||
Write-Progress -Activity "Rendering $action for $name" -CurrentOperation "Completed $progress of $totalCommands." -PercentComplete $(($progress/$totalCommands)*100)
|
||||
}
|
||||
$i = 0
|
||||
$commandsHelp = (Get-Command -module $moduleName) | get-help -full | Where-Object {! $_.name.EndsWith('.ps1')}
|
||||
|
||||
foreach ($h in $commandsHelp){
|
||||
$cmdHelp = (Get-Command $h.Name)
|
||||
|
||||
# Get any aliases associated with the method
|
||||
$alias = get-alias -definition $h.Name -ErrorAction SilentlyContinue
|
||||
if($alias){
|
||||
$h | Add-Member Alias $alias
|
||||
}
|
||||
|
||||
# Parse the related links and assign them to a links hashtable.
|
||||
if(($h.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
$links = $h.relatedLinks.navigationLink | % {
|
||||
if($_.uri){ @{name = $_.uri; link = $_.uri; target='_blank'} }
|
||||
if($_.linkText){ @{name = $_.linkText; link = "#$($_.linkText)"; cssClass = 'psLink'; target='_top'} }
|
||||
}
|
||||
$h | Add-Member Links $links
|
||||
}
|
||||
|
||||
# Add parameter aliases to the object.
|
||||
foreach($p in $h.parameters.parameter ){
|
||||
$paramAliases = ($cmdHelp.parameters.values | where name -like $p.name | select aliases).Aliases
|
||||
if($paramAliases){
|
||||
$p | Add-Member Aliases "$($paramAliases -join ', ')" -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create the output directory if it does not exist
|
||||
if (-Not (Test-Path $outputDir)) {
|
||||
New-Item -Path $outputDir -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
$totalCommands = $commandsHelp.Count
|
||||
if (!$totalCommands) {
|
||||
$totalCommands = 1
|
||||
}
|
||||
|
||||
$template = Get-Content $template -raw -force
|
||||
Invoke-Expression $template > "$outputDir\$fileName"
|
16
source/Classes/CISAuditResult.ps1
Normal file
16
source/Classes/CISAuditResult.ps1
Normal file
@@ -0,0 +1,16 @@
|
||||
class CISAuditResult {
|
||||
[string]$Status
|
||||
[string]$ELevel
|
||||
[string]$ProfileLevel
|
||||
[string]$Rec
|
||||
[string]$RecDescription
|
||||
[string]$CISControlVer = 'v8'
|
||||
[string]$CISControl
|
||||
[string]$CISDescription
|
||||
[bool]$IG1
|
||||
[bool]$IG2
|
||||
[bool]$IG3
|
||||
[bool]$Result
|
||||
[string]$Details
|
||||
[string]$FailureReason
|
||||
}
|
136
source/M365FoundationsCISReport.psd1
Normal file
136
source/M365FoundationsCISReport.psd1
Normal file
@@ -0,0 +1,136 @@
|
||||
#
|
||||
# Module manifest for module 'M365FoundationsCISReport'
|
||||
#
|
||||
# Generated by: Douglas S. Rios (DrIOSx)
|
||||
#
|
||||
# Generated on: 3/25/2024
|
||||
#
|
||||
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'M365FoundationsCISReport.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '0.0.1'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '0d064bfb-d1ce-484b-a173-993b55984dc9'
|
||||
|
||||
# Author of this module
|
||||
Author = 'Douglas S. Rios (DrIOSx)'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = 'CrticalSolutions Net LLC'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) 2024 Douglas S. Rios (DrIOSx). All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Automated assessment of 50 CIS 365 Foundations v3.0.0 benchmark.'
|
||||
|
||||
# Minimum version of the Windows PowerShell engine required by this module
|
||||
PowerShellVersion = '5.0'
|
||||
|
||||
# Name of the Windows PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the Windows PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# CLRVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @()
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = @()
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
Prerelease = ''
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
LicenseUri = 'https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en'
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
ReleaseNotes = ''
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
5
source/M365FoundationsCISReport.psm1
Normal file
5
source/M365FoundationsCISReport.psm1
Normal file
@@ -0,0 +1,5 @@
|
||||
<#
|
||||
This file is intentionally left empty. It is must be left here for the module
|
||||
manifest to refer to. It is recreated during the build process.
|
||||
#>
|
||||
|
33
source/Private/Assert-ModuleAvailability.ps1
Normal file
33
source/Private/Assert-ModuleAvailability.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
function Assert-ModuleAvailability {
|
||||
param(
|
||||
[string]$ModuleName,
|
||||
[string]$RequiredVersion,
|
||||
[string]$SubModuleName
|
||||
)
|
||||
|
||||
try {
|
||||
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }
|
||||
|
||||
if ($null -eq $module) {$auditResult.Profile
|
||||
Write-Host "Installing $ModuleName module..."
|
||||
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..."
|
||||
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
Write-Host "$ModuleName module is already at required version or newer."
|
||||
}
|
||||
|
||||
if ($SubModuleName) {
|
||||
Import-Module -Name "$ModuleName.$SubModuleName" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
|
||||
}
|
||||
else {
|
||||
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "An error occurred with module $ModuleName`: $_"
|
||||
}
|
||||
}
|
56
source/Private/Connect-M365Suite.ps1
Normal file
56
source/Private/Connect-M365Suite.ps1
Normal file
@@ -0,0 +1,56 @@
|
||||
function Connect-M365Suite {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameter to specify the SharePoint Online Tenant Admin URL
|
||||
[Parameter(Mandatory)]
|
||||
[string]$TenantAdminUrl
|
||||
)
|
||||
$VerbosePreference = "SilentlyContinue"
|
||||
try {
|
||||
|
||||
# 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
|
||||
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
|
||||
}
|
||||
|
||||
# Validate SharePoint Online Tenant Admin URL
|
||||
if (-not $TenantAdminUrl) {
|
||||
throw "SharePoint Online Tenant Admin URL is required."
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
}
|
||||
catch {
|
||||
$VerbosePreference = "Continue"
|
||||
Write-Host "There was an error establishing one or more connections: $_" -ForegroundColor Red
|
||||
throw $_
|
||||
}
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
39
source/Private/Disconnect-M365Suite.ps1
Normal file
39
source/Private/Disconnect-M365Suite.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
function Disconnect-M365Suite {
|
||||
# Clean up sessions
|
||||
try {
|
||||
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
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to disconnect from Azure AD: $_"
|
||||
}
|
||||
try {
|
||||
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
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to disconnect from SharePoint Online: $_"
|
||||
}
|
||||
try {
|
||||
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
|
||||
}
|
258
source/Public/Invoke-M365SecurityAudit.ps1
Normal file
258
source/Public/Invoke-M365SecurityAudit.ps1
Normal file
@@ -0,0 +1,258 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Invokes a security audit for Microsoft 365 environments.
|
||||
.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.
|
||||
.PARAMETER DomainName
|
||||
The domain name of the Microsoft 365 environment. This parameter is mandatory.
|
||||
.PARAMETER ELevel
|
||||
Specifies the E-Level (E3 or E5) for the audit. This parameter is optional and can be combined with the ProfileLevel parameter.
|
||||
.PARAMETER ProfileLevel
|
||||
Specifies the profile level (L1 or L2) for the audit. This parameter is optional and can be combined with the ELevel parameter.
|
||||
.PARAMETER IncludeIG1
|
||||
If specified, includes tests where IG1 is true.
|
||||
.PARAMETER IncludeIG2
|
||||
If specified, includes tests where IG2 is true.
|
||||
.PARAMETER IncludeIG3
|
||||
If specified, includes tests where IG3 is true.
|
||||
.PARAMETER IncludeRecommendation
|
||||
Specifies specific recommendations to include in the audit. Accepts an array of recommendation numbers.
|
||||
.PARAMETER SkipRecommendation
|
||||
Specifies specific recommendations to exclude from the audit. Accepts an array of recommendation numbers.
|
||||
.PARAMETER DoNotConnect
|
||||
If specified, the cmdlet will not establish a connection to Microsoft 365 services.
|
||||
.PARAMETER DoNotDisconnect
|
||||
If specified, the cmdlet will not disconnect from Microsoft 365 services after execution.
|
||||
.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"
|
||||
|
||||
Performs a security audit for the E5 level and L1 profile in the specified Microsoft 365 environment.
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -IncludeIG1
|
||||
|
||||
Performs an audit including all tests where IG1 is true.
|
||||
.EXAMPLE
|
||||
PS> Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com" -SkipRecommendation '1.1.3', '2.1.1'
|
||||
|
||||
Performs an audit while excluding specific recommendations 1.1.3 and 2.1.1.
|
||||
.EXAMPLE
|
||||
PS> $auditResults = Invoke-M365SecurityAudit -TenantAdminUrl "https://contoso-admin.sharepoint.com" -DomainName "contoso.com"
|
||||
PS> $auditResults | Export-Csv -Path "auditResults.csv" -NoTypeInformation
|
||||
|
||||
Captures the audit results into a variable and exports them to a CSV file.
|
||||
.INPUTS
|
||||
None. You cannot pipe objects to Invoke-M365SecurityAudit.
|
||||
.OUTPUTS
|
||||
CISAuditResult[]
|
||||
The cmdlet returns an array of CISAuditResult objects representing the results of the security audit.
|
||||
.NOTES
|
||||
- This module is based on CIS benchmarks.
|
||||
- Governed by the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
|
||||
- Commercial use is not permitted. This module cannot be sold or used for commercial purposes.
|
||||
- Modifications and sharing are allowed under the same license.
|
||||
- For full license details, visit: https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en
|
||||
.LINK
|
||||
Online Version: [GitHub Repository URL]
|
||||
#>
|
||||
|
||||
function Invoke-M365SecurityAudit {
|
||||
[CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')]
|
||||
[OutputType([CISAuditResult[]])]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TenantAdminUrl,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DomainName,
|
||||
|
||||
# E-Level with optional ProfileLevel selection
|
||||
[Parameter(ParameterSetName = 'ELevelFilter')]
|
||||
[ValidateSet('E3', 'E5')]
|
||||
[string]$ELevel,
|
||||
|
||||
[Parameter(ParameterSetName = 'ELevelFilter')]
|
||||
[ValidateSet('L1', 'L2')]
|
||||
[string]$ProfileLevel,
|
||||
|
||||
# IG Filters, one at a time
|
||||
[Parameter(ParameterSetName = 'IG1Filter')]
|
||||
[switch]$IncludeIG1,
|
||||
|
||||
[Parameter(ParameterSetName = 'IG2Filter')]
|
||||
[switch]$IncludeIG2,
|
||||
|
||||
[Parameter(ParameterSetName = 'IG3Filter')]
|
||||
[switch]$IncludeIG3,
|
||||
|
||||
# Inclusion of specific recommendation numbers
|
||||
[Parameter(ParameterSetName = 'RecFilter')]
|
||||
[ValidateSet(
|
||||
'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', `
|
||||
'7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', `
|
||||
'8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', `
|
||||
'8.5.7', '8.6.1'
|
||||
)]
|
||||
[string[]]$IncludeRecommendation,
|
||||
|
||||
# Exclusion of specific recommendation numbers
|
||||
[Parameter(ParameterSetName = 'SkipRecFilter')]
|
||||
[ValidateSet(
|
||||
'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', `
|
||||
'7.2.5', '7.2.6', '7.2.7', '7.2.9', '7.3.1', '7.3.2', '7.3.4', '8.1.1', `
|
||||
'8.1.2', '8.2.1', '8.5.1', '8.5.2', '8.5.3', '8.5.4', '8.5.5', '8.5.6', `
|
||||
'8.5.7', '8.6.1'
|
||||
)]
|
||||
[string[]]$SkipRecommendation,
|
||||
|
||||
# Common parameters for all parameter sets
|
||||
[switch]$DoNotConnect,
|
||||
[switch]$DoNotDisconnect,
|
||||
[switch]$NoModuleCheck
|
||||
)
|
||||
|
||||
|
||||
|
||||
Begin {
|
||||
if ($script:MaximumFunctionCount -lt 8192) {
|
||||
$script:MaximumFunctionCount = 8192
|
||||
}
|
||||
# Ensure required modules are installed
|
||||
# Define the required modules and versions in a hashtable
|
||||
if (!($NoModuleCheck)) {
|
||||
$requiredModules = @(
|
||||
@{ 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" }
|
||||
)
|
||||
foreach ($module in $requiredModules) {
|
||||
Assert-ModuleAvailability -ModuleName $module.ModuleName -RequiredVersion $module.RequiredVersion -SubModuleName $module.SubModuleName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# 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"
|
||||
$testDefinitions = Import-Csv -Path $testDefinitionsPath
|
||||
|
||||
# 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 }
|
||||
}
|
||||
}
|
||||
# End switch ($PSCmdlet.ParameterSetName)
|
||||
|
||||
# Determine which test files to load based on filtering
|
||||
$testsToLoad = $testDefinitions.TestFileName | ForEach-Object { $_ -replace '.ps1$', '' }
|
||||
|
||||
# Display the tests that would be loaded if the function is called with -WhatIf
|
||||
|
||||
Write-Verbose "The $(($testsToLoad).count) test/s that would be loaded based on filter criteria:"
|
||||
$testsToLoad | ForEach-Object { Write-Verbose " $_" }
|
||||
} # End Begin
|
||||
|
||||
Process {
|
||||
$allAuditResults = @() # Initialize a collection to hold all results
|
||||
|
||||
# Dynamically dot-source the test scripts
|
||||
$testsFolderPath = Join-Path -Path $PSScriptRoot -ChildPath "tests"
|
||||
$testFiles = Get-ChildItem -Path $testsFolderPath -Filter "Test-*.ps1" |
|
||||
Where-Object { $testsToLoad -contains $_.BaseName }
|
||||
|
||||
# Import the test functions
|
||||
$testFiles | ForEach-Object {
|
||||
Try {
|
||||
. $_.FullName
|
||||
}
|
||||
Catch {
|
||||
Write-Error "Failed to load test function $($_.Name): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Execute each test function from the prepared list
|
||||
foreach ($testFunction in $testFiles) {
|
||||
$functionName = $testFunction.BaseName
|
||||
$functionCmd = Get-Command -Name $functionName
|
||||
|
||||
# Check if the test function needs DomainName parameter
|
||||
$paramList = @{}
|
||||
if ('DomainName' -in $functionCmd.Parameters.Keys) {
|
||||
$paramList.DomainName = $DomainName
|
||||
}
|
||||
|
||||
# Use splatting to pass parameters
|
||||
if ($PSCmdlet.ShouldProcess($functionName, "Execute test")) {
|
||||
Write-Host "Running $functionName..."
|
||||
$result = & $functionName @paramList
|
||||
# Assuming each function returns an array of CISAuditResult or a single CISAuditResult
|
||||
$allAuditResults += $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
End {
|
||||
# Return all collected audit results
|
||||
return $allAuditResults
|
||||
# Check if the Disconnect switch is present
|
||||
if (!($DoNotDisconnect)) {
|
||||
# Clean up sessions
|
||||
Disconnect-M365Suite
|
||||
}
|
||||
}
|
||||
}
|
24
source/en-US/about_M365FoundationsCISReport.help.txt
Normal file
24
source/en-US/about_M365FoundationsCISReport.help.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
TOPIC
|
||||
about_M365FoundationsCISReport
|
||||
|
||||
SHORT DESCRIPTION
|
||||
Automated assessment of 50 CIS 365 Foundations v3.0.0 benchmark.
|
||||
|
||||
LONG DESCRIPTION
|
||||
Automated assessment of 50 CIS 365 Foundations v3.0.0 benchmark.
|
||||
|
||||
EXAMPLES
|
||||
PS C:\> {{ add examples here }}
|
||||
|
||||
NOTE:
|
||||
Thank you to all those who contributed to this module, by writing code, sharing opinions, and provided feedback.
|
||||
|
||||
TROUBLESHOOTING NOTE:
|
||||
Look out on the Github repository for issues and new releases.
|
||||
|
||||
SEE ALSO
|
||||
- {{ Please add Project URI such as github }}}
|
||||
|
||||
KEYWORDS
|
||||
{{ Add comma separated keywords here }}
|
||||
|
51
source/helper/TestDefinitions.csv
Normal file
51
source/helper/TestDefinitions.csv
Normal file
@@ -0,0 +1,51 @@
|
||||
Index,TestFileName,Rec,ELevel,ProfileLevel,IG1,IG2,IG3
|
||||
1,Test-AntiPhishingPolicy.ps1,2.1.7,E5,L1,FALSE,FALSE,TRUE
|
||||
2,Test-AuditDisabledFalse.ps1,6.1.1,E3,L1,TRUE,TRUE,TRUE
|
||||
3,Test-AuditLogSearch.ps1,3.1.1,E3,L1,TRUE,TRUE,TRUE
|
||||
4,Test-BlockChannelEmails.ps1,8.1.2,E3,L1,FALSE,FALSE,FALSE
|
||||
5,Test-BlockMailForwarding.ps1,6.2.1,E3,L1,FALSE,FALSE,FALSE
|
||||
6,Test-BlockSharedMailboxSignIn.ps1,1.2.2,E3,L1,FALSE,FALSE,FALSE
|
||||
7,Test-CommonAttachmentFilter.ps1,2.1.2,E3,L1,FALSE,TRUE,TRUE
|
||||
8,Test-CustomerLockbox.ps1,1.3.6,E5,L2,FALSE,FALSE,FALSE
|
||||
9,Test-DialInBypassLobby.ps1,8.5.4,E3,L1,FALSE,FALSE,FALSE
|
||||
10,Test-DisallowInfectedFilesDownload.ps1,7.3.1,E5,L2,TRUE,TRUE,TRUE
|
||||
11,Test-EnableDKIM.ps1,2.1.9,E3,L1,FALSE,TRUE,TRUE
|
||||
12,Test-ExternalNoControl.ps1,8.5.7,E3,L1,FALSE,FALSE,FALSE
|
||||
13,Test-ExternalSharingCalendars.ps1,1.3.3,E3,L2,FALSE,TRUE,TRUE
|
||||
14,Test-GlobalAdminsCount.ps1,1.1.3,E3,L1,TRUE,TRUE,TRUE
|
||||
15,Test-GuestAccessExpiration.ps1,7.2.9,E3,L1,FALSE,FALSE,FALSE
|
||||
16,Test-IdentifyExternalEmail.ps1,6.2.3,E3,L1,FALSE,FALSE,FALSE
|
||||
17,Test-LinkSharingRestrictions.ps1,7.2.7,E3,L1,TRUE,TRUE,TRUE
|
||||
18,Test-MailboxAuditingE3.ps1,6.1.2,E3,L1,TRUE,TRUE,TRUE
|
||||
19,Test-MailboxAuditingE5.ps1,6.1.3,E5,L1,TRUE,TRUE,TRUE
|
||||
20,Test-MailTipsEnabled.ps1,6.5.2,E3,L2,FALSE,FALSE,FALSE
|
||||
21,Test-ManagedApprovedPublicGroups.ps1,1.2.1,E3,L2,TRUE,TRUE,TRUE
|
||||
22,Test-MeetingChatNoAnonymous.ps1,8.5.5,E3,L1,FALSE,FALSE,FALSE
|
||||
23,Test-ModernAuthExchangeOnline.ps1,6.5.1,E3,L1,FALSE,TRUE,TRUE
|
||||
24,Test-ModernAuthSharePoint.ps1,7.2.1,E3,L1,FALSE,TRUE,TRUE
|
||||
25,Test-NoAnonymousMeetingJoin.ps1,8.5.1,E3,L2,FALSE,FALSE,FALSE
|
||||
26,Test-NoAnonymousMeetingStart.ps1,8.5.2,E3,L1,FALSE,FALSE,FALSE
|
||||
27,Test-NotifyMalwareInternal.ps1,2.1.3,E3,L1,FALSE,TRUE,TRUE
|
||||
28,Test-NoWhitelistDomains.ps1,6.2.2,E3,L1,FALSE,FALSE,FALSE
|
||||
29,Test-OneDriveContentRestrictions.ps1,7.2.4,E3,L2,TRUE,TRUE,TRUE
|
||||
30,Test-OneDriveSyncRestrictions.ps1,7.3.2,E3,L2,FALSE,FALSE,FALSE
|
||||
31,Test-OrganizersPresent.ps1,8.5.6,E3,L1,FALSE,FALSE,FALSE
|
||||
32,Test-OrgOnlyBypassLobby.ps1,8.5.3,E3,L1,FALSE,FALSE,TRUE
|
||||
33,Test-PasswordHashSync.ps1,5.1.8.1,E3,L1,FALSE,TRUE,TRUE
|
||||
34,Test-PasswordNeverExpirePolicy.ps1,1.3.1,E3,L1,TRUE,TRUE,TRUE
|
||||
35,Test-ReauthWithCode.ps1,7.2.10,E3,L1,FALSE,FALSE,FALSE
|
||||
36,Test-ReportSecurityInTeams.ps1,8.6.1,E3,L1,FALSE,FALSE,FALSE
|
||||
37,Test-RestrictCustomScripts.ps1,7.3.4,E3,L1,FALSE,FALSE,TRUE
|
||||
38,Test-RestrictExternalSharing.ps1,7.2.3,E3,L1,TRUE,TRUE,TRUE
|
||||
39,Test-RestrictOutlookAddins.ps1,6.3.1,E3,L2,FALSE,TRUE,TRUE
|
||||
40,Test-RestrictStorageProvidersOutlook.ps1,6.5.3,E3,L2,TRUE,TRUE,TRUE
|
||||
41,Test-RestrictTenantCreation.ps1,5.1.2.3,E3,L1,FALSE,FALSE,FALSE
|
||||
42,Test-SafeAttachmentsPolicy.ps1,2.1.4,E5,L2,FALSE,FALSE,TRUE
|
||||
43,Test-SafeAttachmentsTeams.ps1,2.1.5,E5,L2,TRUE,TRUE,TRUE
|
||||
44,Test-SafeLinksOfficeApps.ps1,2.1.1,E5,L2,TRUE,TRUE,TRUE
|
||||
45,Test-SharePointAADB2B.ps1,7.2.2,E3,L1,FALSE,FALSE,FALSE
|
||||
46,Test-SharePointExternalSharingDomains.ps1,7.2.6,E3,L2,TRUE,TRUE,TRUE
|
||||
47,Test-SharePointGuestsItemSharing.ps1,7.2.5,E3,L2,TRUE,TRUE,TRUE
|
||||
48,Test-SpamPolicyAdminNotify.ps1,2.1.6,E3,L1,FALSE,TRUE,TRUE
|
||||
49,Test-TeamsExternalAccess.ps1,8.2.1,E3,L2,FALSE,FALSE,FALSE
|
||||
50,Test-TeamsExternalFileSharing.ps1,8.1.1,E3,L2,TRUE,TRUE,TRUE
|
|
59
source/tests/Test-AntiPhishingPolicy.ps1
Normal file
59
source/tests/Test-AntiPhishingPolicy.ps1
Normal file
@@ -0,0 +1,59 @@
|
||||
function Test-AntiPhishingPolicy {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
|
||||
# Retrieve and validate the anti-phishing policies
|
||||
$antiPhishPolicies = Get-AntiPhishPolicy
|
||||
$validatedPolicies = $antiPhishPolicies | Where-Object {
|
||||
$_.Enabled -eq $true -and
|
||||
$_.PhishThresholdLevel -ge 2 -and
|
||||
$_.EnableMailboxIntelligenceProtection -eq $true -and
|
||||
$_.EnableMailboxIntelligence -eq $true -and
|
||||
$_.EnableSpoofIntelligence -eq $true
|
||||
}
|
||||
|
||||
# Check if there is at least one policy that meets the requirements
|
||||
$isCompliant = $validatedPolicies.Count -gt 0
|
||||
|
||||
# Prepare failure details if policies are not compliant
|
||||
$failureDetails = if (-not $isCompliant) {
|
||||
"No anti-phishing policy is fully compliant with CIS benchmark requirements."
|
||||
} else {
|
||||
"Compliant Anti-Phish Policy Names: " + ($validatedPolicies.Name -join ', ')
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "2.1.7"
|
||||
$auditResult.RecDescription = "Ensure that an anti-phishing policy has been created"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = $failureDetails
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "Anti-phishing policies do not meet CIS benchmark requirements." } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
43
source/tests/Test-AuditDisabledFalse.ps1
Normal file
43
source/tests/Test-AuditDisabledFalse.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
function Test-AuditDisabledFalse {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False'
|
||||
# Pass if AuditDisabled is False. Fail otherwise.
|
||||
$auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled
|
||||
$auditNotDisabled = -not $auditDisabledConfig.AuditDisabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditNotDisabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "6.1.1"
|
||||
$auditResult.RecDescription = "Ensure 'AuditDisabled' organizationally is set to 'False'"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditNotDisabled
|
||||
$auditResult.Details = if ($auditNotDisabled) { "Audit is not disabled organizationally" } else { "Audit is disabled organizationally" }
|
||||
$auditResult.FailureReason = if (-not $auditNotDisabled) { "AuditDisabled is set to True" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
43
source/tests/Test-AuditLogSearch.ps1
Normal file
43
source/tests/Test-AuditLogSearch.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
function Test-AuditLogSearch {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled
|
||||
# Pass if UnifiedAuditLogIngestionEnabled is True. Fail otherwise.
|
||||
$auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled
|
||||
$auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditLogResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "3.1.1"
|
||||
$auditResult.RecDescription = "Ensure Microsoft 365 audit log search is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditLogResult
|
||||
$auditResult.Details = "UnifiedAuditLogIngestionEnabled: $($auditLogConfig.UnifiedAuditLogIngestionEnabled)"
|
||||
$auditResult.FailureReason = if (-not $auditLogResult) { "Audit log search is not enabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
44
source/tests/Test-BlockChannelEmails.ps1
Normal file
44
source/tests/Test-BlockChannelEmails.ps1
Normal file
@@ -0,0 +1,44 @@
|
||||
function Test-BlockChannelEmails {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.1.2 (L1) Ensure users can't send emails to a channel email address
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global
|
||||
$allowEmailIntoChannel = $teamsClientConfig.AllowEmailIntoChannel
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # This control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.1.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the benchmark
|
||||
$auditResult.IG2 = $false # Set based on the benchmark
|
||||
$auditResult.IG3 = $false # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure users can't send emails to a channel email address"
|
||||
$auditResult.Result = -not $allowEmailIntoChannel
|
||||
$auditResult.Details = "AllowEmailIntoChannel is set to $allowEmailIntoChannel"
|
||||
$auditResult.FailureReason = if ($allowEmailIntoChannel) { "Emails can be sent to a channel email address" } else { "N/A" }
|
||||
$auditResult.Status = if (-not $allowEmailIntoChannel) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
51
source/tests/Test-BlockMailForwarding.ps1
Normal file
51
source/tests/Test-BlockMailForwarding.ps1
Normal file
@@ -0,0 +1,51 @@
|
||||
function Test-BlockMailForwarding {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "6.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure all forms of mail forwarding are blocked and/or disabled"
|
||||
}
|
||||
|
||||
process {
|
||||
# Verify that no rules are forwarding the email to external domains
|
||||
$transportRules = Get-TransportRule | Where-Object { $_.RedirectMessageTo -ne $null }
|
||||
$forwardingBlocked = $transportRules.Count -eq 0
|
||||
|
||||
$auditResult.Result = $forwardingBlocked
|
||||
$auditResult.Details = if ($transportRules.Count -gt 0) {
|
||||
$transportRules | ForEach-Object {
|
||||
"$($_.Name) redirects to $($_.RedirectMessageTo)"
|
||||
} -join " | "
|
||||
} else {
|
||||
"Step 1: No forwarding rules found. Please proceed with Step 2 described in CIS Benchmark."
|
||||
}
|
||||
$auditResult.FailureReason = if (-not $forwardingBlocked) {
|
||||
"Mail forwarding rules found: $($transportRules.Name -join ', ')"
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
$auditResult.Status = if ($forwardingBlocked) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return the result object
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
47
source/tests/Test-BlockSharedMailboxSignIn.ps1
Normal file
47
source/tests/Test-BlockSharedMailboxSignIn.ps1
Normal file
@@ -0,0 +1,47 @@
|
||||
function Test-BlockSharedMailboxSignIn {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked
|
||||
# Pass if all shared mailboxes have AccountEnabled set to False.
|
||||
# Fail if any shared mailbox has AccountEnabled set to True.
|
||||
|
||||
$MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox
|
||||
$sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId }
|
||||
$enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName }
|
||||
$allBlocked = $enabledMailboxes.Count -eq 0
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Control is explicitly not mapped
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "1.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $false # Control is not mapped, hence IG1 is false
|
||||
$auditResult.IG2 = $false # Control is not mapped, hence IG2 is false
|
||||
$auditResult.IG3 = $false # Control is not mapped, hence IG3 is false
|
||||
$auditResult.RecDescription = "Ensure sign-in to shared mailboxes is blocked"
|
||||
$auditResult.Result = $allBlocked
|
||||
$auditResult.Details = "Enabled Mailboxes: $($enabledMailboxes -join ', ')"
|
||||
$auditResult.FailureReason = if ($allBlocked) { "N/A" } else { "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" }
|
||||
$auditResult.Status = if ($allBlocked) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
46
source/tests/Test-CommonAttachmentFilter.ps1
Normal file
46
source/tests/Test-CommonAttachmentFilter.ps1
Normal file
@@ -0,0 +1,46 @@
|
||||
function Test-CommonAttachmentFilter {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled
|
||||
# Pass if EnableFileFilter is set to True. Fail otherwise.
|
||||
|
||||
$attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter
|
||||
$result = $attachmentFilter.EnableFileFilter
|
||||
$details = "File Filter Enabled: $($attachmentFilter.EnableFileFilter)"
|
||||
$failureReason = if ($result) { "N/A" } else { "Common Attachment Types Filter is disabled" }
|
||||
$status = if ($result) { "Pass" } else { "Fail" }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = $status
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "2.1.2"
|
||||
$auditResult.RecDescription = "Ensure the Common Attachment Types Filter is enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.6"
|
||||
$auditResult.CISDescription = "Block Unnecessary File Types"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
40
source/tests/Test-CustomerLockbox.ps1
Normal file
40
source/tests/Test-CustomerLockbox.ps1
Normal file
@@ -0,0 +1,40 @@
|
||||
function Test-CustomerLockbox {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.6 (L2) Ensure the customer lockbox feature is enabled
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled
|
||||
$customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($customerLockboxEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.ProfileLevel = "L2"
|
||||
$auditResult.Rec = "1.3.6"
|
||||
$auditResult.RecDescription = "Ensure the customer lockbox feature is enabled"
|
||||
$auditResult.CISControlVer = 'v8'
|
||||
$auditResult.CISControl = "0.0" # As per the snapshot provided, this is explicitly not mapped
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $customerLockboxEnabled
|
||||
$auditResult.Details = "Customer Lockbox Enabled: $customerLockboxEnabled"
|
||||
$auditResult.FailureReason = if ($customerLockboxEnabled) { "N/A" } else { "Customer lockbox feature is not enabled." }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
45
source/tests/Test-DialInBypassLobby.ps1
Normal file
45
source/tests/Test-DialInBypassLobby.ps1
Normal file
@@ -0,0 +1,45 @@
|
||||
function Test-DialInBypassLobby {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.4 (L1) Ensure users dialing in can't bypass the lobby
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby
|
||||
$PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure users dialing in can't bypass the lobby"
|
||||
$auditResult.Result = $PSTNBypassDisabled
|
||||
$auditResult.Details = "AllowPSTNUsersToBypassLobby is set to $($CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby)"
|
||||
$auditResult.FailureReason = if ($PSTNBypassDisabled) { "N/A" } else { "Users dialing in can bypass the lobby" }
|
||||
$auditResult.Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
41
source/tests/Test-DisallowInfectedFilesDownload.ps1
Normal file
41
source/tests/Test-DisallowInfectedFilesDownload.ps1
Normal file
@@ -0,0 +1,41 @@
|
||||
function Test-DisallowInfectedFilesDownload {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
|
||||
$SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload
|
||||
$isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software"
|
||||
|
||||
$auditResult.Rec = "7.3.1"
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.ProfileLevel = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure Office 365 SharePoint infected files are disallowed for download"
|
||||
|
||||
$auditResult.Result = $isDisallowInfectedFileDownloadEnabled
|
||||
$auditResult.Details = "DisallowInfectedFileDownload: $($SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload)"
|
||||
$auditResult.FailureReason = if (-not $isDisallowInfectedFileDownloadEnabled) { "Downloading infected files is not disallowed." } else { "N/A" }
|
||||
$auditResult.Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
44
source/tests/Test-EnableDKIM.ps1
Normal file
44
source/tests/Test-EnableDKIM.ps1
Normal file
@@ -0,0 +1,44 @@
|
||||
function Test-EnableDKIM {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains
|
||||
# Pass if Enabled is True for all domains. Fail if any domain has Enabled set to False.
|
||||
$dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled
|
||||
$dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false
|
||||
$dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($dkimResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "2.1.9"
|
||||
$auditResult.RecDescription = "Ensure that DKIM is enabled for all Exchange Online Domains"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.5"
|
||||
$auditResult.CISDescription = "Implement DMARC"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $dkimResult
|
||||
$auditResult.Details = if (-not $dkimResult) { "DKIM not enabled for: $($dkimFailedDomains -join ', ')" } else { "All domains have DKIM enabled" }
|
||||
$auditResult.FailureReason = if (-not $dkimResult) { "DKIM is not enabled for some domains" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
45
source/tests/Test-ExternalNoControl.ps1
Normal file
45
source/tests/Test-ExternalNoControl.ps1
Normal file
@@ -0,0 +1,45 @@
|
||||
function Test-ExternalNoControl {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.7 (L1) Ensure external participants can't give or request control
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl
|
||||
$externalControlRestricted = -not $CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure external participants can't give or request control"
|
||||
$auditResult.Result = $externalControlRestricted
|
||||
$auditResult.Details = "AllowExternalParticipantGiveRequestControl is set to $($CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl)"
|
||||
$auditResult.FailureReason = if ($externalControlRestricted) { "N/A" } else { "External participants can give or request control" }
|
||||
$auditResult.Status = if ($externalControlRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
53
source/tests/Test-ExternalSharingCalendars.ps1
Normal file
53
source/tests/Test-ExternalSharingCalendars.ps1
Normal file
@@ -0,0 +1,53 @@
|
||||
function Test-ExternalSharingCalendars {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated)
|
||||
$sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' }
|
||||
|
||||
# Check if calendar sharing is disabled in all applicable policies
|
||||
$isExternalSharingDisabled = $true
|
||||
$sharingPolicyDetails = @()
|
||||
foreach ($policy in $sharingPolicies) {
|
||||
if ($policy.Enabled -eq $true) {
|
||||
$isExternalSharingDisabled = $false
|
||||
$sharingPolicyDetails += "$($policy.Name): Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "1.3.3"
|
||||
$auditResult.RecDescription = "Ensure 'External sharing' of calendars is not available"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L2"
|
||||
# The following IG values are placeholders. Replace with actual values when known.
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.CISControlVer = "v8"
|
||||
# Placeholder for CIS Control, to be replaced with the actual value when available
|
||||
$auditResult.CISControl = "4.8"
|
||||
$auditResult.CISDescription = "Uninstall or Disable Unnecessary Services on Enterprise Assets and Software"
|
||||
$auditResult.Result = $isExternalSharingDisabled
|
||||
$auditResult.Details = if ($isExternalSharingDisabled) { "Calendar sharing with external users is disabled." } else { "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" }
|
||||
$auditResult.FailureReason = if ($isExternalSharingDisabled) { "N/A" } else { "Calendar sharing with external users is enabled in one or more policies." }
|
||||
$auditResult.Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
46
source/tests/Test-GlobalAdminsCount.ps1
Normal file
46
source/tests/Test-GlobalAdminsCount.ps1
Normal file
@@ -0,0 +1,46 @@
|
||||
function Test-GlobalAdminsCount {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.1.3 (L1) Ensure that between two and four global admins are designated
|
||||
# Pass if the count of global admins is between 2 and 4. Fail otherwise.
|
||||
|
||||
$globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'"
|
||||
$globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id
|
||||
$globalAdminCount = $globalAdmins.AdditionalProperties.Count
|
||||
$globalAdminUsernames = ($globalAdmins | ForEach-Object { $_.AdditionalProperties["displayName"] }) -join ', '
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "5.1"
|
||||
$auditResult.CISDescription = "Establish and Maintain an Inventory of Accounts"
|
||||
$auditResult.Rec = "1.1.3"
|
||||
$auditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.)
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $true # Set based on the benchmark
|
||||
$auditResult.IG2 = $true # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure that between two and four global admins are designated"
|
||||
$auditResult.Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4
|
||||
$auditResult.Details = "Count: $globalAdminCount; Users: $globalAdminUsernames"
|
||||
$auditResult.FailureReason = if ($globalAdminCount -lt 2) { "Less than 2 global admins: $globalAdminUsernames" } elseif ($globalAdminCount -gt 4) { "More than 4 global admins: $globalAdminUsernames" } else { "N/A" }
|
||||
$auditResult.Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
41
source/tests/Test-GuestAccessExpiration.ps1
Normal file
41
source/tests/Test-GuestAccessExpiration.ps1
Normal file
@@ -0,0 +1,41 @@
|
||||
function Test-GuestAccessExpiration {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
|
||||
$SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays
|
||||
$isGuestAccessExpirationConfiguredCorrectly = $SPOTenantGuestAccess.ExternalUserExpirationRequired -and $SPOTenantGuestAccess.ExternalUserExpireInDays -le 30
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.9"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure guest access to a site or OneDrive will expire automatically"
|
||||
|
||||
$auditResult.Result = $isGuestAccessExpirationConfiguredCorrectly
|
||||
$auditResult.Details = "ExternalUserExpirationRequired: $($SPOTenantGuestAccess.ExternalUserExpirationRequired); ExternalUserExpireInDays: $($SPOTenantGuestAccess.ExternalUserExpireInDays)"
|
||||
$auditResult.FailureReason = if (-not $isGuestAccessExpirationConfiguredCorrectly) { "Guest access expiration is not configured to automatically expire within 30 days or less." } else { "N/A" }
|
||||
$auditResult.Status = if ($isGuestAccessExpirationConfiguredCorrectly) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
44
source/tests/Test-IdentifyExternalEmail.ps1
Normal file
44
source/tests/Test-IdentifyExternalEmail.ps1
Normal file
@@ -0,0 +1,44 @@
|
||||
function Test-IdentifyExternalEmail {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.2.3 (L1) Ensure email from external senders is identified
|
||||
# Requirement is to have external sender tagging enabled
|
||||
|
||||
$externalInOutlook = Get-ExternalInOutlook
|
||||
$externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "6.2.3"
|
||||
$auditResult.RecDescription = "Ensure email from external senders is identified"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $externalTaggingEnabled
|
||||
$auditResult.Details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)"
|
||||
$auditResult.FailureReason = if (-not $externalTaggingEnabled) { "External sender tagging is disabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
42
source/tests/Test-LinkSharingRestrictions.ps1
Normal file
42
source/tests/Test-LinkSharingRestrictions.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
function Test-LinkSharingRestrictions {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
# Test behavior in prod
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
|
||||
$SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType
|
||||
$isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure link sharing is restricted in SharePoint and OneDrive"
|
||||
|
||||
$auditResult.Result = $isLinkSharingRestricted
|
||||
$auditResult.Details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)"
|
||||
$auditResult.FailureReason = if (-not $isLinkSharingRestricted) { "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
43
source/tests/Test-MailTipsEnabled.ps1
Normal file
43
source/tests/Test-MailTipsEnabled.ps1
Normal file
@@ -0,0 +1,43 @@
|
||||
function Test-MailTipsEnabled {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.5.2 (L2) Ensure MailTips are enabled for end users
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold
|
||||
$allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25
|
||||
$externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled
|
||||
|
||||
# Since there is no direct CIS Control mapping, the control will be set as not applicable.
|
||||
$auditResult.CISControl = "0"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "6.5.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure MailTips are enabled for end users"
|
||||
|
||||
$auditResult.Result = $allTipsEnabled -and $externalRecipientsTipsEnabled
|
||||
$auditResult.Details = "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)"
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "One or more MailTips settings are not configured as required." } else { "N/A" }
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
111
source/tests/Test-MailboxAuditingE3.ps1
Normal file
111
source/tests/Test-MailboxAuditingE3.ps1
Normal file
@@ -0,0 +1,111 @@
|
||||
function Test-MailboxAuditingE3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
|
||||
$e3SkuPartNumbers = @("ENTERPRISEPACK", "OFFICESUBSCRIPTION")
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "6.1.2"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E3 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)"
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license."
|
||||
|
||||
if ($hasOfficeE3) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
# Mark the user as processed
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E3 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
120
source/tests/Test-MailboxAuditingE5.ps1
Normal file
120
source/tests/Test-MailboxAuditingE5.ps1
Normal file
@@ -0,0 +1,120 @@
|
||||
function Test-MailboxAuditingE5 {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
begin {
|
||||
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.ProfileLevel = "L1"
|
||||
$auditResult.Rec = "6.1.3"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E5 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
$e5SkuPartNumbers = @("SPE_E5", "ENTERPRISEPREMIUM", "OFFICEE5")
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license."
|
||||
if ($hasOfficeE5) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "User $($user.UserPrincipalName) passed the mailbox audit checks."
|
||||
}
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
else {
|
||||
# Adding verbose output to indicate the user does not have an E5 license
|
||||
Write-Verbose "User $($user.UserPrincipalName) does not have an Office E5 license."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
if ($allFailures.Count -eq 0) {
|
||||
Write-Verbose "All evaluated E5 users have correct mailbox audit settings."
|
||||
}
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E5 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user