├── .gitattributes ├── .github └── workflows │ └── powershell.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── GitVersion.yml ├── LICENSE.md ├── README copy.md ├── README.md ├── RequiredModules.psd1 ├── Resolve-Dependency.ps1 ├── Resolve-Dependency.psd1 ├── SECURITY.md ├── build.ps1 ├── build.yaml ├── docs └── index.html ├── help ├── Export-M365SecurityAuditTable.md ├── Get-AdminRoleUserLicense.md ├── Get-MFAStatus.md ├── Grant-M365SecurityAuditConsent.md ├── Invoke-M365SecurityAudit.md ├── M365FoundationsCISReport.md ├── New-M365SecurityAuditAuthObject.md ├── Remove-RowsWithEmptyCSVStatus.md ├── Sync-CISExcelAndCsvData.md └── about_M365FoundationsCISReport.md ├── helpers ├── Automation Candidates.md ├── Build-Help.ps1 ├── CIS 365 v3.0.0 Controls │ ├── Test-AntiPhishingPolicy_2.1.7_E5L1_IG3.ps1 │ ├── Test-AuditDisabledFalse_6.1.1_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-AuditLogSearch_3.1.1_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-BlockChannelEmails_8.1.2_E3L1.ps1 │ ├── Test-BlockMailForwarding_6.2.1_E3L1.ps1 │ ├── Test-BlockSharedMailboxSignIn_1.2.2_E3L1.ps1 │ ├── Test-CommonAttachmentFilter_2.1.2_E3L1_IG2_IG3.ps1 │ ├── Test-CustomerLockbox_1.3.6_E5L2.ps1 │ ├── Test-DialInBypassLobby_8.5.4_E3L1.ps1 │ ├── Test-DisallowInfectedFilesDownload_7.3.1_E5L2_IG1_IG2_IG3.ps1 │ ├── Test-EnableDKIM_2.1.9_E3L1_IG2_IG3.ps1 │ ├── Test-ExternalNoControl_8.5.7_E3L1.ps1 │ ├── Test-ExternalSharingCalendars_1.3.3_E3L2_IG2_IG3.ps1 │ ├── Test-GlobalAdminsCount_1.1.3_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-GuestAccessExpiration_7.2.9_E3L1.ps1 │ ├── Test-IdentifyExternalEmail_6.2.3_E3L1.ps1 │ ├── Test-LinkSharingRestrictions_7.2.7_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-MailTipsEnabled_6.5.2_E3L2.ps1 │ ├── Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3.ps1 │ ├── Test-ManagedApprovedPublicGroups_1.2.1_E3L2_IG1_IG2_IG3.ps1 │ ├── Test-MeetingChatNoAnonymous_8.5.5_E3L1.ps1 │ ├── Test-ModernAuthExchangeOnline_6.5.1_E3L1_IG2_IG3.ps1 │ ├── Test-ModernAuthSharePoint_7.2.1_E3L1_IG2_IG3.ps1 │ ├── Test-NoAnonymousMeetingJoin_8.5.1_E3L2.ps1 │ ├── Test-NoAnonymousMeetingStart_8.5.2_E3L1.ps1 │ ├── Test-NoWhitelistDomains_6.2.2_E3L1.ps1 │ ├── Test-NotifyMalwareInternal_2.1.3_E3L1_IG2_IG3.ps1 │ ├── Test-OneDriveContentRestrictions_7.2.4_E3L2_IG1_IG2_IG3.ps1 │ ├── Test-OneDriveSyncRestrictions_7.3.2_E3L2.ps1 │ ├── Test-OrgOnlyBypassLobby_8.5.3_E3L1_IG3.ps1 │ ├── Test-OrganizersPresent_8.5.6_E3L1.ps1 │ ├── Test-PasswordHashSync_5.1.8.1_E3L1_IG2_IG3.ps1 │ ├── Test-PasswordNeverExpirePolicy_1.3.1_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-ReauthWithCode_7.2.10_E3L1.ps1 │ ├── Test-ReportSecurityInTeams_8.6.1_E3L1.ps1 │ ├── Test-RestrictCustomScripts_7.3.4_E3L1_IG3.ps1 │ ├── Test-RestrictExternalSharing_7.2.3_E3L1_IG1_IG2_IG3.ps1 │ ├── Test-RestrictOutlookAddins_6.3.1_E3L2_IG2_IG3.ps1 │ ├── Test-RestrictStorageProvidersOutlook_6.5.3_E3L2_IG1_IG2_IG3.ps1 │ ├── Test-RestrictTenantCreation_5.1.2.3_E3L1.ps1 │ ├── Test-SafeAttachmentsPolicy_2.1.4_E5L2_IG3.ps1 │ ├── Test-SafeAttachmentsTeams_2.1.5_E5L2_IG1_IG2_IG3.ps1 │ ├── Test-SafeLinksOfficeApps_2.1.1_E5L2_IG1_IG2_IG3.ps1 │ ├── Test-SharePointAADB2B_7.2.2_E3L1.ps1 │ ├── Test-SharePointExternalSharingDomains_7.2.6_E3L2_IG1_IG2_IG3.ps1 │ ├── Test-SharePointGuestsItemSharing_7.2.5_E3L2_IG1_IG2_IG3.ps1 │ ├── Test-SpamPolicyAdminNotify_2.1.6_E3L1_IG2_IG3.ps1 │ ├── Test-TeamsExternalAccess_8.2.1_E3L2.ps1 │ ├── Test-TeamsExternalFileSharing_8.1.1_E3L2_IG1_IG2_IG3.ps1 │ └── Test-Template.ps1 ├── Get-AdminRoleUserLicense.ps1 └── psDoc-master │ ├── ADAuditTasks.md │ ├── LICENSE │ ├── readme.md │ └── src │ ├── out-confluence-markup-template.ps1 │ ├── out-html-template.ps1 │ ├── out-markdown-template.ps1 │ └── psDoc.ps1 ├── source ├── Classes │ ├── CISAuditResult.ps1 │ └── CISAuthenticationParameters.ps1 ├── M365FoundationsCISReport.psd1 ├── M365FoundationsCISReport.psm1 ├── Private │ ├── Assert-ModuleAvailability.ps1 │ ├── Connect-M365Suite.ps1 │ ├── Disconnect-M365Suite.ps1 │ ├── Format-RequiredModuleList.ps1 │ ├── Get-Action.ps1 │ ├── Get-AdminRoleUserAndAssignment.ps1 │ ├── Get-AuditMailboxDetail.ps1 │ ├── Get-CISAadOutput.ps1 │ ├── Get-CISExoOutput.ps1 │ ├── Get-CISMSTeamsOutput.ps1 │ ├── Get-CISMgOutput.ps1 │ ├── Get-CISSpoOutput.ps1 │ ├── Get-ExceededLengthResultDetail.ps1 │ ├── Get-MostCommonWord.ps1 │ ├── Get-PhishPolicyDetail.ps1 │ ├── Get-RequiredModule.ps1 │ ├── Get-TestDefinitionsObject.ps1 │ ├── Get-TestError.ps1 │ ├── Get-UniqueConnection.ps1 │ ├── Get-UrlLine.ps1 │ ├── Initialize-CISAuditResult.ps1 │ ├── Initialize-LargeTestTable.ps1 │ ├── Invoke-TestFunction.ps1 │ ├── Measure-AuditResult.ps1 │ ├── Test-IsAdmin.ps1 │ ├── Test-PhishPolicyCompliance.ps1 │ └── Write-AuditLog.ps1 ├── Public │ ├── Export-M365SecurityAuditTable.ps1 │ ├── Get-AdminRoleUserLicense.ps1 │ ├── Get-MFAStatus.ps1 │ ├── Grant-M365SecurityAuditConsent.ps1 │ ├── Invoke-M365SecurityAudit.ps1 │ ├── New-M365SecurityAuditAuthObject.ps1 │ ├── Remove-RowsWithEmptyCSVStatus.ps1 │ └── Sync-CISExcelAndCsvData.ps1 ├── en-US │ ├── M365FoundationsCISReport-help.xml │ └── about_M365FoundationsCISReport.help.txt ├── helper │ └── TestDefinitions.csv └── tests │ ├── Test-AdministrativeAccountCompliance.ps1 │ ├── Test-AntiPhishingPolicy.ps1 │ ├── Test-AuditDisabledFalse.ps1 │ ├── Test-AuditLogSearch.ps1 │ ├── Test-BlockChannelEmails.ps1 │ ├── Test-BlockMailForwarding.ps1 │ ├── Test-BlockSharedMailboxSignIn.ps1 │ ├── Test-CommonAttachmentFilter.ps1 │ ├── Test-CustomerLockbox.ps1 │ ├── Test-DialInBypassLobby.ps1 │ ├── Test-DisallowInfectedFilesDownload.ps1 │ ├── Test-EnableDKIM.ps1 │ ├── Test-ExternalNoControl.ps1 │ ├── Test-ExternalSharingCalendars.ps1 │ ├── Test-GlobalAdminsCount.ps1 │ ├── Test-GuestAccessExpiration.ps1 │ ├── Test-GuestUsersBiweeklyReview.ps1 │ ├── Test-IdentifyExternalEmail.ps1 │ ├── Test-LinkSharingRestrictions.ps1 │ ├── Test-MailTipsEnabled.ps1 │ ├── Test-MailboxAuditingE3.ps1 │ ├── Test-MailboxAuditingE5.ps1 │ ├── Test-ManagedApprovedPublicGroups.ps1 │ ├── Test-MeetingChatNoAnonymous.ps1 │ ├── Test-ModernAuthExchangeOnline.ps1 │ ├── Test-ModernAuthSharePoint.ps1 │ ├── Test-NoAnonymousMeetingJoin.ps1 │ ├── Test-NoAnonymousMeetingStart.ps1 │ ├── Test-NoWhitelistDomains.ps1 │ ├── Test-NotifyMalwareInternal.ps1 │ ├── Test-OneDriveContentRestrictions.ps1 │ ├── Test-OneDriveSyncRestrictions.ps1 │ ├── Test-OrgOnlyBypassLobby.ps1 │ ├── Test-OrganizersPresent.ps1 │ ├── Test-PasswordHashSync.ps1 │ ├── Test-PasswordNeverExpirePolicy.ps1 │ ├── Test-ReauthWithCode.ps1 │ ├── Test-ReportSecurityInTeams.ps1 │ ├── Test-RestrictCustomScripts.ps1 │ ├── Test-RestrictExternalSharing.ps1 │ ├── Test-RestrictOutlookAddins.ps1 │ ├── Test-RestrictStorageProvidersOutlook.ps1 │ ├── Test-RestrictTenantCreation.ps1 │ ├── Test-SafeAttachmentsPolicy.ps1 │ ├── Test-SafeAttachmentsTeams.ps1 │ ├── Test-SafeLinksOfficeApps.ps1 │ ├── Test-SharePointAADB2B.ps1 │ ├── Test-SharePointExternalSharingDomains.ps1 │ ├── Test-SharePointGuestsItemSharing.ps1 │ ├── Test-SpamPolicyAdminNotify.ps1 │ ├── Test-TeamsExternalAccess.ps1 │ └── Test-TeamsExternalFileSharing.ps1 └── tests ├── QA └── module.tests.ps1 └── Unit ├── Private ├── Assert-ModuleAvailability.tests.ps1 ├── Connect-M365Suite.tests.ps1 ├── Disconnect-M365Suite.tests.ps1 ├── Format-RequiredModuleList.tests.ps1 ├── Get-Action.tests.ps1 ├── Get-AdminRoleUserAndAssignment.tests.ps1 ├── Get-AuditMailboxDetail.tests.ps1 ├── Get-CISAadOutput.tests.ps1 ├── Get-CISExoOutput.tests.ps1 ├── Get-CISMSTeamsOutput.tests.ps1 ├── Get-CISMgOutput.tests.ps1 ├── Get-CISSpoOutput.tests.ps1 ├── Get-ExceededLengthResultDetail.tests.ps1 ├── Get-MostCommonWord.tests.ps1 ├── Get-PhishPolicyDetail.tests.ps1 ├── Get-RequiredModule.tests.ps1 ├── Get-TestDefinitionsObject.tests.ps1 ├── Get-TestError.tests.ps1 ├── Get-UniqueConnection.tests.ps1 ├── Get-UrlLine.tests.ps1 ├── Initialize-CISAuditResult.tests.ps1 ├── Initialize-LargeTestTable.tests.ps1 ├── Invoke-TestFunction.tests.ps1 ├── Measure-AuditResult.tests.ps1 ├── Test-IsAdmin.tests.ps1 ├── Test-PhishPolicyCompliance.tests.ps1 └── Write-AuditLog.tests.ps1 └── Public ├── Export-M365SecurityAuditTable.tests.ps1 ├── Get-MFAStatus.tests.ps1 ├── Grant-M365SecurityAuditConsent.tests.ps1 ├── Invoke-M365SecurityAudit.tests.ps1 └── Remove-RowsWithEmptyCSVStatus.tests.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Needed for publishing of examples, build worker defaults to core.autocrlf=input. 2 | * text eol=autocrlf 3 | 4 | *.mof text eol=crlf 5 | *.sh text eol=lf 6 | *.svg eol=lf 7 | 8 | # Ensure any exe files are treated as binary 9 | *.exe binary 10 | *.jpg binary 11 | *.xl* binary 12 | *.pfx binary 13 | *.png binary 14 | *.dll binary 15 | *.so binary 16 | -------------------------------------------------------------------------------- /.github/workflows/powershell.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # 6 | # https://github.com/microsoft/action-psscriptanalyzer 7 | # For more information on PSScriptAnalyzer in general, see 8 | # https://github.com/PowerShell/PSScriptAnalyzer 9 | 10 | name: PSScriptAnalyzer 11 | 12 | on: 13 | push: 14 | branches: [ "main" ] 15 | pull_request: 16 | branches: [ "main" ] 17 | schedule: 18 | - cron: '33 20 * * 4' 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | build: 25 | permissions: 26 | contents: read # for actions/checkout to fetch code 27 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 28 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 29 | name: PSScriptAnalyzer 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Run PSScriptAnalyzer 35 | uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f 36 | with: 37 | # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options. 38 | # The below set up runs PSScriptAnalyzer to your entire repository and runs some basic security rules. 39 | path: .\source 40 | recurse: true 41 | # Include your own basic security rules. Removing this option will run all the rules 42 | includeRule: '"PSAvoidGlobalAliases", "PSAvoidUsingConvertToSecureStringWithPlainText", "PSAvoidUsingPlainTextForPassword", "PSAvoidUsingInvokeExpression", "PSUseApprovedVerbs", "PSAvoidUsingPositionalParameters", "PSAvoidUsingEmptyCatchBlock", "PSAvoidUsingDeprecatedManifestFields", "PSAvoidUsingUserNameAndPasswordParams", "PSAvoidUsingCmdletAliases"' 43 | 44 | output: results.sarif 45 | 46 | # Upload the SARIF file generated in the previous step 47 | - name: Upload SARIF results file 48 | uses: github/codeql-action/upload-sarif@v2 49 | with: 50 | sarif_file: results.sarif 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | 3 | **.bak 4 | *.local.* 5 | !**/README.md 6 | .kitchen/ 7 | 8 | *.nupkg 9 | *.suo 10 | *.user 11 | *.coverage 12 | .vs 13 | .psproj 14 | .sln 15 | markdownissues.txt 16 | node_modules 17 | package-lock.json 18 | Aligned.xlsx 19 | test-gh1.ps1 20 | ModdedModules/* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Msol" 4 | ] 5 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | next-version: 0.0.1 3 | major-version-bump-message: '(breaking\schange|breaking|major)\b' 4 | minor-version-bump-message: '(adds?|features?|minor)\b' 5 | patch-version-bump-message: '\s?(fix|patch)' 6 | no-bump-message: '\+semver:\s?(none|skip)' 7 | assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' 8 | branches: 9 | master: 10 | tag: preview 11 | regex: ^main$ 12 | pull-request: 13 | tag: PR 14 | feature: 15 | tag: useBranchName 16 | increment: Minor 17 | regex: f(eature(s)?)?[\/-] 18 | source-branches: ['master'] 19 | hotfix: 20 | tag: fix 21 | increment: Patch 22 | regex: (hot)?fix(es)?[\/-] 23 | source-branches: ['master'] 24 | 25 | ignore: 26 | sha: [] 27 | merge-message-formats: {} 28 | 29 | 30 | # feature: 31 | # tag: useBranchName 32 | # increment: Minor 33 | # regex: f(eature(s)?)?[/-] 34 | # source-branches: ['master'] 35 | # hotfix: 36 | # tag: fix 37 | # increment: Patch 38 | # regex: (hot)?fix(es)?[/-] 39 | # source-branches: ['master'] 40 | 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) 4 | 5 | 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/). 6 | 7 | ### You are free to: 8 | 9 | - **Share** — copy and redistribute the material in any medium or format 10 | - **Adapt** — remix, transform, and build upon the material 11 | 12 | The licensor cannot revoke these freedoms as long as you follow the license terms. 13 | 14 | ### Under the following terms: 15 | 16 | - **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. 17 | - **NonCommercial** — You may not use the material for commercial purposes. 18 | - **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 19 | - **No additional restrictions** — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 20 | 21 | ### Notices: 22 | 23 | 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. 24 | 25 | 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. 26 | -------------------------------------------------------------------------------- /RequiredModules.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | PSDependOptions = @{ 3 | AddToPath = $true 4 | Target = 'output\RequiredModules' 5 | Parameters = @{ 6 | Repository = 'PSGallery' 7 | } 8 | } 9 | 10 | InvokeBuild = 'latest' 11 | PSScriptAnalyzer = 'latest' 12 | Pester = 'latest' 13 | ModuleBuilder = 'latest' 14 | ChangelogManagement = 'latest' 15 | Sampler = 'latest' 16 | 'Sampler.GitHubTasks' = 'latest' 17 | 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Resolve-Dependency.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | <# 3 | Default parameter values to be loaded by the Resolve-Dependency.ps1 script (unless set in bound parameters 4 | when calling the script). 5 | #> 6 | 7 | #PSDependTarget = './output/modules' 8 | #Proxy = '' 9 | #ProxyCredential = '$MyCredentialVariable' #TODO: find a way to support credentials in build (resolve variable) 10 | 11 | Gallery = 'PSGallery' 12 | 13 | # To use a private nuget repository change the following to your own feed. The locations must be a Nuget v2 feed due 14 | # to limitation in PowerShellGet v2.x. Example below is for a Azure DevOps Server project-scoped feed. While resolving 15 | # dependencies it will be registered as a trusted repository with the name specified in the property 'Gallery' above, 16 | # unless property 'Name' is provided in the hashtable below, if so it will override the property 'Gallery' above. The 17 | # registered repository will be removed when dependencies has been resolved, unless it was already registered to begin 18 | # with. If repository is registered already but with different URL:s the repository will be re-registered and reverted 19 | # after dependencies has been resolved. Currently only Windows integrated security works with private Nuget v2 feeds 20 | # (or if it is a public feed with no security), it is not possible yet to securely provide other credentials for the feed. 21 | # Private repositories will currently only work using PowerShellGet. 22 | #RegisterGallery = @{ 23 | # #Name = 'MyPrivateFeedName' 24 | # GallerySourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 25 | # GalleryPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 26 | # GalleryScriptSourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 27 | # GalleryScriptPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 28 | # #InstallationPolicy = 'Trusted' 29 | #} 30 | 31 | #AllowOldPowerShellGetModule = $true 32 | #MinimumPSDependVersion = '0.3.0' 33 | AllowPrerelease = $false 34 | WithYAML = $true # Will also bootstrap PowerShell-Yaml to read other config files 35 | 36 | # Enable ModuleFast to resolve dependencies. Requires PowerShell 7.2 or higher. 37 | # If this is not configured or set to $false then PowerShellGet and PackageManagement 38 | # will be used to resolve dependencies. 39 | #UseModuleFast = $true 40 | 41 | # Enable PSResourceGet to resolve dependencies. Requires PowerShell 7.2 or higher. 42 | # If this is not configured or set to $false then PowerShellGet and PackageManagement 43 | # will be used to resolve dependencies. 44 | #UsePSResourceGet = $true 45 | #PSResourceGetVersion = '1.0.0' 46 | #UsePowerShellGetCompatibilityModule = $true 47 | #UsePowerShellGetCompatibilityModuleVersion = '3.0.22-beta22' 48 | } 49 | 50 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 0.1.4 | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Use this section to tell people how to report a vulnerability. 15 | 16 | Tell them where to go, how often they can expect to get an update on a 17 | reported vulnerability, what to expect if the vulnerability is accepted or 18 | declined, etc. 19 | -------------------------------------------------------------------------------- /help/Get-AdminRoleUserLicense.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: M365FoundationsCISReport-help.xml 3 | Module Name: M365FoundationsCISReport 4 | online version: https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-AdminRoleUserLicense 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-AdminRoleUserLicense 9 | 10 | ## SYNOPSIS 11 | Retrieves user licenses and roles for administrative accounts from Microsoft 365 via the Graph API. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-AdminRoleUserLicense [-SkipGraphConnection] [-ProgressAction ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | The Get-AdminRoleUserLicense function connects to Microsoft Graph and retrieves all users who are assigned administrative roles along with their user details and licenses. 21 | This function is useful for auditing and compliance checks to ensure that administrators have appropriate licenses and role assignments. 22 | 23 | ## EXAMPLES 24 | 25 | ### EXAMPLE 1 26 | ``` 27 | Get-AdminRoleUserLicense 28 | ``` 29 | 30 | This example retrieves all administrative role users along with their licenses by connecting to Microsoft Graph using the default scopes. 31 | 32 | ### EXAMPLE 2 33 | ``` 34 | Get-AdminRoleUserLicense -SkipGraphConnection 35 | ``` 36 | 37 | This example retrieves all administrative role users along with their licenses without attempting to connect to Microsoft Graph, assuming that the connection is already established. 38 | 39 | ## PARAMETERS 40 | 41 | ### -SkipGraphConnection 42 | A switch parameter that, when set, skips the connection to Microsoft Graph if already established. 43 | This is useful for batch processing or when used within scripts where multiple calls are made and the connection is managed externally. 44 | 45 | ```yaml 46 | Type: SwitchParameter 47 | Parameter Sets: (All) 48 | Aliases: 49 | 50 | Required: False 51 | Position: Named 52 | Default value: False 53 | Accept pipeline input: False 54 | Accept wildcard characters: False 55 | ``` 56 | 57 | ### -ProgressAction 58 | {{ Fill ProgressAction Description }} 59 | 60 | ```yaml 61 | Type: ActionPreference 62 | Parameter Sets: (All) 63 | Aliases: proga 64 | 65 | Required: False 66 | Position: Named 67 | Default value: None 68 | Accept pipeline input: False 69 | Accept wildcard characters: False 70 | ``` 71 | 72 | ### CommonParameters 73 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 74 | 75 | ## INPUTS 76 | 77 | ### None. You cannot pipe objects to Get-AdminRoleUserLicense. 78 | ## OUTPUTS 79 | 80 | ### PSCustomObject 81 | ### Returns a custom object for each user with administrative roles that includes the following properties: RoleName, UserName, UserPrincipalName, UserId, HybridUser, and Licenses. 82 | ## NOTES 83 | Creation Date: 2024-04-15 84 | Purpose/Change: Initial function development to support Microsoft 365 administrative role auditing. 85 | 86 | ## RELATED LINKS 87 | 88 | [https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-AdminRoleUserLicense](https://criticalsolutionsnetwork.github.io/M365FoundationsCISReport/#Get-AdminRoleUserLicense) 89 | 90 | -------------------------------------------------------------------------------- /help/M365FoundationsCISReport.md: -------------------------------------------------------------------------------- 1 | --- 2 | Module Name: M365FoundationsCISReport 3 | Module Guid: 0d064bfb-d1ce-484b-a173-993b55984dc9 4 | Download Help Link: {{Please enter Link manually}} 5 | Help Version: 1.0.0.0 6 | Locale: en-US 7 | --- 8 | 9 | # M365FoundationsCISReport Module 10 | ## Description 11 | The `M365FoundationsCISReport` module provides a set of cmdlets to audit and report on the security compliance of Microsoft 365 environments based on CIS (Center for Internet Security) benchmarks. It enables administrators to generate detailed reports, sync data with CIS Excel sheets, and perform security audits to ensure compliance. 12 | 13 | ## M365FoundationsCISReport Cmdlets 14 | ### [Export-M365SecurityAuditTable](Export-M365SecurityAuditTable) 15 | Exports M365 security audit results to a CSV file or outputs a specific test result as an object. 16 | 17 | ### [Get-AdminRoleUserLicense](Get-AdminRoleUserLicense) 18 | Retrieves user licenses and roles for administrative accounts from Microsoft 365 via the Graph API. 19 | 20 | ### [Get-MFAStatus](Get-MFAStatus) 21 | Retrieves the MFA (Multi-Factor Authentication) status for Azure Active Directory users. 22 | 23 | ### [Grant-M365SecurityAuditConsent](Grant-M365SecurityAuditConsent) 24 | Grants Microsoft Graph permissions for an auditor. 25 | 26 | ### [Invoke-M365SecurityAudit](Invoke-M365SecurityAudit) 27 | Invokes a security audit for Microsoft 365 environments. 28 | 29 | ### [New-M365SecurityAuditAuthObject](New-M365SecurityAuditAuthObject) 30 | Creates a new CISAuthenticationParameters object for Microsoft 365 authentication. 31 | 32 | ### [Remove-RowsWithEmptyCSVStatus](Remove-RowsWithEmptyCSVStatus) 33 | Removes rows from an Excel worksheet where the 'CSV_Status' column is empty and saves the result to a new file. 34 | 35 | ### [Sync-CISExcelAndCsvData](Sync-CISExcelAndCsvData) 36 | Synchronizes and updates data in an Excel worksheet with new information from a CSV file, including audit dates. 37 | 38 | -------------------------------------------------------------------------------- /help/Remove-RowsWithEmptyCSVStatus.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: M365FoundationsCISReport-help.xml 3 | Module Name: M365FoundationsCISReport 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Remove-RowsWithEmptyCSVStatus 9 | 10 | ## SYNOPSIS 11 | Removes rows from an Excel worksheet where the 'CSV_Status' column is empty and saves the result to a new file. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Remove-RowsWithEmptyCSVStatus [-FilePath] [-WorksheetName] 17 | [-ProgressAction ] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | The Remove-RowsWithEmptyCSVStatus function imports data from a specified worksheet in an Excel file, checks for the presence of the 'CSV_Status' column, and filters out rows where the 'CSV_Status' column is empty. 22 | The filtered data is then exported to a new Excel file with a '-Filtered' suffix added to the original file name. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | ``` 28 | Remove-RowsWithEmptyCSVStatus -FilePath "C:\Reports\Report.xlsx" -WorksheetName "Sheet1" 29 | This command imports data from the "Sheet1" worksheet in the "Report.xlsx" file, removes rows where the 'CSV_Status' column is empty, and saves the filtered data to a new file named "Report-Filtered.xlsx" in the same directory. 30 | ``` 31 | 32 | ## PARAMETERS 33 | 34 | ### -FilePath 35 | The path to the Excel file to be processed. 36 | 37 | ```yaml 38 | Type: String 39 | Parameter Sets: (All) 40 | Aliases: 41 | 42 | Required: True 43 | Position: 1 44 | Default value: None 45 | Accept pipeline input: False 46 | Accept wildcard characters: False 47 | ``` 48 | 49 | ### -WorksheetName 50 | The name of the worksheet within the Excel file to be processed. 51 | 52 | ```yaml 53 | Type: String 54 | Parameter Sets: (All) 55 | Aliases: 56 | 57 | Required: True 58 | Position: 2 59 | Default value: None 60 | Accept pipeline input: False 61 | Accept wildcard characters: False 62 | ``` 63 | 64 | ### -ProgressAction 65 | {{ Fill ProgressAction Description }} 66 | 67 | ```yaml 68 | Type: ActionPreference 69 | Parameter Sets: (All) 70 | Aliases: proga 71 | 72 | Required: False 73 | Position: Named 74 | Default value: None 75 | Accept pipeline input: False 76 | Accept wildcard characters: False 77 | ``` 78 | 79 | ### CommonParameters 80 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 81 | 82 | ## INPUTS 83 | 84 | ## OUTPUTS 85 | 86 | ## NOTES 87 | This function requires the ImportExcel module to be installed. 88 | 89 | ## RELATED LINKS 90 | -------------------------------------------------------------------------------- /helpers/Automation Candidates.md: -------------------------------------------------------------------------------- 1 | # Automation Candidates 2 | 3 | ## 5.1.1.1 (L1) Ensure Security Defaults is disabled on Azure Active Directory 4 | 5 | - `Connect-MgGraph -Scopes "Policy.Read.All"` 6 | - `Get-MgPolicyIdentitySecurityDefaultEnforcementPolicy | ft IsEnabled` 7 | 8 | ## 5.1.2.1 (L1) Ensure 'Per-user MFA' is disabled 9 | 10 | - `Connect-MsolService` 11 | - Commands: 12 | 13 | ```powershell 14 | $UserList = Get-MsolUser -All | Where-Object { $_.UserType -eq 'Member' } 15 | $Report = @() 16 | foreach ($user in $UserList) { 17 | $PerUserMFAState = $null 18 | if ($user.StrongAuthenticationRequirements) { 19 | $PerUserMFAState = $user.StrongAuthenticationRequirements.State 20 | } 21 | else { 22 | $PerUserMFAState = 'Disabled' 23 | } 24 | $obj = [pscustomobject][ordered]@{ 25 | UserPrincipalName = $User.UserPrincipalName 26 | DisplayName = $User.DisplayName 27 | PerUserMFAState = $PerUserMFAState 28 | } 29 | $Report += $obj 30 | } 31 | $Report 32 | ``` 33 | 34 | ## 5.1.3.1 (L1) Ensure a dynamic group for guest users is created 35 | 36 | - `Connect-MgGraph -Scopes "Group.Read.All"` 37 | - Commands: 38 | 39 | ```powershell 40 | $groups = Get-MgGroup | Where-Object { $_.GroupTypes -contains "DynamicMembership" } 41 | $groups | ft DisplayName,GroupTypes,MembershipRule 42 | ``` 43 | 44 | ## 6.1.4 (L1) Ensure 'AuditBypassEnabled' is not enabled on mailboxes 45 | 46 | - `Connect-ExchangeOnline` 47 | - Commands: 48 | 49 | ```powershell 50 | $MBX = Get-MailboxAuditBypassAssociation -ResultSize unlimited 51 | $MBX | where {$_.AuditBypassEnabled -eq $true} | Format-Table Name,AuditBypassEnabled 52 | ``` 53 | -------------------------------------------------------------------------------- /helpers/Build-Help.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1 2 | . .\source\Classes\CISAuditResult.ps1 3 | .\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir docs -template ".\helpers\psDoc-master\src\out-html-template.ps1" 4 | .\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir ".\" -template ".\helpers\psDoc-master\src\out-markdown-template.ps1" -fileName ".\README.md" 5 | 6 | 7 | <# 8 | $ver = "v0.1.28" 9 | git checkout main 10 | git pull origin main 11 | git tag -a $ver -m "Release version $ver bugfix Update" 12 | git push origin $ver 13 | "Fix: PR #37" 14 | git push origin $ver 15 | # git tag -d $ver 16 | #> 17 | 18 | $OutputFolder = ".\help" 19 | $parameters = @{ 20 | Module = "M365FoundationsCISReport" 21 | OutputFolder = $OutputFolder 22 | AlphabeticParamsOrder = $true 23 | WithModulePage = $true 24 | ExcludeDontShow = $true 25 | Encoding = [System.Text.Encoding]::UTF8 26 | } 27 | New-MarkdownHelp @parameters 28 | New-MarkdownAboutHelp -OutputFolder $OutputFolder -AboutName "M365FoundationsCISReport" 29 | 30 | 31 | #### 32 | $parameters = @{ 33 | Path = ".\help" 34 | RefreshModulePage = $true 35 | AlphabeticParamsOrder = $true 36 | UpdateInputOutput = $true 37 | ExcludeDontShow = $true 38 | LogPath = ".\log.txt" 39 | Encoding = [System.Text.Encoding]::UTF8 40 | } 41 | Update-MarkdownHelpModule @parameters -Force 42 | Update-MarkdownHelpModule -Path ".\help" -RefreshModulePage -Force 43 | New-ExternalHelp -Path ".\help" -OutputPath ".\source\en-US" -force 44 | 45 | 46 | 47 | # Install Secret Management 48 | Install-Module -Name "Microsoft.PowerShell.SecretManagement", ` 49 | "SecretManagement.JustinGrote.CredMan" -Scope CurrentUser 50 | 51 | # Register Vault 52 | Register-SecretVault -Name ModuleBuildCreds -ModuleName ` 53 | "SecretManagement.JustinGrote.CredMan" -ErrorAction Stop 54 | 55 | 56 | #Set-Secret -Name "GalleryApiToken" -Vault ModuleBuildCreds 57 | #Set-Secret -Name "GitHubToken" -Vault ModuleBuildCreds 58 | 59 | 60 | $GalleryApiToken = Get-Secret -Name "GalleryApiToken" -Vault ModuleBuildCreds -AsPlainText 61 | $GitHubToken = Get-Secret -Name "GitHubToken" -Vault ModuleBuildCreds -AsPlainText 62 | 63 | $GalleryApiToken 64 | $GitHubToken -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-AntiPhishingPolicy_2.1.7_E5L1_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-AntiPhishingPolicy_2.1.7_E5L1_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | 15 | # Retrieve and validate the anti-phishing policies 16 | $antiPhishPolicies = Get-AntiPhishPolicy 17 | $validatedPolicies = $antiPhishPolicies | Where-Object { 18 | $_.Enabled -eq $true -and 19 | $_.PhishThresholdLevel -ge 2 -and 20 | $_.EnableMailboxIntelligenceProtection -eq $true -and 21 | $_.EnableMailboxIntelligence -eq $true -and 22 | $_.EnableSpoofIntelligence -eq $true 23 | } 24 | 25 | # Check if there is at least one policy that meets the requirements 26 | $isCompliant = $validatedPolicies.Count -gt 0 27 | 28 | # Prepare failure details if policies are not compliant 29 | $failureDetails = if (-not $isCompliant) { 30 | "No anti-phishing policy is fully compliant with CIS benchmark requirements." 31 | } else { 32 | "Compliant Anti-Phish Policy Names: " + ($validatedPolicies.Name -join ', ') 33 | } 34 | 35 | # Create an instance of CISAuditResult and populate it 36 | $auditResult = [CISAuditResult]::new() 37 | $auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" } 38 | $auditResult.ELevel = "E5" 39 | $auditResult.Profile = "L1" 40 | $auditResult.Rec = "2.1.7" 41 | $auditResult.RecDescription = "Ensure that an anti-phishing policy has been created" 42 | $auditResult.CISControlVer = "v8" 43 | $auditResult.CISControl = "9.7" 44 | $auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections" 45 | $auditResult.IG1 = $false 46 | $auditResult.IG2 = $false 47 | $auditResult.IG3 = $true 48 | $auditResult.Result = $isCompliant 49 | $auditResult.Details = $failureDetails 50 | $auditResult.FailureReason = if (-not $isCompliant) { "Anti-phishing policies do not meet CIS benchmark requirements." } else { "N/A" } 51 | 52 | $auditResults += $auditResult 53 | } 54 | 55 | end { 56 | # Return auditResults 57 | return $auditResults 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-AuditDisabledFalse_6.1.1_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-AuditDisabledFalse_6.1.1_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' 15 | # Pass if AuditDisabled is False. Fail otherwise. 16 | $auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled 17 | $auditNotDisabled = -not $auditDisabledConfig.AuditDisabled 18 | 19 | # Create an instance of CISAuditResult and populate it 20 | $auditResult = [CISAuditResult]::new() 21 | $auditResult.Status = if ($auditNotDisabled) { "Pass" } else { "Fail" } 22 | $auditResult.ELevel = "E3" 23 | $auditResult.Profile = "L1" 24 | $auditResult.Rec = "6.1.1" 25 | $auditResult.RecDescription = "Ensure 'AuditDisabled' organizationally is set to 'False'" 26 | $auditResult.CISControlVer = "v8" 27 | $auditResult.CISControl = "8.2" 28 | $auditResult.CISDescription = "Collect Audit Logs" 29 | $auditResult.IG1 = $true 30 | $auditResult.IG2 = $true 31 | $auditResult.IG3 = $true 32 | $auditResult.Result = $auditNotDisabled 33 | $auditResult.Details = if ($auditNotDisabled) { "Audit is not disabled organizationally" } else { "Audit is disabled organizationally" } 34 | $auditResult.FailureReason = if (-not $auditNotDisabled) { "AuditDisabled is set to True" } else { "N/A" } 35 | 36 | $auditResults += $auditResult 37 | } 38 | 39 | end { 40 | # Return auditResults 41 | return $auditResults 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-AuditLogSearch_3.1.1_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-AuditLogSearch_3.1.1_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled 15 | # Pass if UnifiedAuditLogIngestionEnabled is True. Fail otherwise. 16 | $auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled 17 | $auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled 18 | 19 | # Create an instance of CISAuditResult and populate it 20 | $auditResult = [CISAuditResult]::new() 21 | $auditResult.Status = if ($auditLogResult) { "Pass" } else { "Fail" } 22 | $auditResult.ELevel = "E3" 23 | $auditResult.Profile = "L1" 24 | $auditResult.Rec = "3.1.1" 25 | $auditResult.RecDescription = "Ensure Microsoft 365 audit log search is Enabled" 26 | $auditResult.CISControlVer = "v8" 27 | $auditResult.CISControl = "8.2" 28 | $auditResult.CISDescription = "Collect Audit Logs" 29 | $auditResult.IG1 = $true 30 | $auditResult.IG2 = $true 31 | $auditResult.IG3 = $true 32 | $auditResult.Result = $auditLogResult 33 | $auditResult.Details = "UnifiedAuditLogIngestionEnabled: $($auditLogConfig.UnifiedAuditLogIngestionEnabled)" 34 | $auditResult.FailureReason = if (-not $auditLogResult) { "Audit log search is not enabled" } else { "N/A" } 35 | 36 | $auditResults += $auditResult 37 | } 38 | 39 | end { 40 | # Return auditResults 41 | return $auditResults 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-BlockChannelEmails_8.1.2_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-BlockChannelEmails_8.1.2_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.1.2 (L1) Ensure users can't send emails to a channel email address 15 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 16 | 17 | $teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global 18 | $allowEmailIntoChannel = $teamsClientConfig.AllowEmailIntoChannel 19 | 20 | # Create an instance of CISAuditResult and populate it 21 | $auditResult = [CISAuditResult]::new() 22 | $auditResult.CISControlVer = "v8" 23 | $auditResult.CISControl = "0.0" # This control is Explicitly Not Mapped as per the image provided 24 | $auditResult.CISDescription = "Explicitly Not Mapped" 25 | $auditResult.Rec = "8.1.2" 26 | $auditResult.ELevel = "E3" 27 | $auditResult.Profile = "L1" 28 | $auditResult.IG1 = $false # Set based on the benchmark 29 | $auditResult.IG2 = $false # Set based on the benchmark 30 | $auditResult.IG3 = $false # Set based on the benchmark 31 | $auditResult.RecDescription = "Ensure users can't send emails to a channel email address" 32 | $auditResult.Result = -not $allowEmailIntoChannel 33 | $auditResult.Details = "AllowEmailIntoChannel is set to $allowEmailIntoChannel" 34 | $auditResult.FailureReason = if ($allowEmailIntoChannel) { "Emails can be sent to a channel email address" } else { "N/A" } 35 | $auditResult.Status = if (-not $allowEmailIntoChannel) { "Pass" } else { "Fail" } 36 | 37 | $auditResults += $auditResult 38 | } 39 | 40 | end { 41 | # Return auditResults 42 | return $auditResults 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-BlockMailForwarding_6.2.1_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-BlockMailForwarding_6.2.1_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResult = [CISAuditResult]::new() 10 | $auditResult.Rec = "6.2.1" 11 | $auditResult.ELevel = "E3" 12 | $auditResult.Profile = "L1" 13 | $auditResult.CISControlVer = "v8" 14 | $auditResult.CISControl = "0.0" 15 | $auditResult.CISDescription = "Explicitly Not Mapped" 16 | $auditResult.IG1 = $false 17 | $auditResult.IG2 = $false 18 | $auditResult.IG3 = $false 19 | $auditResult.RecDescription = "Ensure all forms of mail forwarding are blocked and/or disabled" 20 | } 21 | 22 | process { 23 | # Verify that no rules are forwarding the email to external domains 24 | $transportRules = Get-TransportRule | Where-Object { $_.RedirectMessageTo -ne $null } 25 | $forwardingBlocked = $transportRules.Count -eq 0 26 | 27 | $auditResult.Result = $forwardingBlocked 28 | $auditResult.Details = if ($transportRules.Count -gt 0) { 29 | $transportRules | ForEach-Object { 30 | "$($_.Name) redirects to $($_.RedirectMessageTo)" 31 | } -join " | " 32 | } else { 33 | "Step 1: No forwarding rules found. Please proceed with Step 2 described in CIS Benchmark." 34 | } 35 | $auditResult.FailureReason = if (-not $forwardingBlocked) { 36 | "Mail forwarding rules found: $($transportRules.Name -join ', ')" 37 | } else { 38 | "N/A" 39 | } 40 | $auditResult.Status = if ($forwardingBlocked) { "Pass" } else { "Fail" } 41 | } 42 | 43 | end { 44 | # Return the result object 45 | return $auditResult 46 | } 47 | } 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-BlockSharedMailboxSignIn_1.2.2_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-BlockSharedMailboxSignIn_1.2.2_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked 15 | # Pass if all shared mailboxes have AccountEnabled set to False. 16 | # Fail if any shared mailbox has AccountEnabled set to True. 17 | 18 | $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox 19 | $sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId } 20 | $enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName } 21 | $allBlocked = $enabledMailboxes.Count -eq 0 22 | 23 | # Create an instance of CISAuditResult and populate it 24 | $auditResult = [CISAuditResult]::new() 25 | $auditResult.CISControlVer = "v8" 26 | $auditResult.CISControl = "0.0" # Control is explicitly not mapped 27 | $auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8" 28 | $auditResult.Rec = "1.2.2" 29 | $auditResult.ELevel = "E3" 30 | $auditResult.Profile = "L1" 31 | $auditResult.IG1 = $false # Control is not mapped, hence IG1 is false 32 | $auditResult.IG2 = $false # Control is not mapped, hence IG2 is false 33 | $auditResult.IG3 = $false # Control is not mapped, hence IG3 is false 34 | $auditResult.RecDescription = "Ensure sign-in to shared mailboxes is blocked" 35 | $auditResult.Result = $allBlocked 36 | $auditResult.Details = "Enabled Mailboxes: $($enabledMailboxes -join ', ')" 37 | $auditResult.FailureReason = if ($allBlocked) { "N/A" } else { "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" } 38 | $auditResult.Status = if ($allBlocked) { "Pass" } else { "Fail" } 39 | 40 | $auditResults += $auditResult 41 | } 42 | 43 | end { 44 | # Return auditResults 45 | return $auditResults 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-CommonAttachmentFilter_2.1.2_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-CommonAttachmentFilter_2.1.2_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled 14 | # Pass if EnableFileFilter is set to True. Fail otherwise. 15 | 16 | $attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter 17 | $result = $attachmentFilter.EnableFileFilter 18 | $details = "File Filter Enabled: $($attachmentFilter.EnableFileFilter)" 19 | $failureReason = if ($result) { "N/A" } else { "Common Attachment Types Filter is disabled" } 20 | $status = if ($result) { "Pass" } else { "Fail" } 21 | 22 | # Create an instance of CISAuditResult and populate it 23 | $auditResult = [CISAuditResult]::new() 24 | $auditResult.Status = $status 25 | $auditResult.ELevel = "E3" 26 | $auditResult.Profile = "L1" 27 | $auditResult.Rec = "2.1.2" 28 | $auditResult.RecDescription = "Ensure the Common Attachment Types Filter is enabled" 29 | $auditResult.CISControlVer = "v8" 30 | $auditResult.CISControl = "9.6" 31 | $auditResult.CISDescription = "Block Unnecessary File Types" 32 | $auditResult.IG1 = $false 33 | $auditResult.IG2 = $true 34 | $auditResult.IG3 = $true 35 | $auditResult.Result = $result 36 | $auditResult.Details = $details 37 | $auditResult.FailureReason = $failureReason 38 | 39 | $auditResults += $auditResult 40 | } 41 | 42 | end { 43 | # Return auditResults 44 | return $auditResults 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-CustomerLockbox_1.3.6_E5L2.ps1: -------------------------------------------------------------------------------- 1 | function Test-CustomerLockbox_1.3.6_E5L2 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # 1.3.6 (L2) Ensure the customer lockbox feature is enabled 14 | $orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled 15 | $customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled 16 | 17 | $auditResult = [CISAuditResult]::new() 18 | $auditResult.Status = if ($customerLockboxEnabled) { "Pass" } else { "Fail" } 19 | $auditResult.ELevel = "E5" 20 | $auditResult.Profile = "L2" 21 | $auditResult.Rec = "1.3.6" 22 | $auditResult.RecDescription = "Ensure the customer lockbox feature is enabled" 23 | $auditResult.CISControlVer = 'v8' 24 | $auditResult.CISControl = "0.0" # As per the snapshot provided, this is explicitly not mapped 25 | $auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8" 26 | $auditResult.IG1 = $false 27 | $auditResult.IG2 = $false 28 | $auditResult.IG3 = $false 29 | $auditResult.Result = $customerLockboxEnabled 30 | $auditResult.Details = "Customer Lockbox Enabled: $customerLockboxEnabled" 31 | $auditResult.FailureReason = if ($customerLockboxEnabled) { "N/A" } else { "Customer lockbox feature is not enabled." } 32 | 33 | $auditResults += $auditResult 34 | } 35 | 36 | end { 37 | # Return auditResults 38 | return $auditResults 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-DialInBypassLobby_8.5.4_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-DialInBypassLobby_8.5.4_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.4 (L1) Ensure users dialing in can't bypass the lobby 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby 19 | $PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 25 | $auditResult.CISDescription = "Explicitly Not Mapped" 26 | $auditResult.Rec = "8.5.4" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L1" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $false # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure users dialing in can't bypass the lobby" 33 | $auditResult.Result = $PSTNBypassDisabled 34 | $auditResult.Details = "AllowPSTNUsersToBypassLobby is set to $($CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby)" 35 | $auditResult.FailureReason = if ($PSTNBypassDisabled) { "N/A" } else { "Users dialing in can bypass the lobby" } 36 | $auditResult.Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-DisallowInfectedFilesDownload_7.3.1_E5L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-DisallowInfectedFilesDownload_7.3.1_E5L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download 15 | $SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload 16 | $isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "10.1" 21 | $auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software" 22 | 23 | $auditResult.Rec = "7.3.1" 24 | $auditResult.ELevel = "E5" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true 29 | $auditResult.RecDescription = "Ensure Office 365 SharePoint infected files are disallowed for download" 30 | 31 | $auditResult.Result = $isDisallowInfectedFileDownloadEnabled 32 | $auditResult.Details = "DisallowInfectedFileDownload: $($SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload)" 33 | $auditResult.FailureReason = if (-not $isDisallowInfectedFileDownloadEnabled) { "Downloading infected files is not disallowed." } else { "N/A" } 34 | $auditResult.Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-EnableDKIM_2.1.9_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-EnableDKIM_2.1.9_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains 15 | # Pass if Enabled is True for all domains. Fail if any domain has Enabled set to False. 16 | $dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled 17 | $dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false 18 | $dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain } 19 | 20 | # Create an instance of CISAuditResult and populate it 21 | $auditResult = [CISAuditResult]::new() 22 | $auditResult.Status = if ($dkimResult) { "Pass" } else { "Fail" } 23 | $auditResult.ELevel = "E3" 24 | $auditResult.Profile = "L1" 25 | $auditResult.Rec = "2.1.9" 26 | $auditResult.RecDescription = "Ensure that DKIM is enabled for all Exchange Online Domains" 27 | $auditResult.CISControlVer = "v8" 28 | $auditResult.CISControl = "9.5" 29 | $auditResult.CISDescription = "Implement DMARC" 30 | $auditResult.IG1 = $false 31 | $auditResult.IG2 = $true 32 | $auditResult.IG3 = $true 33 | $auditResult.Result = $dkimResult 34 | $auditResult.Details = if (-not $dkimResult) { "DKIM not enabled for: $($dkimFailedDomains -join ', ')" } else { "All domains have DKIM enabled" } 35 | $auditResult.FailureReason = if (-not $dkimResult) { "DKIM is not enabled for some domains" } else { "N/A" } 36 | 37 | $auditResults += $auditResult 38 | } 39 | 40 | end { 41 | # Return auditResults 42 | return $auditResults 43 | } 44 | } -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ExternalNoControl_8.5.7_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-ExternalNoControl_8.5.7_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.7 (L1) Ensure external participants can't give or request control 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl 19 | $externalControlRestricted = -not $CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 25 | $auditResult.CISDescription = "Explicitly Not Mapped" 26 | $auditResult.Rec = "8.5.7" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L1" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $false # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure external participants can't give or request control" 33 | $auditResult.Result = $externalControlRestricted 34 | $auditResult.Details = "AllowExternalParticipantGiveRequestControl is set to $($CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl)" 35 | $auditResult.FailureReason = if ($externalControlRestricted) { "N/A" } else { "External participants can give or request control" } 36 | $auditResult.Status = if ($externalControlRestricted) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ExternalSharingCalendars_1.3.3_E3L2_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-ExternalSharingCalendars_1.3.3_E3L2_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated) 15 | $sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' } 16 | 17 | # Check if calendar sharing is disabled in all applicable policies 18 | $isExternalSharingDisabled = $true 19 | $sharingPolicyDetails = @() 20 | foreach ($policy in $sharingPolicies) { 21 | if ($policy.Enabled -eq $true) { 22 | $isExternalSharingDisabled = $false 23 | $sharingPolicyDetails += "$($policy.Name): Enabled" 24 | } 25 | } 26 | 27 | # Create an instance of CISAuditResult and populate it 28 | $auditResult = [CISAuditResult]::new() 29 | $auditResult.Rec = "1.3.3" 30 | $auditResult.RecDescription = "Ensure 'External sharing' of calendars is not available" 31 | $auditResult.ELevel = "E3" 32 | $auditResult.Profile = "L2" 33 | # The following IG values are placeholders. Replace with actual values when known. 34 | $auditResult.IG1 = $false 35 | $auditResult.IG2 = $true 36 | $auditResult.IG3 = $true 37 | $auditResult.CISControlVer = "v8" 38 | # Placeholder for CIS Control, to be replaced with the actual value when available 39 | $auditResult.CISControl = "4.8" 40 | $auditResult.CISDescription = "Uninstall or Disable Unnecessary Services on Enterprise Assets and Software" 41 | $auditResult.Result = $isExternalSharingDisabled 42 | $auditResult.Details = if ($isExternalSharingDisabled) { "Calendar sharing with external users is disabled." } else { "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" } 43 | $auditResult.FailureReason = if ($isExternalSharingDisabled) { "N/A" } else { "Calendar sharing with external users is enabled in one or more policies." } 44 | $auditResult.Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" } 45 | 46 | $auditResults += $auditResult 47 | } 48 | 49 | end { 50 | # Return auditResults 51 | return $auditResults 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-GlobalAdminsCount_1.1.3_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-GlobalAdminsCount_1.1.3_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 1.1.3 (L1) Ensure that between two and four global admins are designated 15 | # Pass if the count of global admins is between 2 and 4. Fail otherwise. 16 | 17 | $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" 18 | $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id 19 | $globalAdminCount = $globalAdmins.AdditionalProperties.Count 20 | $globalAdminUsernames = ($globalAdmins | ForEach-Object { $_.AdditionalProperties["displayName"] }) -join ', ' 21 | 22 | # Create an instance of CISAuditResult and populate it 23 | $auditResult = [CISAuditResult]::new() 24 | $auditResult.CISControlVer = "v8" 25 | $auditResult.CISControl = "5.1" 26 | $auditResult.CISDescription = "Establish and Maintain an Inventory of Accounts" 27 | $auditResult.Rec = "1.1.3" 28 | $auditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.) 29 | $auditResult.Profile = "L1" 30 | $auditResult.IG1 = $true # Set based on the benchmark 31 | $auditResult.IG2 = $true # Set based on the benchmark 32 | $auditResult.IG3 = $true # Set based on the benchmark 33 | $auditResult.RecDescription = "Ensure that between two and four global admins are designated" 34 | $auditResult.Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4 35 | $auditResult.Details = "Count: $globalAdminCount; Users: $globalAdminUsernames" 36 | $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" } 37 | $auditResult.Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" } 38 | 39 | $auditResults += $auditResult 40 | } 41 | 42 | end { 43 | # Return auditResults 44 | return $auditResults 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-GuestAccessExpiration_7.2.9_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-GuestAccessExpiration_7.2.9_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically 15 | $SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays 16 | $isGuestAccessExpirationConfiguredCorrectly = $SPOTenantGuestAccess.ExternalUserExpirationRequired -and $SPOTenantGuestAccess.ExternalUserExpireInDays -le 30 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "0.0" 21 | $auditResult.CISDescription = "Explicitly Not Mapped" 22 | 23 | $auditResult.Rec = "7.2.9" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L1" 26 | $auditResult.IG1 = $false 27 | $auditResult.IG2 = $false 28 | $auditResult.IG3 = $false 29 | $auditResult.RecDescription = "Ensure guest access to a site or OneDrive will expire automatically" 30 | 31 | $auditResult.Result = $isGuestAccessExpirationConfiguredCorrectly 32 | $auditResult.Details = "ExternalUserExpirationRequired: $($SPOTenantGuestAccess.ExternalUserExpirationRequired); ExternalUserExpireInDays: $($SPOTenantGuestAccess.ExternalUserExpireInDays)" 33 | $auditResult.FailureReason = if (-not $isGuestAccessExpirationConfiguredCorrectly) { "Guest access expiration is not configured to automatically expire within 30 days or less." } else { "N/A" } 34 | $auditResult.Status = if ($isGuestAccessExpirationConfiguredCorrectly) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-IdentifyExternalEmail_6.2.3_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-IdentifyExternalEmail_6.2.3_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 6.2.3 (L1) Ensure email from external senders is identified 15 | # Requirement is to have external sender tagging enabled 16 | 17 | $externalInOutlook = Get-ExternalInOutlook 18 | $externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true 19 | 20 | # Create an instance of CISAuditResult and populate it 21 | $auditResult = [CISAuditResult]::new() 22 | $auditResult.Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" } 23 | $auditResult.ELevel = "E3" 24 | $auditResult.Profile = "L1" 25 | $auditResult.Rec = "6.2.3" 26 | $auditResult.RecDescription = "Ensure email from external senders is identified" 27 | $auditResult.CISControlVer = "v8" 28 | $auditResult.CISControl = "0.0" 29 | $auditResult.CISDescription = "Explicitly Not Mapped" 30 | $auditResult.IG1 = $false 31 | $auditResult.IG2 = $false 32 | $auditResult.IG3 = $false 33 | $auditResult.Result = $externalTaggingEnabled 34 | $auditResult.Details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)" 35 | $auditResult.FailureReason = if (-not $externalTaggingEnabled) { "External sender tagging is disabled" } else { "N/A" } 36 | 37 | $auditResults += $auditResult 38 | } 39 | 40 | end { 41 | # Return auditResults 42 | return $auditResults 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-LinkSharingRestrictions_7.2.7_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-LinkSharingRestrictions_7.2.7_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | # Test behavior in prod 6 | ) 7 | 8 | begin { 9 | # Initialization code 10 | . ".\source\Classes\CISAuditResult.ps1" 11 | $auditResult = [CISAuditResult]::new() 12 | } 13 | 14 | process { 15 | # 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive 16 | $SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType 17 | $isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation 18 | 19 | # Populate the auditResult object with the required properties 20 | $auditResult.CISControlVer = "v8" 21 | $auditResult.CISControl = "3.3" 22 | $auditResult.CISDescription = "Configure Data Access Control Lists" 23 | 24 | $auditResult.Rec = "7.2.7" 25 | $auditResult.ELevel = "E3" 26 | $auditResult.Profile = "L1" 27 | $auditResult.IG1 = $true 28 | $auditResult.IG2 = $true 29 | $auditResult.IG3 = $true 30 | $auditResult.RecDescription = "Ensure link sharing is restricted in SharePoint and OneDrive" 31 | 32 | $auditResult.Result = $isLinkSharingRestricted 33 | $auditResult.Details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)" 34 | $auditResult.FailureReason = if (-not $isLinkSharingRestricted) { "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" } else { "N/A" } 35 | $auditResult.Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" } 36 | } 37 | 38 | end { 39 | # Return auditResult 40 | return $auditResult 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-MailTipsEnabled_6.5.2_E3L2.ps1: -------------------------------------------------------------------------------- 1 | function Test-MailTipsEnabled_6.5.2_E3L2 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 6.5.2 (L2) Ensure MailTips are enabled for end users 15 | $orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold 16 | $allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25 17 | $externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled 18 | 19 | # Since there is no direct CIS Control mapping, the control will be set as not applicable. 20 | $auditResult.CISControl = "N/A" 21 | $auditResult.CISControlVer = "v8" 22 | $auditResult.CISDescription = "Explicitly Not Mapped" 23 | 24 | $auditResult.Rec = "6.5.2" 25 | $auditResult.ELevel = "E3" 26 | $auditResult.Profile = "L2" 27 | $auditResult.IG1 = $false 28 | $auditResult.IG2 = $false 29 | $auditResult.IG3 = $false 30 | $auditResult.RecDescription = "Ensure MailTips are enabled for end users" 31 | 32 | $auditResult.Result = $allTipsEnabled -and $externalRecipientsTipsEnabled 33 | $auditResult.Details = "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)" 34 | $auditResult.FailureReason = if (-not $auditResult.Result) { "One or more MailTips settings are not configured as required." } else { "N/A" } 35 | $auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" } 36 | } 37 | 38 | end { 39 | # Return auditResult 40 | return $auditResult 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ManagedApprovedPublicGroups_1.2.1_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-ManagedApprovedPublicGroups_1.2.1_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated) 15 | 16 | $allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility 17 | 18 | # Check if there are public groups and if they are organizationally managed/approved 19 | $auditResult = [CISAuditResult]::new() 20 | $auditResult.CISControlVer = "v8" 21 | $auditResult.CISControl = "3.3" 22 | $auditResult.CISDescription = "Configure Data Access Control Lists" 23 | $auditResult.Rec = "1.2.1" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true # Based on the provided CIS Control image, IG3 is not applicable 29 | $auditResult.RecDescription = "Ensure that only organizationally managed/approved public groups exist" 30 | 31 | if ($null -eq $allGroups -or $allGroups.Count -eq 0) { 32 | $auditResult.Result = $true 33 | $auditResult.Details = "No public groups found." 34 | $auditResult.FailureReason = "N/A" 35 | $auditResult.Status = "Pass" 36 | } 37 | else { 38 | $groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" } 39 | $detailsString = $groupDetails -join ', ' 40 | 41 | $auditResult.Result = $false 42 | $auditResult.Details = "Public groups found: $detailsString" 43 | $auditResult.FailureReason = "There are public groups present that are not organizationally managed/approved." 44 | $auditResult.Status = "Fail" 45 | } 46 | 47 | $auditResults += $auditResult 48 | } 49 | 50 | end { 51 | # Return auditResults 52 | return $auditResults 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-MeetingChatNoAnonymous_8.5.5_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-MeetingChatNoAnonymous_8.5.5_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.5 (L2) Ensure meeting chat does not allow anonymous users 15 | # Name doesn't match profile level in benchmarks either. 16 | 17 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 18 | 19 | $CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType 20 | $chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous' 21 | 22 | # Create an instance of CISAuditResult and populate it 23 | $auditResult = [CISAuditResult]::new() 24 | $auditResult.CISControlVer = "v8" 25 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 26 | $auditResult.CISDescription = "Explicitly Not Mapped" 27 | $auditResult.Rec = "8.5.5" 28 | $auditResult.ELevel = "E3" 29 | $auditResult.Profile = "L1" 30 | $auditResult.IG1 = $false # Set based on the CIS Controls image 31 | $auditResult.IG2 = $false # Set based on the CIS Controls image 32 | $auditResult.IG3 = $false # Set based on the CIS Controls image 33 | $auditResult.RecDescription = "Ensure meeting chat does not allow anonymous users" 34 | $auditResult.Result = $chatAnonDisabled 35 | $auditResult.Details = "MeetingChatEnabledType is set to $($CsTeamsMeetingPolicyChat.MeetingChatEnabledType)" 36 | $auditResult.FailureReason = if ($chatAnonDisabled) { "N/A" } else { "Meeting chat allows anonymous users" } 37 | $auditResult.Status = if ($chatAnonDisabled) { "Pass" } else { "Fail" } 38 | 39 | $auditResults += $auditResult 40 | } 41 | 42 | end { 43 | # Return auditResults 44 | return $auditResults 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ModernAuthExchangeOnline_6.5.1_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-ModernAuthExchangeOnline_6.5.1_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $CISAuditResult = [CISAuditResult]::new() 10 | # Initialization code 11 | } 12 | 13 | process { 14 | try { 15 | # Ensuring the ExchangeOnlineManagement module is available 16 | 17 | 18 | # 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled 19 | $orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | 23 | $CISAuditResult.CISControlVer = "v8" 24 | $CISAuditResult.CISControl = "3.10" 25 | $CISAuditResult.CISDescription = "Encrypt Sensitive Data in Transit" 26 | $CISAuditResult.IG1 = $false # As per CIS Control v8 mapping for IG1 27 | $CISAuditResult.IG2 = $true # As per CIS Control v8 mapping for IG2 28 | $CISAuditResult.IG3 = $true # As per CIS Control v8 mapping for IG3 29 | $CISAuditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.) 30 | $CISAuditResult.Profile = "L1" 31 | $CISAuditResult.Rec = "6.5.1" 32 | $CISAuditResult.RecDescription = "Ensure modern authentication for Exchange Online is enabled (Automated)" 33 | $CISAuditResult.Result = $orgConfig.OAuth2ClientProfileEnabled 34 | $CISAuditResult.Details = $orgConfig | Out-String 35 | $CISAuditResult.FailureReason = if (-not $orgConfig.OAuth2ClientProfileEnabled) { "Modern authentication is disabled" } else { "N/A" } 36 | $CISAuditResult.Status = if ($orgConfig.OAuth2ClientProfileEnabled) { "Pass" } else { "Fail" } 37 | 38 | 39 | } 40 | catch { 41 | Write-Error "An error occurred while testing modern authentication for Exchange Online: $_" 42 | } 43 | } 44 | 45 | end { 46 | # Return auditResults 47 | return $CISAuditResult 48 | } 49 | } 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ModernAuthSharePoint_7.2.1_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-ModernAuthSharePoint_7.2.1_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required 15 | $SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled 16 | $modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.10" 21 | $auditResult.CISDescription = "Encrypt Sensitive Data in Transit" 22 | $auditResult.Rec = "7.2.1" 23 | $auditResult.ELevel = "E3" 24 | $auditResult.Profile = "L1" 25 | $auditResult.IG1 = $false 26 | $auditResult.IG2 = $true 27 | $auditResult.IG3 = $true 28 | $auditResult.RecDescription = "Modern Authentication for SharePoint Applications" 29 | $auditResult.Result = $modernAuthForSPRequired 30 | $auditResult.Details = "LegacyAuthProtocolsEnabled: $($SPOTenant.LegacyAuthProtocolsEnabled)" 31 | $auditResult.FailureReason = if (-not $modernAuthForSPRequired) { "Legacy authentication protocols are enabled" } else { "N/A" } 32 | $auditResult.Status = if ($modernAuthForSPRequired) { "Pass" } else { "Fail" } 33 | } 34 | 35 | end { 36 | # Return auditResult 37 | return $auditResult 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-NoAnonymousMeetingJoin_8.5.1_E3L2.ps1: -------------------------------------------------------------------------------- 1 | function Test-NoAnonymousMeetingJoin_8.5.1_E3L2 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.1 (L2) Ensure anonymous users can't join a meeting 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global 19 | $allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided 25 | $auditResult.CISDescription = "Explicitly Not Mapped" 26 | $auditResult.Rec = "8.5.1" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L2" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $false # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure anonymous users can't join a meeting" 33 | $auditResult.Result = -not $allowAnonymousUsersToJoinMeeting 34 | $auditResult.Details = "AllowAnonymousUsersToJoinMeeting is set to $allowAnonymousUsersToJoinMeeting" 35 | $auditResult.FailureReason = if ($allowAnonymousUsersToJoinMeeting) { "Anonymous users are allowed to join meetings" } else { "N/A" } 36 | $auditResult.Status = if (-not $allowAnonymousUsersToJoinMeeting) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-NoAnonymousMeetingStart_8.5.2_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-NoAnonymousMeetingStart_8.5.2_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting 19 | $anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 25 | $auditResult.CISDescription = "Explicitly Not Mapped" 26 | $auditResult.Rec = "8.5.2" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L1" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $false # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure anonymous users and dial-in callers can't start a meeting" 33 | $auditResult.Result = $anonymousStartDisabled 34 | $auditResult.Details = "AllowAnonymousUsersToStartMeeting is set to $($CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting)" 35 | $auditResult.FailureReason = if ($anonymousStartDisabled) { "N/A" } else { "Anonymous users and dial-in callers can start a meeting" } 36 | $auditResult.Status = if ($anonymousStartDisabled) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-NoWhitelistDomains_6.2.2_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-NoWhitelistDomains_6.2.2_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains 15 | $whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $_.SenderDomainIs -ne $null } 16 | 17 | $auditResult.CISControl = "0.0" 18 | $auditResult.CISControlVer = "v8" 19 | $auditResult.CISDescription = "Explicitly Not Mapped" 20 | 21 | $auditResult.Rec = "6.2.2" 22 | $auditResult.ELevel = "E3" 23 | $auditResult.Profile = "L1" 24 | $auditResult.IG1 = $false 25 | $auditResult.IG2 = $false 26 | $auditResult.IG3 = $false 27 | $auditResult.RecDescription = "Ensure mail transport rules do not whitelist specific domains" 28 | 29 | if ($whitelistedRules) { 30 | $ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') } 31 | $auditResult.Result = $false 32 | $auditResult.Details = "Whitelisted Rules: $($ruleDetails -join '; ')" 33 | $auditResult.FailureReason = "There are transport rules whitelisting specific domains." 34 | $auditResult.Status = "Fail" 35 | } else { 36 | $auditResult.Result = $true 37 | $auditResult.Details = "No transport rules whitelisting specific domains found." 38 | $auditResult.FailureReason = "N/A" 39 | $auditResult.Status = "Pass" 40 | } 41 | } 42 | 43 | end { 44 | # Return auditResult 45 | return $auditResult 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-NotifyMalwareInternal_2.1.3_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-NotifyMalwareInternal_2.1.3_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # Retrieve all 'Custom' malware filter policies and check notification settings 14 | $malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' } 15 | $policiesToReport = @() 16 | 17 | foreach ($policy in $malwareNotifications) { 18 | if ($policy.EnableInternalSenderAdminNotifications -ne $true) { 19 | $policiesToReport += "$($policy.Identity): Notifications Disabled" 20 | } 21 | } 22 | 23 | # Determine the result based on the presence of custom policies without notifications 24 | $result = $policiesToReport.Count -eq 0 25 | $details = if ($result) { "All custom malware policies have notifications enabled." } else { "Misconfigured Policies: $($policiesToReport -join ', ')" } 26 | $failureReason = if ($result) { "N/A" } else { "Some custom policies do not have notifications for internal users sending malware enabled." } 27 | 28 | # Create an instance of CISAuditResult and populate it 29 | $auditResult = [CISAuditResult]::new() 30 | $auditResult.Status = if ($result) { "Pass" } else { "Fail" } 31 | $auditResult.ELevel = "E3" 32 | $auditResult.Profile = "L1" 33 | $auditResult.Rec = "2.1.3" 34 | $auditResult.RecDescription = "Ensure notifications for internal users sending malware is Enabled" 35 | $auditResult.CISControlVer = "v8" 36 | $auditResult.CISControl = "17.5" 37 | $auditResult.CISDescription = "Assign Key Roles and Responsibilities" 38 | $auditResult.IG1 = $false 39 | $auditResult.IG2 = $true 40 | $auditResult.IG3 = $true 41 | $auditResult.Result = $result 42 | $auditResult.Details = $details 43 | $auditResult.FailureReason = $failureReason 44 | 45 | $auditResults += $auditResult 46 | } 47 | 48 | end { 49 | # Return auditResults 50 | return $auditResults 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-OneDriveContentRestrictions_7.2.4_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-OneDriveContentRestrictions_7.2.4_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.4 (L2) Ensure OneDrive content sharing is restricted 15 | $SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability 16 | $isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled' 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.3" 21 | $auditResult.CISDescription = "Configure Data Access Control Lists" 22 | 23 | $auditResult.Rec = "7.2.4" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true 29 | $auditResult.RecDescription = "Ensure OneDrive content sharing is restricted" 30 | 31 | $auditResult.Result = $isOneDriveSharingRestricted 32 | $auditResult.Details = "OneDriveSharingCapability: $($SPOTenant.OneDriveSharingCapability)" 33 | $auditResult.FailureReason = if (-not $isOneDriveSharingRestricted) { "OneDrive content sharing is not restricted to 'Disabled'. Current setting: $($SPOTenant.OneDriveSharingCapability)" } else { "N/A" } 34 | $auditResult.Status = if ($isOneDriveSharingRestricted) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-OneDriveSyncRestrictions_7.3.2_E3L2.ps1: -------------------------------------------------------------------------------- 1 | function Test-OneDriveSyncRestrictions_7.3.2_E3L2 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.3.2 (L2) Ensure OneDrive sync is restricted for unmanaged devices 15 | $SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList 16 | $isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "0.0" 21 | $auditResult.CISDescription = "Explicitly Not Mapped" 22 | 23 | $auditResult.Rec = "7.3.2" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $false 27 | $auditResult.IG2 = $false 28 | $auditResult.IG3 = $false 29 | $auditResult.RecDescription = "Ensure OneDrive sync is restricted for unmanaged devices" 30 | 31 | $auditResult.Result = $isSyncRestricted 32 | $auditResult.Details = "TenantRestrictionEnabled: $($SPOTenantSyncClientRestriction.TenantRestrictionEnabled); AllowedDomainList: $($SPOTenantSyncClientRestriction.AllowedDomainList -join ', ')" 33 | $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" } 34 | $auditResult.Status = if ($isSyncRestricted) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-OrgOnlyBypassLobby_8.5.3_E3L1_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-OrgOnlyBypassLobby_8.5.3_E3L1_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.3 (L1) Ensure only people in my org can bypass the lobby 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $CsTeamsMeetingPolicyLobby = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AutoAdmittedUsers 19 | $lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests' 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "6.8" 25 | $auditResult.CISDescription = "Define and Maintain Role-Based Access Control" 26 | $auditResult.Rec = "8.5.3" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L1" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $true # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure only people in my org can bypass the lobby" 33 | $auditResult.Result = $lobbyBypassRestricted 34 | $auditResult.Details = "AutoAdmittedUsers is set to $($CsTeamsMeetingPolicyLobby.AutoAdmittedUsers)" 35 | $auditResult.FailureReason = if ($lobbyBypassRestricted) { "N/A" } else { "External participants can bypass the lobby" } 36 | $auditResult.Status = if ($lobbyBypassRestricted) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-OrganizersPresent_8.5.6_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-OrganizersPresent_8.5.6_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.5.6 (L2) Ensure only organizers and co-organizers can present 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode 19 | $presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride' 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.CISControlVer = "v8" 24 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 25 | $auditResult.CISDescription = "Explicitly Not Mapped" 26 | $auditResult.Rec = "8.5.6" 27 | $auditResult.ELevel = "E3" 28 | $auditResult.Profile = "L1" 29 | $auditResult.IG1 = $false # Set based on the CIS Controls image 30 | $auditResult.IG2 = $false # Set based on the CIS Controls image 31 | $auditResult.IG3 = $false # Set based on the CIS Controls image 32 | $auditResult.RecDescription = "Ensure only organizers and co-organizers can present" 33 | $auditResult.Result = $presenterRoleRestricted 34 | $auditResult.Details = "DesignatedPresenterRoleMode is set to $($CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode)" 35 | $auditResult.FailureReason = if ($presenterRoleRestricted) { "N/A" } else { "Others besides organizers and co-organizers can present" } 36 | $auditResult.Status = if ($presenterRoleRestricted) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-PasswordHashSync_5.1.8.1_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-PasswordHashSync_5.1.8.1_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments 15 | # Pass if OnPremisesSyncEnabled is True. Fail otherwise. 16 | $passwordHashSync = Get-MgOrganization | Select-Object OnPremisesSyncEnabled 17 | $hashSyncResult = $passwordHashSync.OnPremisesSyncEnabled 18 | 19 | # Create an instance of CISAuditResult and populate it 20 | $auditResult = [CISAuditResult]::new() 21 | $auditResult.Status = if ($hashSyncResult) { "Pass" } else { "Fail" } 22 | $auditResult.ELevel = "E3" 23 | $auditResult.Profile = "L1" 24 | $auditResult.Rec = "5.1.8.1" 25 | $auditResult.RecDescription = "Ensure password hash sync is enabled for hybrid deployments" 26 | $auditResult.CISControlVer = "v8" 27 | $auditResult.CISControl = "6.7" 28 | $auditResult.CISDescription = "Centralize Access Control" 29 | $auditResult.IG1 = $false 30 | $auditResult.IG2 = $true 31 | $auditResult.IG3 = $true 32 | $auditResult.Result = $hashSyncResult 33 | $auditResult.Details = "OnPremisesSyncEnabled: $($passwordHashSync.OnPremisesSyncEnabled)" 34 | $auditResult.FailureReason = if (-not $hashSyncResult) { "Password hash sync for hybrid deployments is not enabled" } else { "N/A" } 35 | 36 | $auditResults += $auditResult 37 | } 38 | 39 | end { 40 | # Return auditResults 41 | return $auditResults 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-PasswordNeverExpirePolicy_1.3.1_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-PasswordNeverExpirePolicy_1.3.1_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory)] 5 | [string]$DomainName # DomainName parameter is now mandatory 6 | ) 7 | 8 | begin { 9 | # Dot source the class script 10 | . ".\source\Classes\CISAuditResult.ps1" 11 | $auditResults = @() 12 | } 13 | 14 | process { 15 | # 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire' 16 | # Pass if PasswordValidityPeriodInDays is 0. 17 | # Fail otherwise. 18 | 19 | $passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object PasswordValidityPeriodInDays 20 | 21 | # Create an instance of CISAuditResult and populate it 22 | $auditResult = [CISAuditResult]::new() 23 | $auditResult.Rec = "1.3.1" 24 | $auditResult.RecDescription = "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire'" 25 | $auditResult.ELevel = "E3" 26 | $auditResult.Profile = "L1" 27 | $auditResult.CISControlVer = "v8" 28 | $auditResult.CISControl = "5.2" 29 | $auditResult.CISDescription = "Use Unique Passwords" 30 | $auditResult.IG1 = $true 31 | $auditResult.IG2 = $true 32 | $auditResult.IG3 = $true # All are true 33 | $auditResult.Result = $passwordPolicy.PasswordValidityPeriodInDays -eq 0 34 | $auditResult.Details = "Validity Period: $($passwordPolicy.PasswordValidityPeriodInDays) days" 35 | $auditResult.FailureReason = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "N/A" } else { "Password expiration is not set to never expire" } 36 | $auditResult.Status = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "Pass" } else { "Fail" } 37 | 38 | $auditResults += $auditResult 39 | } 40 | 41 | end { 42 | # Return auditResults 43 | return $auditResults 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ReauthWithCode_7.2.10_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-ReauthWithCode_7.2.10_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.10 (L1) Ensure reauthentication with verification code is restricted 15 | $SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays 16 | $isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "0.0" 21 | $auditResult.CISDescription = "Explicitly Not Mapped" 22 | 23 | $auditResult.Rec = "7.2.10" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L1" 26 | $auditResult.IG1 = $false 27 | $auditResult.IG2 = $false 28 | $auditResult.IG3 = $false 29 | $auditResult.RecDescription = "Ensure reauthentication with verification code is restricted" 30 | 31 | $auditResult.Result = $isReauthenticationRestricted 32 | $auditResult.Details = "EmailAttestationRequired: $($SPOTenantReauthentication.EmailAttestationRequired); EmailAttestationReAuthDays: $($SPOTenantReauthentication.EmailAttestationReAuthDays)" 33 | $auditResult.FailureReason = if (-not $isReauthenticationRestricted) { "Reauthentication with verification code does not require reauthentication within 15 days or less." } else { "N/A" } 34 | $auditResult.Status = if ($isReauthenticationRestricted) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-ReportSecurityInTeams_8.6.1_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-ReportSecurityInTeams_8.6.1_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.6.1 (L1) Ensure users can report security concerns in Teams 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | # Connect to Exchange Online PowerShell using Connect-ExchangeOnline 18 | 19 | $CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting 20 | $ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled 21 | 22 | $securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and 23 | $ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and 24 | $ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress -and 25 | $ReportSubmissionPolicy.ReportPhishToCustomizedAddress -and 26 | $ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled 27 | 28 | # Create an instance of CISAuditResult and populate it 29 | $auditResult = [CISAuditResult]::new() 30 | $auditResult.CISControlVer = "v8" 31 | $auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided 32 | $auditResult.CISDescription = "Explicitly Not Mapped" 33 | $auditResult.Rec = "8.6.1" 34 | $auditResult.ELevel = "E3" 35 | $auditResult.Profile = "L1" 36 | $auditResult.IG1 = $false # Set based on the CIS Controls image 37 | $auditResult.IG2 = $false # Set based on the CIS Controls image 38 | $auditResult.IG3 = $false # Set based on the CIS Controls image 39 | $auditResult.RecDescription = "Ensure users can report security concerns in Teams" 40 | $auditResult.Result = $securityReportEnabled 41 | $auditResult.Details = "AllowSecurityEndUserReporting: $($CsTeamsMessagingPolicy.AllowSecurityEndUserReporting); " + 42 | "ReportJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportJunkToCustomizedAddress); " + 43 | "ReportNotJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress); " + 44 | "ReportPhishToCustomizedAddress: $($ReportSubmissionPolicy.ReportPhishToCustomizedAddress); " + 45 | "ReportChatMessageToCustomizedAddressEnabled: $($ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled)" 46 | $auditResult.FailureReason = if (-not $securityReportEnabled) { "Users cannot report security concerns in Teams due to one or more incorrect settings" } else { "N/A" } 47 | $auditResult.Status = if ($securityReportEnabled) { "Pass" } else { "Fail" } 48 | 49 | $auditResults += $auditResult 50 | } 51 | 52 | end { 53 | # Return auditResults 54 | return $auditResults 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-RestrictCustomScripts_7.3.4_E3L1_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-RestrictCustomScripts_7.3.4_E3L1_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here if needed 5 | ) 6 | 7 | begin { 8 | # .TODO Test behavior in Prod 9 | # Dot source the class script 10 | . ".\source\Classes\CISAuditResult.ps1" 11 | $auditResults = @() 12 | } 13 | 14 | process { 15 | # CIS 2.7 Ensure custom script execution is restricted on site collections 16 | # Pass if DenyAddAndCustomizePages is set to true. Fail otherwise. 17 | 18 | # Connect to SharePoint Online using Connect-SPOService 19 | 20 | $SPOSitesCustomScript = Get-SPOSite | Select-Object Title, Url, DenyAddAndCustomizePages 21 | $customScriptDisabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -eq 'Enabled' } 22 | $customScriptEnabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -ne 'Enabled' } 23 | $customScriptDisabledResult = $customScriptEnabledSites.Count -eq 0 24 | 25 | # Correctly gathering details for sites with custom scripts enabled 26 | $customScriptEnabledDetails = $customScriptEnabledSites | ForEach-Object { "$($_.Title) ($($_.Url)): Custom Script - $($_.DenyAddAndCustomizePages)" } 27 | 28 | # Create an instance of CISAuditResult and populate it 29 | $auditResult = [CISAuditResult]::new() 30 | $auditResult.CISControlVer = "v8" 31 | $auditResult.CISControl = "2.7" 32 | $auditResult.CISDescription = "Allowlist Authorized Scripts" 33 | $auditResult.Rec = "7.3.4" 34 | $auditResult.ELevel = "E3" 35 | $auditResult.Profile = "L1" 36 | $auditResult.IG1 = $false # Set based on the benchmark 37 | $auditResult.IG2 = $false # Set based on the benchmark 38 | $auditResult.IG3 = $true # Set based on the benchmark 39 | $auditResult.RecDescription = "Ensure custom script execution is restricted on site collections" 40 | $auditResult.Result = $customScriptDisabledResult 41 | $auditResult.Details = if (-not $customScriptDisabledResult) { $customScriptEnabledDetails -join "; " } else { "All site collections have custom script execution restricted" } 42 | $auditResult.FailureReason = if (-not $customScriptDisabledResult) { "The following site collections have custom script execution enabled: " + ($customScriptEnabledDetails -join "; ") } else { "N/A" } 43 | $auditResult.Status = if ($customScriptDisabledResult) { "Pass" } else { "Fail" } 44 | 45 | $auditResults += $auditResult 46 | } 47 | 48 | end { 49 | # Return auditResults 50 | return $auditResults 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-RestrictExternalSharing_7.2.3_E3L1_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-RestrictExternalSharing_7.2.3_E3L1_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.3 (L1) Ensure external content sharing is restricted 15 | $SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability 16 | $isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled') 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.3" 21 | $auditResult.CISDescription = "Configure Data Access Control Lists" 22 | 23 | $auditResult.Rec = "7.2.3" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L1" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true 29 | $auditResult.RecDescription = "Ensure external content sharing is restricted" 30 | 31 | $auditResult.Result = $isRestricted 32 | $auditResult.Details = "SharingCapability: $($SPOTenantSharingCapability.SharingCapability)" 33 | $auditResult.FailureReason = if (-not $isRestricted) { "External content sharing is not adequately restricted. Current setting: $($SPOTenantSharingCapability.SharingCapability)" } else { "N/A" } 34 | $auditResult.Status = if ($isRestricted) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-RestrictStorageProvidersOutlook_6.5.3_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-RestrictStorageProvidersOutlook_6.5.3_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web 15 | $owaPolicies = Get-OwaMailboxPolicy 16 | $allPoliciesRestricted = $owaPolicies | ForEach-Object { $_.AdditionalStorageProvidersAvailable } | ForEach-Object { -not $_ } 17 | 18 | # Create an instance of CISAuditResult and populate it 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.3" 21 | $auditResult.CISDescription = "Configure Data Access Control Lists" 22 | $auditResult.Rec = "6.5.3" 23 | $auditResult.ELevel = "E3" # Based on your environment 24 | $auditResult.Profile = "L2" 25 | $auditResult.IG1 = $true 26 | $auditResult.IG2 = $true 27 | $auditResult.IG3 = $true 28 | $auditResult.RecDescription = "Ensure additional storage providers are restricted in Outlook on the web" 29 | $auditResult.Result = $allPoliciesRestricted 30 | $auditResult.Details = if($allPoliciesRestricted) { 31 | "All OwaMailbox policies restrict AdditionalStorageProvidersAvailable" 32 | } else { 33 | $nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } | Select-Object -ExpandProperty Name 34 | "Non-compliant OwaMailbox policies: $($nonCompliantPolicies -join ', ')" 35 | } 36 | $auditResult.FailureReason = if(-not $allPoliciesRestricted) { "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." } else { "N/A" } 37 | $auditResult.Status = if($allPoliciesRestricted) { "Pass" } else { "Fail" } 38 | } 39 | 40 | end { 41 | # Return auditResult 42 | return $auditResult 43 | } 44 | } 45 | 46 | 47 | # Additional helper functions (if any) 48 | 49 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-RestrictTenantCreation_5.1.2.3_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-RestrictTenantCreation_5.1.2.3_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' 15 | # Pass if AllowedToCreateTenants is False. Fail otherwise. 16 | $tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants 17 | $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants 18 | 19 | # Create an instance of CISAuditResult and populate it 20 | $auditResult = [CISAuditResult]::new() 21 | $auditResult.Status = if ($tenantCreationResult) { "Pass" } else { "Fail" } 22 | $auditResult.ELevel = "E3" 23 | $auditResult.Profile = "L1" 24 | $auditResult.Rec = "5.1.2.3" 25 | $auditResult.RecDescription = "Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'" 26 | $auditResult.CISControlVer = "v8" 27 | $auditResult.CISControl = "0.0" 28 | $auditResult.CISDescription = "Explicitly Not Mapped" 29 | $auditResult.IG1 = $false 30 | $auditResult.IG2 = $false 31 | $auditResult.IG3 = $false 32 | $auditResult.Result = $tenantCreationResult 33 | $auditResult.Details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)" 34 | $auditResult.FailureReason = if (-not $tenantCreationResult) { "Non-admin users can create tenants" } else { "N/A" } 35 | 36 | $auditResults += $auditResult 37 | } 38 | 39 | end { 40 | # Return auditResults 41 | return $auditResults 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SafeAttachmentsPolicy_2.1.4_E5L2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-SafeAttachmentsPolicy_2.1.4_E5L2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # Retrieve all Safe Attachment policies where Enable is set to True 14 | $safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true } 15 | 16 | # If there are any enabled policies, the result is Pass. If not, it's Fail. 17 | $result = $safeAttachmentPolicies -ne $null -and $safeAttachmentPolicies.Count -gt 0 18 | $details = if ($result) { 19 | "Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')" 20 | } else { 21 | "No Safe Attachments Policies are enabled." 22 | } 23 | $failureReason = if ($result) { "N/A" } else { "Safe Attachments policy is not enabled." } 24 | 25 | # Create an instance of CISAuditResult and populate it 26 | $auditResult = [CISAuditResult]::new() 27 | $auditResult.Status = if ($result) { "Pass" } else { "Fail" } 28 | $auditResult.ELevel = "E5" 29 | $auditResult.Profile = "L2" 30 | $auditResult.Rec = "2.1.4" 31 | $auditResult.RecDescription = "Ensure Safe Attachments policy is enabled" 32 | $auditResult.CISControlVer = "v8" 33 | $auditResult.CISControl = "9.7" 34 | $auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections" 35 | $auditResult.IG1 = $false 36 | $auditResult.IG2 = $false 37 | $auditResult.IG3 = $true 38 | $auditResult.Result = $result 39 | $auditResult.Details = $details 40 | $auditResult.FailureReason = $failureReason 41 | 42 | $auditResults += $auditResult 43 | } 44 | 45 | end { 46 | # Return auditResults 47 | return $auditResults 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SafeAttachmentsTeams_2.1.5_E5L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-SafeAttachmentsTeams_2.1.5_E5L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # Retrieve the ATP policies for Office 365 and check Safe Attachments settings 14 | $atpPolicies = Get-AtpPolicyForO365 15 | 16 | # Check if the required ATP policies are enabled 17 | $atpPolicyResult = $atpPolicies | Where-Object { 18 | $_.EnableATPForSPOTeamsODB -eq $true -and 19 | $_.EnableSafeDocs -eq $true -and 20 | $_.AllowSafeDocsOpen -eq $false 21 | } 22 | 23 | # Determine the result based on the ATP policy settings 24 | $result = $null -ne $atpPolicyResult 25 | $details = if ($result) { 26 | "ATP for SharePoint, OneDrive, and Teams is enabled with correct settings." 27 | } else { 28 | "ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings." 29 | } 30 | $failureReason = if ($result) { "N/A" } else { "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." } 31 | 32 | # Create an instance of CISAuditResult and populate it 33 | $auditResult = [CISAuditResult]::new() 34 | $auditResult.Status = if ($result) { "Pass" } else { "Fail" } 35 | $auditResult.ELevel = "E5" 36 | $auditResult.Profile = "L2" 37 | $auditResult.Rec = "2.1.5" 38 | $auditResult.RecDescription = "Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled" 39 | $auditResult.CISControlVer = "v8" 40 | $auditResult.CISControl = "9.7, 10.1" 41 | $auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections, Deploy and Maintain Anti-Malware Software" 42 | $auditResult.IG1 = $true 43 | $auditResult.IG2 = $true 44 | $auditResult.IG3 = $true 45 | $auditResult.Result = $result 46 | $auditResult.Details = $details 47 | $auditResult.FailureReason = $failureReason 48 | 49 | $auditResults += $auditResult 50 | } 51 | 52 | end { 53 | # Return auditResults 54 | return $auditResults 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SharePointAADB2B_7.2.2_E3L1.ps1: -------------------------------------------------------------------------------- 1 | function Test-SharePointAADB2B_7.2.2_E3L1 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled 15 | $SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration 16 | 17 | # Populate the auditResult object with the required properties 18 | $auditResult.CISControlVer = "v8" 19 | $auditResult.CISControl = "0.0" 20 | $auditResult.CISDescription = "Explicitly Not Mapped" 21 | 22 | $auditResult.Rec = "7.2.2" 23 | $auditResult.ELevel = "E3" 24 | $auditResult.Profile = "L1" 25 | $auditResult.IG1 = $false 26 | $auditResult.IG2 = $false 27 | $auditResult.IG3 = $false 28 | $auditResult.RecDescription = "Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled" 29 | 30 | $auditResult.Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration 31 | $auditResult.Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)" 32 | $auditResult.FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled" } else { "N/A" } 33 | $auditResult.Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" } 34 | } 35 | 36 | end { 37 | # Return auditResult 38 | return $auditResult 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SharePointExternalSharingDomains_7.2.6_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-SharePointExternalSharingDomains_7.2.6_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists 15 | $SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList 16 | $isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList' 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.3" 21 | $auditResult.CISDescription = "Configure Data Access Control Lists" 22 | 23 | $auditResult.Rec = "7.2.6" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true 29 | $auditResult.RecDescription = "Ensure SharePoint external sharing is managed through domain whitelist/blacklists" 30 | 31 | $auditResult.Result = $isDomainRestrictionConfigured 32 | $auditResult.Details = "SharingDomainRestrictionMode: $($SPOTenant.SharingDomainRestrictionMode); SharingAllowedDomainList: $($SPOTenant.SharingAllowedDomainList)" 33 | $auditResult.FailureReason = if (-not $isDomainRestrictionConfigured) { "Domain restrictions for SharePoint external sharing are not configured to 'AllowList'. Current setting: $($SPOTenant.SharingDomainRestrictionMode)" } else { "N/A" } 34 | $auditResult.Status = if ($isDomainRestrictionConfigured) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SharePointGuestsItemSharing_7.2.5_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-SharePointGuestsItemSharing_7.2.5_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Define your parameters here 5 | ) 6 | 7 | begin { 8 | # Initialization code 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResult = [CISAuditResult]::new() 11 | } 12 | 13 | process { 14 | # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own 15 | $SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing 16 | $isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing 17 | 18 | # Populate the auditResult object with the required properties 19 | $auditResult.CISControlVer = "v8" 20 | $auditResult.CISControl = "3.3" 21 | $auditResult.CISDescription = "Configure Data Access Control Lists" 22 | 23 | $auditResult.Rec = "7.2.5" 24 | $auditResult.ELevel = "E3" 25 | $auditResult.Profile = "L2" 26 | $auditResult.IG1 = $true 27 | $auditResult.IG2 = $true 28 | $auditResult.IG3 = $true 29 | $auditResult.RecDescription = "Ensure that SharePoint guest users cannot share items they don't own" 30 | 31 | $auditResult.Result = $isGuestResharingPrevented 32 | $auditResult.Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented" 33 | $auditResult.FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own." } else { "N/A" } 34 | $auditResult.Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" } 35 | } 36 | 37 | end { 38 | # Return auditResult 39 | return $auditResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-SpamPolicyAdminNotify_2.1.6_E3L1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-SpamPolicyAdminNotify_2.1.6_E3L1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added if needed 5 | ) 6 | 7 | begin { 8 | . ".\source\Classes\CISAuditResult.ps1" 9 | $auditResults = @() 10 | } 11 | 12 | process { 13 | # Get the default hosted outbound spam filter policy 14 | $hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true } 15 | 16 | # Check if both settings are enabled 17 | $bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail 18 | $notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam 19 | $areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled 20 | 21 | # Prepare failure details if any setting is not enabled 22 | $failureDetails = @() 23 | if (-not $bccSuspiciousOutboundMailEnabled) { 24 | $failureDetails += "BccSuspiciousOutboundMail is not enabled." 25 | } 26 | if (-not $notifyOutboundSpamEnabled) { 27 | $failureDetails += "NotifyOutboundSpam is not enabled." 28 | } 29 | 30 | # Create an instance of CISAuditResult and populate it 31 | $auditResult = [CISAuditResult]::new() 32 | $auditResult.Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" } 33 | $auditResult.ELevel = "E3" 34 | $auditResult.Profile = "L1" 35 | $auditResult.Rec = "2.1.6" 36 | $auditResult.RecDescription = "Ensure Exchange Online Spam Policies are set to notify administrators" 37 | $auditResult.CISControlVer = "v8" 38 | $auditResult.CISControl = "17.5" 39 | $auditResult.CISDescription = "Assign Key Roles and Responsibilities" 40 | $auditResult.IG1 = $false 41 | $auditResult.IG2 = $true 42 | $auditResult.IG3 = $true 43 | $auditResult.Result = $areSettingsEnabled 44 | $auditResult.Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' } 45 | $auditResult.FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" } 46 | 47 | $auditResults += $auditResult 48 | } 49 | 50 | end { 51 | # Return auditResults 52 | return $auditResults 53 | } 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-TeamsExternalAccess_8.2.1_E3L2.ps1: -------------------------------------------------------------------------------- 1 | function Test-TeamsExternalAccess_8.2.1_E3L2 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be defined here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center 15 | 16 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 17 | 18 | $externalAccessConfig = Get-CsTenantFederationConfiguration 19 | 20 | $allowedDomainsLimited = $false 21 | if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) { 22 | $allowedDomainsLimited = $true 23 | } 24 | 25 | # Check if the configurations are as recommended 26 | $isCompliant = -not $externalAccessConfig.AllowTeamsConsumer -and -not $externalAccessConfig.AllowPublicUsers -and (-not $externalAccessConfig.AllowFederatedUsers -or $allowedDomainsLimited) 27 | 28 | # Create an instance of CISAuditResult and populate it 29 | $auditResult = [CISAuditResult]::new() 30 | $auditResult.CISControlVer = "v8" 31 | $auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided 32 | $auditResult.CISDescription = "Explicitly Not Mapped" 33 | $auditResult.Rec = "8.2.1" 34 | $auditResult.ELevel = "E3" 35 | $auditResult.Profile = "L2" 36 | $auditResult.IG1 = $false # Set based on the CIS Controls image 37 | $auditResult.IG2 = $false # Set based on the CIS Controls image 38 | $auditResult.IG3 = $false # Set based on the CIS Controls image 39 | $auditResult.RecDescription = "Ensure 'external access' is restricted in the Teams admin center" 40 | $auditResult.Result = $isCompliant 41 | $auditResult.Details = "AllowTeamsConsumer: $($externalAccessConfig.AllowTeamsConsumer); AllowPublicUsers: $($externalAccessConfig.AllowPublicUsers); AllowFederatedUsers: $($externalAccessConfig.AllowFederatedUsers); AllowedDomains limited: $allowedDomainsLimited" 42 | $auditResult.FailureReason = if (-not $isCompliant) { "One or more external access configurations are not compliant." } else { "N/A" } 43 | $auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" } 44 | 45 | $auditResults += $auditResult 46 | } 47 | 48 | end { 49 | # Return auditResults 50 | return $auditResults 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /helpers/CIS 365 v3.0.0 Controls/Test-TeamsExternalFileSharing_8.1.1_E3L2_IG1_IG2_IG3.ps1: -------------------------------------------------------------------------------- 1 | function Test-TeamsExternalFileSharing_8.1.1_E3L2_IG1_IG2_IG3 { 2 | [CmdletBinding()] 3 | param ( 4 | # Parameters can be added here if needed 5 | ) 6 | 7 | begin { 8 | # Dot source the class script 9 | . ".\source\Classes\CISAuditResult.ps1" 10 | $auditResults = @() 11 | } 12 | 13 | process { 14 | # 8.1.1 (L2) Ensure external file sharing in Teams is enabled for only approved cloud storage services 15 | # Connect to Teams PowerShell using Connect-MicrosoftTeams 16 | 17 | # Assuming that 'approvedProviders' is a list of approved cloud storage service names 18 | # This list must be defined according to your organization's approved cloud storage services 19 | $approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte") 20 | $clientConfig = Get-CsTeamsClientConfiguration 21 | 22 | $isCompliant = $true 23 | $nonCompliantProviders = @() 24 | 25 | foreach ($provider in $approvedProviders) { 26 | if (-not $clientConfig.$provider) { 27 | $isCompliant = $false 28 | $nonCompliantProviders += $provider 29 | } 30 | } 31 | 32 | # Create an instance of CISAuditResult and populate it 33 | $auditResult = [CISAuditResult]::new() 34 | $auditResult.CISControlVer = "v8" 35 | $auditResult.CISControl = "3.3" 36 | $auditResult.CISDescription = "Configure Data Access Control Lists" 37 | $auditResult.Rec = "8.1.1" 38 | $auditResult.ELevel = "E3" 39 | $auditResult.Profile = "L2" 40 | $auditResult.IG1 = $true # Set based on the benchmark 41 | $auditResult.IG2 = $true # Set based on the benchmark 42 | $auditResult.IG3 = $true # Set based on the benchmark 43 | $auditResult.RecDescription = "Ensure external file sharing in Teams is enabled for only approved cloud storage services" 44 | $auditResult.Result = $isCompliant 45 | $auditResult.Details = if (-not $isCompliant) { "Non-approved providers enabled: $($nonCompliantProviders -join ', ')" } else { "All cloud storage services are approved providers" } 46 | $auditResult.FailureReason = if (-not $isCompliant) { "The following non-approved providers are enabled: $($nonCompliantProviders -join ', ')" } else { "N/A" } 47 | $auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" } 48 | 49 | $auditResults += $auditResult 50 | } 51 | 52 | end { 53 | # Return auditResults 54 | return $auditResults 55 | } 56 | } -------------------------------------------------------------------------------- /helpers/Get-AdminRoleUserLicense.ps1: -------------------------------------------------------------------------------- 1 | function Get-AdminRoleUserLicense { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory = $false)] 5 | [bool]$SkipGraphConnection = $false 6 | ) 7 | 8 | # Connect to Microsoft Graph if not skipping connection 9 | if (-not $SkipGraphConnection) { 10 | Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome 11 | } 12 | 13 | $adminRoleUsers = @() 14 | $userIds = @() 15 | $adminroles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { $_.DisplayName -like "*Admin*" } 16 | 17 | foreach ($role in $adminroles) { 18 | $usersInRole = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$($role.Id)'" 19 | 20 | foreach ($user in $usersInRole) { 21 | $userIds += $user.PrincipalId 22 | $userDetails = Get-MgUser -UserId $user.PrincipalId -Property "DisplayName, UserPrincipalName, Id, onPremisesSyncEnabled" 23 | 24 | $adminRoleUsers += [PSCustomObject]@{ 25 | RoleName = $role.DisplayName 26 | UserName = $userDetails.DisplayName 27 | UserPrincipalName = $userDetails.UserPrincipalName 28 | UserId = $userDetails.Id 29 | HybridUser = $userDetails.onPremisesSyncEnabled 30 | Licenses = "" # Placeholder for licenses, to be filled later 31 | } 32 | } 33 | } 34 | 35 | foreach ($userId in $userIds | Select-Object -Unique) { 36 | $licenses = Get-MgUserLicenseDetail -UserId $userId 37 | $licenseList = ($licenses.SkuPartNumber -join '|') 38 | 39 | $adminRoleUsers | Where-Object { $_.UserId -eq $userId } | ForEach-Object { 40 | $_.Licenses = $licenseList 41 | } 42 | } 43 | 44 | return $adminRoleUsers 45 | } -------------------------------------------------------------------------------- /helpers/psDoc-master/ADAuditTasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | Module Name: ADAuditTasks 3 | Module Guid: '7ddb359a-e07f-4be0-b63a-a81f44c61fff' 4 | Download Help Link: https://audittaskshelpfiles.blob.core.windows.net/helpfiles/ 5 | Help Version: 1.0.0.5 6 | Locale: en-US 7 | --- 8 | 9 | # ADAuditTasks Module 10 | ## Description 11 | {{ Fill in the Description }} 12 | 13 | ## ADAuditTasks Cmdlets 14 | ### [Convert-NmapXMLToCSV](Convert-NmapXMLToCSV) 15 | Converts an Nmap XML scan output file to a CSV file. 16 | 17 | ### [Get-ADActiveUserAudit](Get-ADActiveUserAudit) 18 | Gets active but stale AD User accounts that haven't logged in within the last 90 days by default. 19 | 20 | ### [Get-ADHostAudit](Get-ADHostAudit) 21 | Active Directory Server and Workstation Audit with Report export option (Can also be piped to CSV if Report isn't specified). 22 | 23 | ### [Get-ADUserLogonAudit](Get-ADUserLogonAudit) 24 | Retrieves the most recent LastLogon timestamp for a specified Active Directory user 25 | account from all domain controllers and outputs it as a DateTime object. 26 | 27 | ### [Get-ADUserPrivilegeAudit](Get-ADUserPrivilegeAudit) 28 | Produces three object outputs: PrivilegedGroups, AdExtendedRights, and possible service accounts. 29 | 30 | ### [Get-ADUserWildCardAudit](Get-ADUserWildCardAudit) 31 | Takes a search string to find commonly named accounts. 32 | 33 | ### [Get-HostTag](Get-HostTag) 34 | Creates a host name or tag based on predetermined criteria for as many as 999 hosts at a time. 35 | 36 | ### [Get-NetworkAudit](Get-NetworkAudit) 37 | Discovers the local network and runs port scans on all hosts found for specific or default sets of ports, displaying MAC ID vendor info. 38 | 39 | ### [Get-QuickPing](Get-QuickPing) 40 | Performs a quick ping on a range of IP addresses and returns an array of IP addresses 41 | that responded to the ping and an array of IP addresses that failed to respond. 42 | 43 | ### [Get-WebCertAudit](Get-WebCertAudit) 44 | Retrieves the certificate information for a web server. 45 | 46 | ### [Join-CSVFile](Join-CSVFile) 47 | Joins multiple CSV files with the same headers into a single CSV file. 48 | 49 | ### [Merge-ADAuditZip](Merge-ADAuditZip) 50 | Combines multiple audit report files into a single compressed ZIP file. 51 | 52 | ### [Merge-NmapToADHostAudit](Merge-NmapToADHostAudit) 53 | Merges Nmap network audit data with Active Directory host audit data. 54 | 55 | ### [Send-AuditEmail](Send-AuditEmail) 56 | This is a wrapper function for Send-MailKitMessage and takes string arrays as input. 57 | 58 | ### [Submit-FTPUpload](Submit-FTPUpload) 59 | Uploads a file to an FTP server using the WinSCP module. 60 | 61 | -------------------------------------------------------------------------------- /helpers/psDoc-master/LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (MS-PL) 2 | This license governs use of the accompanying software. If you use the software, you 3 | accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the 7 | same meaning here as under U.S. copyright law. 8 | A "contribution" is the original software, or any additions or changes to the software. 9 | A "contributor" is any person that distributes its contribution under this license. 10 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 11 | 12 | 2. Grant of Rights 13 | (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. 14 | (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. 15 | 16 | 3. Conditions and Limitations 17 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 18 | (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. 19 | (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. 20 | (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. 21 | (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. 22 | -------------------------------------------------------------------------------- /helpers/psDoc-master/readme.md: -------------------------------------------------------------------------------- 1 | psDoc is a Powershell Help Document generator. 2 | 3 | ---- 4 | ### Using psDoc ### 5 | 6 | To generate documentation off of your module, simply import your module 7 | 8 | ``` 9 | Import-Module MySpecialModule 10 | ``` 11 | 12 | And generate the documentation 13 | 14 | ``` 15 | .\psDoc.ps1 -moduleName MySpecialModule 16 | ``` 17 | 18 | ### License ### 19 | 20 | [Microsoft Public License (Ms-PL)](https://opensource.org/licenses/MS-PL) 21 | -------------------------------------------------------------------------------- /helpers/psDoc-master/src/psDoc.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [parameter(Mandatory=$true, Position=0)] [string] $moduleName, 3 | [parameter(Mandatory=$false, Position=1)] [string] $template = "./out-html-template.ps1", 4 | [parameter(Mandatory=$false, Position=2)] [string] $outputDir = './help', 5 | [parameter(Mandatory=$false, Position=3)] [string] $fileName = 'index.html' 6 | ) 7 | 8 | function FixString ($in = '', [bool]$includeBreaks = $false){ 9 | if ($in -eq $null) { return } 10 | 11 | $rtn = $in.Replace('&', '&').Replace('<', '<').Replace('>', '>').Trim() 12 | 13 | if($includeBreaks){ 14 | $rtn = $rtn.Replace([Environment]::NewLine, '
') 15 | } 16 | return $rtn 17 | } 18 | 19 | function Update-Progress($name, $action){ 20 | Write-Progress -Activity "Rendering $action for $name" -CurrentOperation "Completed $progress of $totalCommands." -PercentComplete $(($progress/$totalCommands)*100) 21 | } 22 | $i = 0 23 | $commandsHelp = (Get-Command -module $moduleName) | get-help -full | Where-Object {! $_.name.EndsWith('.ps1')} 24 | 25 | foreach ($h in $commandsHelp){ 26 | $cmdHelp = (Get-Command $h.Name) 27 | 28 | # Get any aliases associated with the method 29 | $alias = get-alias -definition $h.Name -ErrorAction SilentlyContinue 30 | if($alias){ 31 | $h | Add-Member Alias $alias 32 | } 33 | 34 | # Parse the related links and assign them to a links hashtable. 35 | if(($h.relatedLinks | Out-String).Trim().Length -gt 0) { 36 | $links = $h.relatedLinks.navigationLink | % { 37 | if($_.uri){ @{name = $_.uri; link = $_.uri; target='_blank'} } 38 | if($_.linkText){ @{name = $_.linkText; link = "#$($_.linkText)"; cssClass = 'psLink'; target='_top'} } 39 | } 40 | $h | Add-Member Links $links 41 | } 42 | 43 | # Add parameter aliases to the object. 44 | foreach($p in $h.parameters.parameter ){ 45 | $paramAliases = ($cmdHelp.parameters.values | where name -like $p.name | select aliases).Aliases 46 | if($paramAliases){ 47 | $p | Add-Member Aliases "$($paramAliases -join ', ')" -Force 48 | } 49 | } 50 | } 51 | 52 | # Create the output directory if it does not exist 53 | if (-Not (Test-Path $outputDir)) { 54 | New-Item -Path $outputDir -ItemType Directory | Out-Null 55 | } 56 | 57 | $totalCommands = $commandsHelp.Count 58 | if (!$totalCommands) { 59 | $totalCommands = 1 60 | } 61 | 62 | $template = Get-Content $template -raw -force 63 | Invoke-Expression $template > "$outputDir\$fileName" 64 | -------------------------------------------------------------------------------- /source/Classes/CISAuditResult.ps1: -------------------------------------------------------------------------------- 1 | class CISAuditResult { 2 | [string]$Status 3 | [string]$ELevel 4 | [string]$ProfileLevel 5 | [bool]$Automated 6 | [string]$Connection 7 | [string]$Rec 8 | [string]$RecDescription 9 | [string]$CISControlVer = 'v8' 10 | [string]$CISControl 11 | [string]$CISDescription 12 | [bool]$IG1 13 | [bool]$IG2 14 | [bool]$IG3 15 | [bool]$Result 16 | [string]$Details 17 | [string]$FailureReason 18 | } 19 | -------------------------------------------------------------------------------- /source/Classes/CISAuthenticationParameters.ps1: -------------------------------------------------------------------------------- 1 | class CISAuthenticationParameters { 2 | [string]$ClientCertThumbPrint 3 | [string]$ClientId 4 | [string]$TenantId 5 | [string]$OnMicrosoftUrl 6 | [string]$SpAdminUrl 7 | 8 | # Constructor with validation 9 | CISAuthenticationParameters( 10 | [string]$ClientCertThumbPrint, 11 | [string]$ClientId, 12 | [string]$TenantId, 13 | [string]$OnMicrosoftUrl, 14 | [string]$SpAdminUrl 15 | ) { 16 | # Validate ClientCertThumbPrint 17 | if (-not $ClientCertThumbPrint -or $ClientCertThumbPrint.Length -ne 40 -or $ClientCertThumbPrint -notmatch '^[0-9a-fA-F]{40}$') { 18 | throw [ArgumentException]::new("ClientCertThumbPrint must be a 40-character hexadecimal string.") 19 | } 20 | # Validate ClientId 21 | if (-not $ClientId -or $ClientId -notmatch '^[0-9a-fA-F\-]{36}$') { 22 | throw [ArgumentException]::new("ClientId must be a valid GUID in the format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.") 23 | } 24 | # Validate TenantId 25 | if (-not $TenantId -or $TenantId -notmatch '^[0-9a-fA-F\-]{36}$') { 26 | throw [ArgumentException]::new("TenantId must be a valid GUID in the format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.") 27 | } 28 | # Validate OnMicrosoftUrl 29 | if (-not $OnMicrosoftUrl -or $OnMicrosoftUrl -notmatch '^[a-zA-Z0-9]+\.onmicrosoft\.com$') { 30 | throw [ArgumentException]::new("OnMicrosoftUrl must be in the format 'example.onmicrosoft.com'.") 31 | } 32 | # Validate SpAdminUrl 33 | if (-not $SpAdminUrl -or $SpAdminUrl -notmatch '^https:\/\/[a-zA-Z0-9\-]+\-admin\.sharepoint\.com$') { 34 | throw [ArgumentException]::new("SpAdminUrl must be in the format 'https://[name]-admin.sharepoint.com'.") 35 | } 36 | # Assign validated properties 37 | $this.ClientCertThumbPrint = $ClientCertThumbPrint 38 | $this.ClientId = $ClientId 39 | $this.TenantId = $TenantId 40 | $this.OnMicrosoftUrl = $OnMicrosoftUrl 41 | $this.SpAdminUrl = $SpAdminUrl 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/M365FoundationsCISReport.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | This file is intentionally left empty. It is must be left here for the module 3 | manifest to refer to. It is recreated during the build process. 4 | #> 5 | 6 | -------------------------------------------------------------------------------- /source/Private/Assert-ModuleAvailability.ps1: -------------------------------------------------------------------------------- 1 | function Assert-ModuleAvailability { 2 | [CmdletBinding()] 3 | [OutputType([void]) ] 4 | param( 5 | [string]$ModuleName, 6 | [string]$RequiredVersion, 7 | [string[]]$SubModules = @() 8 | ) 9 | process { 10 | try { 11 | $module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion } 12 | if ($null -eq $module) { 13 | Write-Verbose "Installing $ModuleName module..." 14 | Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null 15 | } 16 | elseif ($module.Version -lt [version]$RequiredVersion) { 17 | Write-Verbose "Updating $ModuleName module to required version..." 18 | Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null 19 | } 20 | else { 21 | Write-Verbose "$ModuleName module is already at required version or newer." 22 | } 23 | if ($ModuleName -eq "Microsoft.Graph") { 24 | # "Preloading Microsoft.Graph assembly to prevent type-loading issues..." 25 | Write-Verbose "Preloading Microsoft.Graph assembly to prevent type-loading issues..." 26 | try { 27 | # Run a harmless cmdlet to preload the assembly 28 | Get-MgGroup -Top 1 -ErrorAction SilentlyContinue | Out-Null 29 | } 30 | catch { 31 | Write-Verbose "Could not preload Microsoft.Graph assembly. Error: $_" 32 | } 33 | } 34 | if ($SubModules.Count -gt 0) { 35 | foreach ($subModule in $SubModules) { 36 | Write-Verbose "Importing submodule $ModuleName.$subModule..." 37 | Get-Module "$ModuleName.$subModule" | Import-Module -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null 38 | } 39 | } 40 | else { 41 | Write-Verbose "Importing module $ModuleName..." 42 | Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null 43 | } 44 | } 45 | catch { 46 | throw "Assert-ModuleAvailability:`n$_" 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /source/Private/Disconnect-M365Suite.ps1: -------------------------------------------------------------------------------- 1 | function Disconnect-M365Suite { 2 | [OutputType([void])] 3 | param ( 4 | [Parameter(Mandatory)] 5 | [string[]]$RequiredConnections 6 | ) 7 | 8 | # Clean up sessions 9 | try { 10 | if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") { 11 | Write-Verbose "Disconnecting from Exchange Online..." 12 | Disconnect-ExchangeOnline -Confirm:$false | Out-Null 13 | } 14 | } 15 | catch { 16 | Write-Warning "Failed to disconnect from Exchange Online: $_" 17 | } 18 | 19 | try { 20 | if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") { 21 | Write-Verbose "Disconnecting from Azure AD..." 22 | Disconnect-AzureAD | Out-Null 23 | } 24 | } 25 | catch { 26 | Write-Warning "Failed to disconnect from Azure AD: $_" 27 | } 28 | 29 | try { 30 | if ($RequiredConnections -contains "Microsoft Graph") { 31 | Write-Verbose "Disconnecting from Microsoft Graph..." 32 | Disconnect-MgGraph | Out-Null 33 | } 34 | } 35 | catch { 36 | Write-Warning "Failed to disconnect from Microsoft Graph: $_" 37 | } 38 | 39 | try { 40 | if ($RequiredConnections -contains "SPO") { 41 | if (($script:PnpAuth)) { 42 | Write-Verbose "Disconnecting from PnPOnline..." 43 | Disconnect-PnPOnline | Out-Null 44 | } 45 | else { 46 | Write-Verbose "Disconnecting from SharePoint Online..." 47 | Disconnect-SPOService | Out-Null 48 | } 49 | } 50 | } 51 | catch { 52 | Write-Warning "Failed to disconnect from SharePoint Online: $_" 53 | } 54 | 55 | try { 56 | if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") { 57 | Write-Verbose "Disconnecting from Microsoft Teams..." 58 | Disconnect-MicrosoftTeams | Out-Null 59 | } 60 | } 61 | catch { 62 | Write-Warning "Failed to disconnect from Microsoft Teams: $_" 63 | } 64 | Write-Verbose "All necessary sessions have been disconnected." 65 | } -------------------------------------------------------------------------------- /source/Private/Format-RequiredModuleList.ps1: -------------------------------------------------------------------------------- 1 | function Format-RequiredModuleList { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [System.Object[]]$RequiredModules 7 | ) 8 | 9 | $requiredModulesFormatted = "" 10 | foreach ($module in $RequiredModules) { 11 | if ($module.SubModules -and $module.SubModules.Count -gt 0) { 12 | $subModulesFormatted = $module.SubModules -join ', ' 13 | $requiredModulesFormatted += "$($module.ModuleName) (SubModules: $subModulesFormatted), " 14 | } else { 15 | $requiredModulesFormatted += "$($module.ModuleName), " 16 | } 17 | } 18 | return $requiredModulesFormatted.TrimEnd(", ") 19 | } 20 | -------------------------------------------------------------------------------- /source/Private/Get-AdminRoleUserAndAssignment.ps1: -------------------------------------------------------------------------------- 1 | function Get-AdminRoleUserAndAssignment { 2 | [CmdletBinding()] 3 | param () 4 | 5 | $result = @{} 6 | 7 | # Get the DisplayNames of all admin roles 8 | $adminRoleNames = (Get-MgDirectoryRole | Where-Object { $null -ne $_.RoleTemplateId }).DisplayName 9 | 10 | # Get Admin Roles 11 | $adminRoles = Get-MgRoleManagementDirectoryRoleDefinition | Where-Object { ($adminRoleNames -contains $_.DisplayName) -and ($_.DisplayName -ne "Directory Synchronization Accounts") } 12 | 13 | foreach ($role in $adminRoles) { 14 | Write-Verbose "Processing role: $($role.DisplayName)" 15 | $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "roleDefinitionId eq '$($role.Id)'" 16 | 17 | foreach ($assignment in $roleAssignments) { 18 | Write-Verbose "Processing role assignment for principal ID: $($assignment.PrincipalId)" 19 | $userDetails = Get-MgUser -UserId $assignment.PrincipalId -Property "DisplayName, UserPrincipalName, Id, OnPremisesSyncEnabled" -ErrorAction SilentlyContinue 20 | 21 | if ($userDetails) { 22 | Write-Verbose "Retrieved user details for: $($userDetails.UserPrincipalName)" 23 | $licenses = Get-MgUserLicenseDetail -UserId $assignment.PrincipalId -ErrorAction SilentlyContinue 24 | 25 | if (-not $result[$role.DisplayName]) { 26 | $result[$role.DisplayName] = @() 27 | } 28 | $result[$role.DisplayName] += [PSCustomObject]@{ 29 | AssignmentId = $assignment.Id 30 | UserDetails = $userDetails 31 | Licenses = $licenses 32 | } 33 | } 34 | } 35 | } 36 | 37 | return $result 38 | } 39 | -------------------------------------------------------------------------------- /source/Private/Get-AuditMailboxDetail.ps1: -------------------------------------------------------------------------------- 1 | function Get-AuditMailboxDetail { 2 | [cmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory = $true)] 5 | [String]$Details, 6 | [Parameter(Mandatory = $true)] 7 | [String]$Version 8 | ) 9 | process { 10 | switch ($Version) { 11 | "6.1.2" { [string]$VersionText = "No M365 E3 licenses found."} 12 | "6.1.3" { [string]$VersionText = "No M365 E5 licenses found."} 13 | } 14 | if ($details -ne $VersionText ) { 15 | $csv = $details | ConvertFrom-Csv -Delimiter '|' 16 | } 17 | else { 18 | $csv = $null 19 | } 20 | if ($null -ne $csv) { 21 | foreach ($row in $csv) { 22 | $row.AdminActionsMissing = (Get-Action -AbbreviatedActions $row.AdminActionsMissing.Split(',') -ReverseActionType Admin -Version $Version) -join ',' 23 | $row.DelegateActionsMissing = (Get-Action -AbbreviatedActions $row.DelegateActionsMissing.Split(',') -ReverseActionType Delegate -Version $Version ) -join ',' 24 | $row.OwnerActionsMissing = (Get-Action -AbbreviatedActions $row.OwnerActionsMissing.Split(',') -ReverseActionType Owner -Version $Version ) -join ',' 25 | } 26 | $newObjectDetails = $csv 27 | } 28 | else { 29 | $newObjectDetails = $details 30 | } 31 | return $newObjectDetails 32 | } 33 | } -------------------------------------------------------------------------------- /source/Private/Get-CISAadOutput.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This is a sample Private function only visible within the module. 4 | .DESCRIPTION 5 | This sample function is not exported to the module and only return the data passed as parameter. 6 | .EXAMPLE 7 | $null = Get-Get-CISAadOutput -PrivateData 'NOTHING TO SEE HERE' 8 | .PARAMETER PrivateData 9 | The PrivateData parameter is what will be returned without transformation. 10 | #> 11 | function Get-CISAadOutput { 12 | [cmdletBinding()] 13 | param( 14 | [Parameter(Mandatory = $true)] 15 | [String]$Rec 16 | ) 17 | begin { 18 | # Begin Block # 19 | <# 20 | # Tests 21 | 1.2.2 22 | # Test number 23 | $testNumbers ="1.2.2" 24 | #> 25 | } 26 | process { 27 | switch ($Rec) { 28 | '1.2.2' { 29 | # Test-BlockSharedMailboxSignIn.ps1 30 | $users = Get-AzureADUser 31 | } 32 | default { throw "No match found for test: $Rec" } 33 | } 34 | } 35 | end { 36 | Write-Verbose "Get-CISAadOutput: Retuning data for Rec: $Rec" 37 | return $users 38 | } 39 | } # end function Get-CISAadOutput 40 | -------------------------------------------------------------------------------- /source/Private/Get-ExceededLengthResultDetail.ps1: -------------------------------------------------------------------------------- 1 | function Get-ExceededLengthResultDetail { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory = $true, ParameterSetName = 'UpdateArray')] 5 | [Parameter(Mandatory = $true, ParameterSetName = 'ReturnExceedingTests')] 6 | [object[]]$AuditResults, 7 | 8 | [Parameter(Mandatory = $true, ParameterSetName = 'UpdateArray')] 9 | [Parameter(Mandatory = $true, ParameterSetName = 'ReturnExceedingTests')] 10 | [string[]]$TestNumbersToCheck, 11 | 12 | [Parameter(Mandatory = $false, ParameterSetName = 'UpdateArray')] 13 | [string[]]$ExportedTests, 14 | 15 | [Parameter(Mandatory = $true, ParameterSetName = 'ReturnExceedingTests')] 16 | [switch]$ReturnExceedingTestsOnly, 17 | 18 | [int]$DetailsLengthLimit = 30000, 19 | 20 | [Parameter(Mandatory = $true, ParameterSetName = 'UpdateArray')] 21 | [int]$PreviewLineCount = 50 22 | ) 23 | 24 | $exceedingTests = @() 25 | $updatedResults = @() 26 | 27 | for ($i = 0; $i -lt $AuditResults.Count; $i++) { 28 | $auditResult = $AuditResults[$i] 29 | if ($auditResult.Rec -in $TestNumbersToCheck) { 30 | if ($auditResult.Details.Length -gt $DetailsLengthLimit) { 31 | if ($ReturnExceedingTestsOnly) { 32 | $exceedingTests += $auditResult.Rec 33 | } else { 34 | $previewLines = ($auditResult.Details -split '\r?\n' | Select-Object -First $PreviewLineCount) -join "`n" 35 | $message = "The test result is too large to be exported to CSV. Use the audit result and the export function for full output.`n`nPreview:`n$previewLines" 36 | 37 | if ($ExportedTests -contains $auditResult.Rec) { 38 | Write-Information "The test result for $($auditResult.Rec) is too large for CSV and was included in the export. Check the exported files." 39 | $auditResult.Details = $message 40 | } else { 41 | $auditResult.Details = $message 42 | } 43 | } 44 | } 45 | } 46 | $updatedResults += $auditResult 47 | } 48 | 49 | if ($ReturnExceedingTestsOnly) { 50 | return $exceedingTests 51 | } else { 52 | return $updatedResults 53 | } 54 | } -------------------------------------------------------------------------------- /source/Private/Get-MostCommonWord.ps1: -------------------------------------------------------------------------------- 1 | function Get-MostCommonWord { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [string[]]$InputStrings 7 | ) 8 | 9 | # Combine all strings into one large string 10 | $allText = $InputStrings -join ' ' 11 | 12 | # Split the large string into words 13 | $words = $allText -split '\s+' 14 | 15 | # Group words and count occurrences 16 | $wordGroups = $words | Group-Object | Sort-Object Count -Descending 17 | 18 | # Return the most common word if it occurs at least 3 times 19 | if ($wordGroups.Count -gt 0 -and $wordGroups[0].Count -ge 3) { 20 | return $wordGroups[0].Name 21 | } else { 22 | return $null 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/Private/Get-PhishPolicyDetail.ps1: -------------------------------------------------------------------------------- 1 | function Get-PhishPolicyDetail { 2 | param ( 3 | [Parameter(Mandatory = $true)] 4 | [pscustomobject]$policy, 5 | 6 | [Parameter(Mandatory = $true)] 7 | [bool]$isCompliant 8 | ) 9 | 10 | return "Policy: $($policy.Identity)`n" + 11 | "Enabled: $($policy.Enabled)`n" + 12 | "PhishThresholdLevel: $($policy.PhishThresholdLevel)`n" + 13 | "MailboxIntelligenceProtection: $($policy.EnableMailboxIntelligenceProtection)`n" + 14 | "MailboxIntelligence: $($policy.EnableMailboxIntelligence)`n" + 15 | "SpoofIntelligence: $($policy.EnableSpoofIntelligence)`n" + 16 | "TargetedUsersToProtect: $($policy.TargetedUsersToProtect -join ', ')`n" + 17 | "IsCompliant: $isCompliant" 18 | } -------------------------------------------------------------------------------- /source/Private/Get-RequiredModule.ps1: -------------------------------------------------------------------------------- 1 | function Get-RequiredModule { 2 | [CmdletBinding(DefaultParameterSetName = 'AuditFunction')] 3 | [OutputType([System.Object[]])] 4 | param ( 5 | [Parameter(Mandatory = $true, ParameterSetName = 'AuditFunction')] 6 | [switch]$AuditFunction, 7 | [Parameter(Mandatory = $true, ParameterSetName = 'SyncFunction')] 8 | [switch]$SyncFunction 9 | ) 10 | switch ($PSCmdlet.ParameterSetName) { 11 | 'AuditFunction' { 12 | if (($script:PnpAuth)) { 13 | return @( 14 | @{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0"; SubModules = @() }, 15 | @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModules = @("DeviceManagement", "Users", "Identity.DirectoryManagement", "Identity.SignIns") }, 16 | @{ ModuleName = "PnP.PowerShell"; RequiredVersion = "2.5.0"; SubModules = @() }, 17 | @{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0"; SubModules = @() } 18 | ) 19 | } 20 | else { 21 | return @( 22 | @{ ModuleName = "ExchangeOnlineManagement"; RequiredVersion = "3.3.0"; SubModules = @() }, 23 | @{ ModuleName = "Microsoft.Graph"; RequiredVersion = "2.4.0"; SubModules = @("DeviceManagement", "Users", "Identity.DirectoryManagement", "Identity.SignIns") }, 24 | @{ ModuleName = "Microsoft.Online.SharePoint.PowerShell"; RequiredVersion = "16.0.24009.12000"; SubModules = @() }, 25 | @{ ModuleName = "MicrosoftTeams"; RequiredVersion = "5.5.0"; SubModules = @() } 26 | ) 27 | } 28 | } 29 | 'SyncFunction' { 30 | return @( 31 | @{ ModuleName = "ImportExcel"; RequiredVersion = "7.8.9"; SubModules = @() } 32 | ) 33 | } 34 | default { 35 | throw "Please specify either -AuditFunction or -SyncFunction switch." 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /source/Private/Get-TestDefinitionsObject.ps1: -------------------------------------------------------------------------------- 1 | function Get-TestDefinitionsObject { 2 | [CmdletBinding()] 3 | [OutputType([object[]])] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [object[]]$TestDefinitions, 7 | 8 | [Parameter(Mandatory = $true)] 9 | [string]$ParameterSetName, 10 | 11 | [string]$ELevel, 12 | [string]$ProfileLevel, 13 | [string[]]$IncludeRecommendation, 14 | [string[]]$SkipRecommendation 15 | ) 16 | 17 | Write-Verbose "Initial test definitions count: $($TestDefinitions.Count)" 18 | 19 | switch ($ParameterSetName) { 20 | 'ELevelFilter' { 21 | Write-Verbose "Applying ELevelFilter" 22 | if ($null -ne $ELevel -and $null -ne $ProfileLevel) { 23 | Write-Verbose "Filtering on ELevel = $ELevel and ProfileLevel = $ProfileLevel" 24 | $TestDefinitions = $TestDefinitions | Where-Object { 25 | $_.ELevel -eq $ELevel -and $_.ProfileLevel -eq $ProfileLevel 26 | } 27 | } 28 | elseif ($null -ne $ELevel) { 29 | Write-Verbose "Filtering on ELevel = $ELevel" 30 | $TestDefinitions = $TestDefinitions | Where-Object { 31 | $_.ELevel -eq $ELevel 32 | } 33 | } 34 | elseif ($null -ne $ProfileLevel) { 35 | Write-Verbose "Filtering on ProfileLevel = $ProfileLevel" 36 | $TestDefinitions = $TestDefinitions | Where-Object { 37 | $_.ProfileLevel -eq $ProfileLevel 38 | } 39 | } 40 | } 41 | 'IG1Filter' { 42 | Write-Verbose "Applying IG1Filter" 43 | $TestDefinitions = $TestDefinitions | Where-Object { $_.IG1 -eq 'TRUE' } 44 | } 45 | 'IG2Filter' { 46 | Write-Verbose "Applying IG2Filter" 47 | $TestDefinitions = $TestDefinitions | Where-Object { $_.IG2 -eq 'TRUE' } 48 | } 49 | 'IG3Filter' { 50 | Write-Verbose "Applying IG3Filter" 51 | $TestDefinitions = $TestDefinitions | Where-Object { $_.IG3 -eq 'TRUE' } 52 | } 53 | 'RecFilter' { 54 | Write-Verbose "Applying RecFilter" 55 | $TestDefinitions = $TestDefinitions | Where-Object { $IncludeRecommendation -contains $_.Rec } 56 | } 57 | 'SkipRecFilter' { 58 | Write-Verbose "Applying SkipRecFilter" 59 | $TestDefinitions = $TestDefinitions | Where-Object { $SkipRecommendation -notcontains $_.Rec } 60 | } 61 | } 62 | 63 | Write-Verbose "Filtered test definitions count: $($TestDefinitions.Count)" 64 | return $TestDefinitions 65 | } 66 | -------------------------------------------------------------------------------- /source/Private/Get-TestError.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | <# 4 | .SYNOPSIS 5 | This is a sample Private function only visible within the module. 6 | 7 | .DESCRIPTION 8 | This sample function is not exported to the module and only return the data passed as parameter. 9 | 10 | .EXAMPLE 11 | $null = Get-TestError -PrivateData 'NOTHING TO SEE HERE' 12 | 13 | .PARAMETER PrivateData 14 | The PrivateData parameter is what will be returned without transformation. 15 | 16 | #> 17 | 18 | function Get-TestError { 19 | [cmdletBinding()] 20 | param ( 21 | $LastError, 22 | $recnum 23 | ) 24 | # Retrieve the description from the test definitions 25 | $testDefinition = $script:TestDefinitionsObject | Where-Object { $_.Rec -eq $recnum } 26 | $description = if ($testDefinition) { $testDefinition.RecDescription } else { "Description not found" } 27 | $script:FailedTests.Add([PSCustomObject]@{ Rec = $recnum; Description = $description; Error = $LastError }) 28 | # Call Initialize-CISAuditResult with error parameters 29 | $auditResult = Initialize-CISAuditResult -Rec $recnum -Failure 30 | Write-Verbose "An error occurred during the test $recnum`: `n$LastError" -Verbose 31 | return $auditResult 32 | } 33 | 34 | -------------------------------------------------------------------------------- /source/Private/Get-UniqueConnection.ps1: -------------------------------------------------------------------------------- 1 | function Get-UniqueConnection { 2 | [CmdletBinding()] 3 | [OutputType([string[]])] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [string[]]$Connections 7 | ) 8 | 9 | $uniqueConnections = @() 10 | 11 | if ($Connections -contains "Microsoft Graph" -or $Connections -contains "AzureAD | EXO | Microsoft Graph" -or $Connections -contains "EXO | Microsoft Graph") { 12 | $uniqueConnections += "Microsoft Graph" 13 | } 14 | if ($Connections -contains "EXO" -or $Connections -contains "AzureAD | EXO" -or $Connections -contains "Microsoft Teams | EXO" -or $Connections -contains "AzureAD | EXO | Microsoft Graph") { 15 | $uniqueConnections += "EXO" 16 | } 17 | if ($Connections -contains "SPO") { 18 | $uniqueConnections += "SPO" 19 | } 20 | if ($Connections -contains "Microsoft Teams" -or $Connections -contains "Microsoft Teams | EXO") { 21 | $uniqueConnections += "Microsoft Teams" 22 | } 23 | 24 | return $uniqueConnections | Sort-Object -Unique 25 | } 26 | -------------------------------------------------------------------------------- /source/Private/Get-UrlLine.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This is a sample Private function only visible within the module. 4 | 5 | .DESCRIPTION 6 | This sample function is not exported to the module and only return the data passed as parameter. 7 | 8 | .EXAMPLE 9 | $null = Get-UrlLine -PrivateData 'NOTHING TO SEE HERE' 10 | 11 | .PARAMETER PrivateData 12 | The PrivateData parameter is what will be returned without transformation. 13 | #> 14 | function Get-UrlLine { 15 | [cmdletBinding()] 16 | [OutputType([string])] 17 | param ( 18 | [Parameter(Mandatory=$true)] 19 | [string]$Output 20 | ) 21 | # Split the output into lines 22 | $Lines = $Output -split "`n" 23 | # Iterate over each line 24 | foreach ($Line in $Lines) { 25 | # If the line starts with 'https', return it 26 | if ($Line.StartsWith('https')) { 27 | return $Line.Trim() 28 | } 29 | } 30 | # If no line starts with 'https', return an empty string 31 | return $null 32 | } -------------------------------------------------------------------------------- /source/Private/Initialize-CISAuditResult.ps1: -------------------------------------------------------------------------------- 1 | function Initialize-CISAuditResult { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | param ( 5 | [Parameter(Mandatory = $true)] 6 | [string]$Rec, 7 | 8 | [Parameter(Mandatory = $true, ParameterSetName = 'Full')] 9 | [bool]$Result, 10 | 11 | [Parameter(Mandatory = $true, ParameterSetName = 'Full')] 12 | [string]$Status, 13 | 14 | [Parameter(Mandatory = $true, ParameterSetName = 'Full')] 15 | [string]$Details, 16 | 17 | [Parameter(Mandatory = $true, ParameterSetName = 'Full')] 18 | [string]$FailureReason, 19 | 20 | [Parameter(ParameterSetName = 'Error')] 21 | [switch]$Failure 22 | ) 23 | 24 | # Import the test definitions CSV file 25 | $testDefinitions = $script:TestDefinitionsObject 26 | 27 | # Find the row that matches the provided recommendation (Rec) 28 | $testDefinition = $testDefinitions | Where-Object { $_.Rec -eq $Rec } 29 | 30 | if (-not $testDefinition) { 31 | throw "Test definition for recommendation '$Rec' not found." 32 | } 33 | 34 | # Create an instance of CISAuditResult and populate it 35 | $auditResult = [CISAuditResult]::new() 36 | $auditResult.Rec = $Rec 37 | $auditResult.ELevel = $testDefinition.ELevel 38 | $auditResult.ProfileLevel = $testDefinition.ProfileLevel 39 | $auditResult.IG1 = [bool]::Parse($testDefinition.IG1) 40 | $auditResult.IG2 = [bool]::Parse($testDefinition.IG2) 41 | $auditResult.IG3 = [bool]::Parse($testDefinition.IG3) 42 | $auditResult.RecDescription = $testDefinition.RecDescription 43 | $auditResult.CISControl = $testDefinition.CISControl 44 | $auditResult.CISDescription = $testDefinition.CISDescription 45 | $auditResult.Automated = [bool]::Parse($testDefinition.Automated) 46 | $auditResult.Connection = $testDefinition.Connection 47 | $auditResult.CISControlVer = 'v8' 48 | 49 | if ($PSCmdlet.ParameterSetName -eq 'Full') { 50 | $auditResult.Result = $Result 51 | $auditResult.Status = $Status 52 | $auditResult.Details = $Details 53 | $auditResult.FailureReason = $FailureReason 54 | } elseif ($PSCmdlet.ParameterSetName -eq 'Error') { 55 | $auditResult.Result = $false 56 | $auditResult.Status = 'Fail' 57 | $auditResult.Details = "An error occurred while processing the test." 58 | $auditResult.FailureReason = "Initialization error: Failed to process the test." 59 | } 60 | 61 | return $auditResult 62 | } 63 | -------------------------------------------------------------------------------- /source/Private/Initialize-LargeTestTable.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This function generates a large table with the specified number of lines. 4 | .DESCRIPTION 5 | This function generates a large table with the specified number of lines. The table has a header and each line has the same format. 6 | .EXAMPLE 7 | Initialize-LargeTestTable -lineCount 1000 8 | .PARAMETER lineCount 9 | The number of lines to generate. 10 | .INPUTS 11 | System.Int32 12 | .OUTPUTS 13 | System.String 14 | .NOTES 15 | The function is intended for testing purposes. 16 | #> 17 | function Initialize-LargeTestTable { 18 | [cmdletBinding()] 19 | [OutputType([string])] 20 | param( 21 | [Parameter()] 22 | [int]$lineCount = 1000 # Number of lines to generate 23 | ) 24 | process { 25 | $header = "UserPrincipalName|AuditEnabled|AdminActionsMissing|DelegateActionsMissing|OwnerActionsMissing" 26 | $lineTemplate = "user{0}@contosonorthwind.net|True|FB,CP,MV|FB,MV|ML,MV,CR" 27 | # Generate the header and lines 28 | $lines = @($header) 29 | for ($i = 1; $i -le $lineCount; $i++) { 30 | $lines += [string]::Format($lineTemplate, $i) 31 | } 32 | $output = $lines -join "`n" 33 | Write-Host "Details character count: $($output.Length)" 34 | return $output 35 | } 36 | } -------------------------------------------------------------------------------- /source/Private/Invoke-TestFunction.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-TestFunction { 2 | [OutputType([CISAuditResult[]])] 3 | param ( 4 | [Parameter(Mandatory = $true)] 5 | [PSObject]$FunctionFile, 6 | [Parameter(Mandatory = $false)] 7 | [string]$DomainName, 8 | [Parameter(Mandatory = $false)] 9 | [string[]]$ApprovedCloudStorageProviders, 10 | [Parameter(Mandatory = $false)] 11 | [string[]]$ApprovedFederatedDomains 12 | ) 13 | 14 | $functionName = $FunctionFile.BaseName 15 | $functionCmd = Get-Command -Name $functionName 16 | 17 | # Check if the test function needs DomainName parameter 18 | $paramList = @{} 19 | if ('DomainName' -in $functionCmd.Parameters.Keys) { 20 | $paramList.DomainName = $DomainName 21 | } 22 | if ('ApprovedCloudStorageProviders' -in $functionCmd.Parameters.Keys) { 23 | $paramList.ApprovedCloudStorageProviders = $ApprovedCloudStorageProviders 24 | } 25 | if ('ApprovedFederatedDomains' -in $functionCmd.Parameters.Keys) { 26 | $paramList.ApprovedFederatedDomains = $ApprovedFederatedDomains 27 | } 28 | # Use splatting to pass parameters 29 | Write-Verbose "Running $functionName..." 30 | try { 31 | $result = & $functionName @paramList 32 | # Assuming each function returns an array of CISAuditResult or a single CISAuditResult 33 | return $result 34 | } 35 | catch { 36 | Write-Error "An error occurred during the test $recnum`:: $_" 37 | $script:FailedTests.Add([PSCustomObject]@{ Test = $functionName; Error = $_ }) 38 | 39 | # Call Initialize-CISAuditResult with error parameters 40 | $auditResult = Initialize-CISAuditResult -Rec $functionName -Failure 41 | return $auditResult 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/Private/Measure-AuditResult.ps1: -------------------------------------------------------------------------------- 1 | function Measure-AuditResult { 2 | [OutputType([void])] 3 | param ( 4 | [Parameter(Mandatory = $true)] 5 | [System.Collections.ArrayList]$AllAuditResults, 6 | 7 | [Parameter(Mandatory = $false)] 8 | [System.Collections.ArrayList]$FailedTests 9 | ) 10 | 11 | # Calculate the total number of tests 12 | $totalTests = $AllAuditResults.Count 13 | 14 | # Calculate the number of passed tests 15 | $passedTests = $AllAuditResults.ToArray() | Where-Object { $_.Result -eq $true } | Measure-Object | Select-Object -ExpandProperty Count 16 | 17 | # Calculate the pass percentage 18 | $passPercentage = if ($totalTests -eq 0) { 0 } else { [math]::Round(($passedTests / $totalTests) * 100, 2) } 19 | 20 | # Display the pass percentage to the user 21 | Write-Information "Audit completed. $passedTests out of $totalTests tests passed." 22 | Write-Information "Your passing percentage is $passPercentage%." 23 | 24 | # Display details of failed tests 25 | if ($FailedTests.Count -gt 0) { 26 | Write-Verbose "The following tests failed to complete:" 27 | foreach ($failedTest in $FailedTests) { 28 | Write-Verbose "Test: $($failedTest.Test)" 29 | Write-Verbose "Error: $($failedTest.Error)" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/Private/Test-IsAdmin.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsAdmin { 2 | <# 3 | .SYNOPSIS 4 | Checks if the current user is an administrator on the machine. 5 | .DESCRIPTION 6 | This private function returns a Boolean value indicating whether 7 | the current user has administrator privileges on the machine. 8 | It does this by creating a new WindowsPrincipal object, passing 9 | in a WindowsIdentity object representing the current user, and 10 | then checking if that principal is in the Administrator role. 11 | .INPUTS 12 | None. 13 | .OUTPUTS 14 | Boolean. Returns True if the current user is an administrator, and False otherwise. 15 | .EXAMPLE 16 | PS C:\> Test-IsAdmin 17 | True 18 | #> 19 | 20 | # Create a new WindowsPrincipal object for the current user and check if it is in the Administrator role 21 | (New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) 22 | } -------------------------------------------------------------------------------- /source/Private/Test-PhishPolicyCompliance.ps1: -------------------------------------------------------------------------------- 1 | function Test-PhishPolicyCompliance { 2 | param ($policy) 3 | return ($policy.Enabled -eq $true -and 4 | $policy.PhishThresholdLevel -ge 2 -and 5 | $policy.EnableMailboxIntelligenceProtection -eq $true -and 6 | $policy.EnableMailboxIntelligence -eq $true -and 7 | $policy.EnableSpoofIntelligence -eq $true) 8 | } -------------------------------------------------------------------------------- /source/Public/Remove-RowsWithEmptyCSVStatus.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Removes rows from an Excel worksheet where the 'CSV_Status' column is empty and saves the result to a new file. 4 | .DESCRIPTION 5 | The Remove-RowsWithEmptyCSVStatus function imports data from a specified worksheet in an Excel file, checks for the presence of the 'CSV_Status' column, and filters out rows where the 'CSV_Status' column is empty. The filtered data is then exported to a new Excel file with a '-Filtered' suffix added to the original file name. 6 | .PARAMETER FilePath 7 | The path to the Excel file to be processed. 8 | .PARAMETER WorksheetName 9 | The name of the worksheet within the Excel file to be processed. 10 | .EXAMPLE 11 | PS C:\> Remove-RowsWithEmptyCSVStatus -FilePath "C:\Reports\Report.xlsx" -WorksheetName "Sheet1" 12 | This command imports data from the "Sheet1" worksheet in the "Report.xlsx" file, removes rows where the 'CSV_Status' column is empty, and saves the filtered data to a new file named "Report-Filtered.xlsx" in the same directory. 13 | .NOTES 14 | This function requires the ImportExcel module to be installed. 15 | #> 16 | function Remove-RowsWithEmptyCSVStatus { 17 | [CmdletBinding()] 18 | param ( 19 | [Parameter(Mandatory = $true)] 20 | [string]$FilePath, 21 | 22 | [Parameter(Mandatory = $true)] 23 | [string]$WorksheetName 24 | ) 25 | # Import the Excel file 26 | $ExcelData = Import-Excel -Path $FilePath -WorksheetName $WorksheetName 27 | # Check if CSV_Status column exists 28 | if (-not $ExcelData.PSObject.Properties.Match("CSV_Status")) { 29 | throw "CSV_Status column not found in the worksheet." 30 | } 31 | # Filter rows where CSV_Status is not empty 32 | $FilteredData = $ExcelData | Where-Object { $null -ne $_.CSV_Status -and $_.CSV_Status -ne '' } 33 | # Get the original file name and directory 34 | $OriginalFileName = [System.IO.Path]::GetFileNameWithoutExtension($FilePath) 35 | $Directory = [System.IO.Path]::GetDirectoryName($FilePath) 36 | # Create a new file name for the filtered data 37 | $NewFileName = "$OriginalFileName-Filtered.xlsx" 38 | $NewFilePath = Join-Path -Path $Directory -ChildPath $NewFileName 39 | # Export the filtered data to a new Excel file 40 | $FilteredData | Export-Excel -Path $NewFilePath -WorksheetName $WorksheetName -Show 41 | Write-Output "Filtered Excel file created at $NewFilePath" 42 | } -------------------------------------------------------------------------------- /source/tests/Test-AuditDisabledFalse.ps1: -------------------------------------------------------------------------------- 1 | function Test-AuditDisabledFalse { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | # Aligned 5 | param ( 6 | # Parameters can be added if needed 7 | ) 8 | begin { 9 | # Dot source the class script if necessary 10 | #. .\source\Classes\CISAuditResult.ps1 11 | # Conditions for 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' 12 | # 13 | # Validate test for a pass: 14 | # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. 15 | # - Specific conditions to check: 16 | # - Condition A: The `AuditDisabled` organizational setting is set to `False` in the Microsoft 365 admin center. 17 | # - Condition B: Using PowerShell, the `AuditDisabled` property in the organization's configuration is set to `False`. 18 | # - Condition C: Ensure mailbox auditing is enabled by default at the organizational level. 19 | # 20 | # Validate test for a fail: 21 | # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. 22 | # - Specific conditions to check: 23 | # - Condition A: The `AuditDisabled` organizational setting is set to `True` in the Microsoft 365 admin center. 24 | # - Condition B: Using PowerShell, the `AuditDisabled` property in the organization's configuration is set to `True`. 25 | # - Condition C: Mailbox auditing is not enabled by default at the organizational level. 26 | # Initialization code, if needed 27 | $recnum = "6.1.1" 28 | Write-Verbose "Running Test-AuditDisabledFalse for $recnum..." 29 | } 30 | process { 31 | try { 32 | # 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False' 33 | # Retrieve the AuditDisabled configuration (Condition B) 34 | $auditNotDisabled = Get-CISExoOutput -Rec $recnum 35 | # Prepare failure reasons and details based on compliance 36 | $failureReasons = if (-not $auditNotDisabled) { 37 | "AuditDisabled is set to True" # Condition A Fail 38 | } 39 | else { 40 | "N/A" 41 | } 42 | $details = if ($auditNotDisabled) { 43 | "Audit is not disabled organizationally" # Condition C Pass 44 | } 45 | else { 46 | "Audit is disabled organizationally" # Condition C Fail 47 | } 48 | # Create and populate the CISAuditResult object 49 | $params = @{ 50 | Rec = $recnum 51 | Result = $auditNotDisabled 52 | Status = if ($auditNotDisabled) { "Pass" } else { "Fail" } 53 | Details = $details 54 | FailureReason = $failureReasons 55 | } 56 | $auditResult = Initialize-CISAuditResult @params 57 | } 58 | catch { 59 | $LastError = $_ 60 | $auditResult = Get-TestError -LastError $LastError -recnum $recnum 61 | } 62 | } 63 | end { 64 | # Return the audit result 65 | return $auditResult 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /source/tests/Test-GuestUsersBiweeklyReview.ps1: -------------------------------------------------------------------------------- 1 | function Test-GuestUsersBiweeklyReview { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | param ( 5 | # Aligned 6 | # Define your parameters here if needed 7 | ) 8 | 9 | begin { 10 | # Dot source the class script if necessary 11 | #. .\source\Classes\CISAuditResult.ps1 12 | 13 | # Initialization code, if needed 14 | $recnum = "1.1.4" 15 | } 16 | 17 | process { 18 | try { 19 | # 1.1.4 (L1) Ensure Guest Users are reviewed at least biweekly 20 | 21 | 22 | # Retrieve guest users from Microsoft Graph 23 | # Connect-MgGraph -Scopes "User.Read.All" 24 | $guestUsers = Get-MgUser -All -Filter "UserType eq 'Guest'" 25 | 26 | # Prepare failure reasons and details based on compliance 27 | $failureReasons = if ($guestUsers) { 28 | "Guest users present: $($guestUsers.Count)" 29 | } 30 | else { 31 | "N/A" 32 | } 33 | 34 | $details = if ($guestUsers) { 35 | $auditCommand = "Get-MgUser -All -Property UserType,UserPrincipalName | Where {`$_.UserType -ne 'Member'} | Format-Table UserPrincipalName, UserType" 36 | "Manual review required. To list guest users, run: `"$auditCommand`"." 37 | } 38 | else { 39 | "No guest users found." 40 | } 41 | 42 | # Create and populate the CISAuditResult object 43 | $params = @{ 44 | Rec = $recnum 45 | Result = -not $guestUsers 46 | Status = if ($guestUsers) { "Fail" } else { "Pass" } 47 | Details = $details 48 | FailureReason = $failureReasons 49 | } 50 | $auditResult = Initialize-CISAuditResult @params 51 | } 52 | catch { 53 | $LastError = $_ 54 | $auditResult = Get-TestError -LastError $LastError -recnum $recnum 55 | } 56 | } 57 | 58 | end { 59 | # Return the audit result 60 | return $auditResult 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /source/tests/Test-RestrictTenantCreation.ps1: -------------------------------------------------------------------------------- 1 | function Test-RestrictTenantCreation { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | param ( 5 | # Aligned 6 | # Parameters can be added if needed 7 | ) 8 | begin { 9 | # Dot source the class script if necessary 10 | #. .\source\Classes\CISAuditResult.ps1 11 | # Initialization code, if needed 12 | $recnum = "5.1.2.3" 13 | Write-Verbose "Starting Test-RestrictTenantCreation with Rec: $recnum" 14 | <# 15 | Conditions for 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' 16 | Validate test for a pass: 17 | - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. 18 | - Specific conditions to check: 19 | - Condition A: Restrict non-admin users from creating tenants is set to 'Yes' in the Azure AD and Entra administration portal. 20 | - Condition B: Using PowerShell, the setting for restricting non-admin users from creating tenants is set to 'Yes'. 21 | Validate test for a fail: 22 | - Confirm that the failure conditions in the automated test are consistent with the manual audit results. 23 | - Specific conditions to check: 24 | - Condition A: Restrict non-admin users from creating tenants is not set to 'Yes' in the Azure AD and Entra administration portal. 25 | - Condition B: Using PowerShell, the setting for restricting non-admin users from creating tenants is not set to 'Yes'. 26 | #> 27 | } 28 | process { 29 | try { 30 | # 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes' 31 | # Retrieve the tenant creation policy 32 | $tenantCreationPolicy = Get-CISMgOutput -Rec $recnum 33 | $tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants 34 | # Prepare failure reasons and details based on compliance 35 | $failureReasons = if ($tenantCreationResult) { 36 | "N/A" 37 | } 38 | else { 39 | "Non-admin users can create tenants" 40 | } 41 | $details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)" 42 | # Create and populate the CISAuditResult object 43 | $params = @{ 44 | Rec = $recnum 45 | Result = $tenantCreationResult 46 | Status = if ($tenantCreationResult) { "Pass" } else { "Fail" } 47 | Details = $details 48 | FailureReason = $failureReasons 49 | } 50 | $auditResult = Initialize-CISAuditResult @params 51 | } 52 | catch { 53 | $LastError = $_ 54 | $auditResult = Get-TestError -LastError $LastError -recnum $recnum 55 | } 56 | } 57 | end { 58 | # Return the audit result 59 | return $auditResult 60 | } 61 | } -------------------------------------------------------------------------------- /source/tests/Test-SharePointAADB2B.ps1: -------------------------------------------------------------------------------- 1 | function Test-SharePointAADB2B { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | param ( 5 | # Aligned 6 | # Define your parameters here 7 | ) 8 | begin { 9 | # Conditions for 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled 10 | # 11 | # Validate test for a pass: 12 | # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. 13 | # - Specific conditions to check: 14 | # - Condition A: Ensure the `EnableAzureADB2BIntegration` property is set to `True` for the SharePoint tenant. 15 | # - Condition B: Verify that the SharePoint and OneDrive integration with Azure AD B2B is active. 16 | # - Condition C: Ensure that guest accounts are managed in Azure AD and subject to access policies. 17 | # 18 | # Validate test for a fail: 19 | # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. 20 | # - Specific conditions to check: 21 | # - Condition A: The `EnableAzureADB2BIntegration` property is set to `False` for the SharePoint tenant. 22 | # - Condition B: The integration between SharePoint, OneDrive, and Azure AD B2B is not active. 23 | # - Condition C: Guest accounts are not managed in Azure AD and are not subject to access policies. 24 | # Dot source the class script if necessary 25 | #. .\source\Classes\CISAuditResult.ps1 26 | # Initialization code, if needed 27 | $recnum = "7.2.2" 28 | Write-Verbose "Running Test-SharePointAADB2B for $recnum..." 29 | } 30 | process { 31 | try { 32 | # 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled 33 | # $SPOTenantAzureADB2B Mock Object 34 | <# 35 | $SPOTenantAzureADB2B = [PSCustomObject]@{ 36 | EnableAzureADB2BIntegration = $false 37 | } 38 | #> 39 | $SPOTenantAzureADB2B = Get-CISSpoOutput -Rec $recnum 40 | # Populate the auditResult object with the required properties 41 | $params = @{ 42 | Rec = $recnum 43 | Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration 44 | Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" } 45 | Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)" 46 | FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled. The following command can be used to enable:`nSet-SPOTenant -EnableAzureADB2BIntegration `$true" } else { "N/A" } 47 | } 48 | $auditResult = Initialize-CISAuditResult @params 49 | } 50 | catch { 51 | $LastError = $_ 52 | $auditResult = Get-TestError -LastError $LastError -recnum $recnum 53 | } 54 | } 55 | end { 56 | # Return auditResult 57 | return $auditResult 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/tests/Test-SharePointGuestsItemSharing.ps1: -------------------------------------------------------------------------------- 1 | function Test-SharePointGuestsItemSharing { 2 | [CmdletBinding()] 3 | [OutputType([CISAuditResult])] 4 | param ( 5 | # Aligned 6 | # Define your parameters here 7 | ) 8 | begin { 9 | # Dot source the class script if necessary 10 | #. .\source\Classes\CISAuditResult.ps1 11 | # Initialization code, if needed 12 | $recnum = "7.2.5" 13 | Write-Verbose "Running Test-SharePointGuestsItemSharing for $recnum..." 14 | # Conditions for 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own 15 | # 16 | # Validate test for a pass: 17 | # - Confirm that the automated test results align with the manual audit steps outlined in the CIS benchmark. 18 | # - Specific conditions to check: 19 | # - Condition A: The SharePoint setting "PreventExternalUsersFromResharing" is set to `True` using PowerShell. 20 | # - Condition B: The SharePoint admin center setting "Allow guests to share items they don't own" is unchecked. 21 | # - Condition C: Ensure that external users cannot re-share items they don't own. 22 | # 23 | # Validate test for a fail: 24 | # - Confirm that the failure conditions in the automated test are consistent with the manual audit results. 25 | # - Specific conditions to check: 26 | # - Condition A: The SharePoint setting "PreventExternalUsersFromResharing" is set to `False` using PowerShell. 27 | # - Condition B: The SharePoint admin center setting "Allow guests to share items they don't own" is checked. 28 | # - Condition C: Ensure that external users can re-share items they don't own. 29 | } 30 | process { 31 | try { 32 | # 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own 33 | # $SPOTenant Mock Object 34 | <# 35 | $SPOTenant = [PSCustomObject]@{ 36 | PreventExternalUsersFromResharing = $false 37 | } 38 | #> 39 | $SPOTenant = Get-CISSpoOutput -Rec $recnum 40 | $isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing 41 | # Populate the auditResult object with the required properties 42 | $params = @{ 43 | Rec = $recnum 44 | Result = $isGuestResharingPrevented 45 | Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" } 46 | Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented" 47 | FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own. To prevent external users from resharing content they don't own,`nuse the following command:`nSet-SPOTenant -PreventExternalUsersFromResharing `$True" } else { "N/A" } 48 | } 49 | $auditResult = Initialize-CISAuditResult @params 50 | } 51 | catch { 52 | $LastError = $_ 53 | $auditResult = Get-TestError -LastError $LastError -recnum $recnum 54 | } 55 | } 56 | end { 57 | # Return auditResult 58 | return $auditResult 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Unit/Private/Assert-ModuleAvailability.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Connect-M365Suite.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Disconnect-M365Suite.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Format-RequiredModuleList.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-Action.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-AdminRoleUserAndAssignment.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-AuditMailboxDetail.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-CISAadOutput.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-CISExoOutput.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-CISMSTeamsOutput.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-CISMgOutput.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-CISSpoOutput.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-ExceededLengthResultDetail.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-MostCommonWord.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-PhishPolicyDetail.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-RequiredModule.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-TestDefinitionsObject.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-TestError.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-UniqueConnection.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Get-UrlLine.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Initialize-CISAuditResult.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Initialize-LargeTestTable.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Invoke-TestFunction.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Measure-AuditResult.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Test-IsAdmin.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Test-PhishPolicyCompliance.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Private/Write-AuditLog.tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-PrivateFunction { 12 | Context 'Default' { 13 | BeforeEach { 14 | $return = Get-PrivateFunction -PrivateData 'string' 15 | } 16 | 17 | It 'Returns a single object' { 18 | ($return | Measure-Object).Count | Should -Be 1 19 | } 20 | 21 | It 'Returns a string based on the parameter PrivateData' { 22 | $return | Should -Be 'string' 23 | } 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/Unit/Public/Export-M365SecurityAuditTable.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' 3 | 4 | # If the module is not found, run the build task 'noop'. 5 | if (-not (Get-Module -Name $script:moduleName -ListAvailable)) 6 | { 7 | # Redirect all streams to $null, except the error stream (stream 2) 8 | & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null 9 | } 10 | 11 | # Re-import the module using force to get any code changes between runs. 12 | Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' 13 | 14 | $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName 15 | $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName 16 | $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName 17 | } 18 | 19 | AfterAll { 20 | $PSDefaultParameterValues.Remove('Mock:ModuleName') 21 | $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') 22 | $PSDefaultParameterValues.Remove('Should:ModuleName') 23 | 24 | Remove-Module -Name $script:moduleName 25 | } 26 | 27 | Describe Get-Something { 28 | 29 | Context 'Return values' { 30 | BeforeEach { 31 | $return = Get-Something -Data 'value' 32 | } 33 | 34 | It 'Returns a single object' { 35 | ($return | Measure-Object).Count | Should -Be 1 36 | } 37 | 38 | } 39 | 40 | Context 'Pipeline' { 41 | It 'Accepts values from the pipeline by value' { 42 | $return = 'value1', 'value2' | Get-Something 43 | 44 | $return[0] | Should -Be 'value1' 45 | $return[1] | Should -Be 'value2' 46 | } 47 | 48 | It 'Accepts value from the pipeline by property name' { 49 | $return = 'value1', 'value2' | ForEach-Object { 50 | [PSCustomObject]@{ 51 | Data = $_ 52 | OtherProperty = 'other' 53 | } 54 | } | Get-Something 55 | 56 | 57 | $return[0] | Should -Be 'value1' 58 | $return[1] | Should -Be 'value2' 59 | } 60 | } 61 | 62 | Context 'ShouldProcess' { 63 | It 'Supports WhatIf' { 64 | (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true 65 | { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw 66 | } 67 | 68 | 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/Unit/Public/Get-MFAStatus.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' 3 | 4 | # If the module is not found, run the build task 'noop'. 5 | if (-not (Get-Module -Name $script:moduleName -ListAvailable)) 6 | { 7 | # Redirect all streams to $null, except the error stream (stream 2) 8 | & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null 9 | } 10 | 11 | # Re-import the module using force to get any code changes between runs. 12 | Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' 13 | 14 | $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName 15 | $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName 16 | $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName 17 | } 18 | 19 | AfterAll { 20 | $PSDefaultParameterValues.Remove('Mock:ModuleName') 21 | $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') 22 | $PSDefaultParameterValues.Remove('Should:ModuleName') 23 | 24 | Remove-Module -Name $script:moduleName 25 | } 26 | 27 | Describe Get-Something { 28 | 29 | Context 'Return values' { 30 | BeforeEach { 31 | $return = Get-Something -Data 'value' 32 | } 33 | 34 | It 'Returns a single object' { 35 | ($return | Measure-Object).Count | Should -Be 1 36 | } 37 | 38 | } 39 | 40 | Context 'Pipeline' { 41 | It 'Accepts values from the pipeline by value' { 42 | $return = 'value1', 'value2' | Get-Something 43 | 44 | $return[0] | Should -Be 'value1' 45 | $return[1] | Should -Be 'value2' 46 | } 47 | 48 | It 'Accepts value from the pipeline by property name' { 49 | $return = 'value1', 'value2' | ForEach-Object { 50 | [PSCustomObject]@{ 51 | Data = $_ 52 | OtherProperty = 'other' 53 | } 54 | } | Get-Something 55 | 56 | 57 | $return[0] | Should -Be 'value1' 58 | $return[1] | Should -Be 'value2' 59 | } 60 | } 61 | 62 | Context 'ShouldProcess' { 63 | It 'Supports WhatIf' { 64 | (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true 65 | { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw 66 | } 67 | 68 | 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/Unit/Public/Grant-M365SecurityAuditConsent.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' 3 | 4 | # If the module is not found, run the build task 'noop'. 5 | if (-not (Get-Module -Name $script:moduleName -ListAvailable)) 6 | { 7 | # Redirect all streams to $null, except the error stream (stream 2) 8 | & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null 9 | } 10 | 11 | # Re-import the module using force to get any code changes between runs. 12 | Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' 13 | 14 | $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName 15 | $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName 16 | $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName 17 | } 18 | 19 | AfterAll { 20 | $PSDefaultParameterValues.Remove('Mock:ModuleName') 21 | $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') 22 | $PSDefaultParameterValues.Remove('Should:ModuleName') 23 | 24 | Remove-Module -Name $script:moduleName 25 | } 26 | 27 | Describe Get-Something { 28 | 29 | Context 'Return values' { 30 | BeforeEach { 31 | $return = Get-Something -Data 'value' 32 | } 33 | 34 | It 'Returns a single object' { 35 | ($return | Measure-Object).Count | Should -Be 1 36 | } 37 | 38 | } 39 | 40 | Context 'Pipeline' { 41 | It 'Accepts values from the pipeline by value' { 42 | $return = 'value1', 'value2' | Get-Something 43 | 44 | $return[0] | Should -Be 'value1' 45 | $return[1] | Should -Be 'value2' 46 | } 47 | 48 | It 'Accepts value from the pipeline by property name' { 49 | $return = 'value1', 'value2' | ForEach-Object { 50 | [PSCustomObject]@{ 51 | Data = $_ 52 | OtherProperty = 'other' 53 | } 54 | } | Get-Something 55 | 56 | 57 | $return[0] | Should -Be 'value1' 58 | $return[1] | Should -Be 'value2' 59 | } 60 | } 61 | 62 | Context 'ShouldProcess' { 63 | It 'Supports WhatIf' { 64 | (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true 65 | { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw 66 | } 67 | 68 | 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/Unit/Public/Invoke-M365SecurityAudit.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' 3 | 4 | # If the module is not found, run the build task 'noop'. 5 | if (-not (Get-Module -Name $script:moduleName -ListAvailable)) 6 | { 7 | # Redirect all streams to $null, except the error stream (stream 2) 8 | & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null 9 | } 10 | 11 | # Re-import the module using force to get any code changes between runs. 12 | Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' 13 | 14 | $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName 15 | $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName 16 | $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName 17 | } 18 | 19 | AfterAll { 20 | $PSDefaultParameterValues.Remove('Mock:ModuleName') 21 | $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') 22 | $PSDefaultParameterValues.Remove('Should:ModuleName') 23 | 24 | Remove-Module -Name $script:moduleName 25 | } 26 | 27 | Describe Get-Something { 28 | 29 | Context 'Return values' { 30 | BeforeEach { 31 | $return = Get-Something -Data 'value' 32 | } 33 | 34 | It 'Returns a single object' { 35 | ($return | Measure-Object).Count | Should -Be 1 36 | } 37 | 38 | } 39 | 40 | Context 'Pipeline' { 41 | It 'Accepts values from the pipeline by value' { 42 | $return = 'value1', 'value2' | Get-Something 43 | 44 | $return[0] | Should -Be 'value1' 45 | $return[1] | Should -Be 'value2' 46 | } 47 | 48 | It 'Accepts value from the pipeline by property name' { 49 | $return = 'value1', 'value2' | ForEach-Object { 50 | [PSCustomObject]@{ 51 | Data = $_ 52 | OtherProperty = 'other' 53 | } 54 | } | Get-Something 55 | 56 | 57 | $return[0] | Should -Be 'value1' 58 | $return[1] | Should -Be 'value2' 59 | } 60 | } 61 | 62 | Context 'ShouldProcess' { 63 | It 'Supports WhatIf' { 64 | (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true 65 | { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw 66 | } 67 | 68 | 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/Unit/Public/Remove-RowsWithEmptyCSVStatus.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | $script:moduleName = '<% $PLASTER_PARAM_ModuleName %>' 3 | 4 | # If the module is not found, run the build task 'noop'. 5 | if (-not (Get-Module -Name $script:moduleName -ListAvailable)) 6 | { 7 | # Redirect all streams to $null, except the error stream (stream 2) 8 | & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null 9 | } 10 | 11 | # Re-import the module using force to get any code changes between runs. 12 | Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' 13 | 14 | $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName 15 | $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName 16 | $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName 17 | } 18 | 19 | AfterAll { 20 | $PSDefaultParameterValues.Remove('Mock:ModuleName') 21 | $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') 22 | $PSDefaultParameterValues.Remove('Should:ModuleName') 23 | 24 | Remove-Module -Name $script:moduleName 25 | } 26 | 27 | Describe Get-Something { 28 | 29 | Context 'Return values' { 30 | BeforeEach { 31 | $return = Get-Something -Data 'value' 32 | } 33 | 34 | It 'Returns a single object' { 35 | ($return | Measure-Object).Count | Should -Be 1 36 | } 37 | 38 | } 39 | 40 | Context 'Pipeline' { 41 | It 'Accepts values from the pipeline by value' { 42 | $return = 'value1', 'value2' | Get-Something 43 | 44 | $return[0] | Should -Be 'value1' 45 | $return[1] | Should -Be 'value2' 46 | } 47 | 48 | It 'Accepts value from the pipeline by property name' { 49 | $return = 'value1', 'value2' | ForEach-Object { 50 | [PSCustomObject]@{ 51 | Data = $_ 52 | OtherProperty = 'other' 53 | } 54 | } | Get-Something 55 | 56 | 57 | $return[0] | Should -Be 'value1' 58 | $return[1] | Should -Be 'value2' 59 | } 60 | } 61 | 62 | Context 'ShouldProcess' { 63 | It 'Supports WhatIf' { 64 | (Get-Command Get-Something).Parameters.ContainsKey('WhatIf') | Should -Be $true 65 | { Get-Something -Data 'value' -WhatIf } | Should -Not -Throw 66 | } 67 | 68 | 69 | } 70 | } 71 | 72 | --------------------------------------------------------------------------------