diff --git a/powershell/Maester.Format.ps1xml b/powershell/Maester.Format.ps1xml
index 57eb7dd89..088334462 100644
--- a/powershell/Maester.Format.ps1xml
+++ b/powershell/Maester.Format.ps1xml
@@ -85,6 +85,23 @@
}
+
+
+
+ $null -ne $_.SharePoint
+
+
+
+
+ if ($_.SharePoint) {
+ "Connected`n" +
+ "URL: $($_.SharePoint.Url)`n" +
+ "Tenant: $($_.SharePoint.Tenant)`n"
+ } else {
+ ''
+ }
+
+
diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1
index a8142ff5c..64dde7bb0 100644
--- a/powershell/Maester.psd1
+++ b/powershell/Maester.psd1
@@ -63,7 +63,7 @@
'Get-MailAuthenticationRecord', 'Get-MtAdminPortalUrl', 'Get-MtAuthenticationMethodPolicyConfig',
'Get-MtAzureManagementGroup', 'Get-MtConditionalAccessPolicy', 'Get-MtExo', 'Get-MtExoThreatPolicyMalware',
'Get-MtGraphScope', 'Get-MtGroupMember', 'Get-MtHtmlReport', 'Get-MtLicenseInformation', 'Get-MtMaesterApp', 'Get-MtRole',
- 'Get-MtRoleMember', 'Get-MtSafeMarkdown', 'Get-MtSession', 'Get-MtTestInventory', 'Get-MtUser',
+ 'Get-MtRoleMember', 'Get-MtSafeMarkdown', 'Get-MtSession', 'Get-MtSpo', 'Get-MtTestInventory', 'Get-MtUser',
'Get-MtUserAuthenticationMethod', 'Get-MtUserAuthenticationMethodInfoByType', 'Import-MtMaesterResult',
'Install-MaesterTests', 'Invoke-Maester', 'Invoke-MtAzureRequest', 'Invoke-MtAzureResourceGraphRequest',
'Invoke-MtGraphRequest', 'Invoke-MtGraphSecurityQuery', 'Merge-MtMaesterResult', 'New-MtMaesterApp', 'Resolve-SPFRecord',
@@ -131,7 +131,14 @@
'Test-MtCisFormsPhishingProtectionEnabled', 'Test-MtCisGlobalAdminCount', 'Test-MtCisHostedConnectionFilterPolicy',
'Test-MtCisInternalMalwareNotification', 'Test-MtCisOutboundSpamFilterPolicy', 'Test-MtCisPasswordExpiry',
'Test-MtCisSafeAntiPhishingPolicy', 'Test-MtCisSafeAttachment', 'Test-MtCisSafeAttachmentsAtpPolicy',
- 'Test-MtCisSafeLink', 'Test-MtCisSharedMailboxSignIn', 'Test-MtCisTeamsLobbyBypass',
+ 'Test-MtCisSafeLink', 'Test-MtCisSharedMailboxSignIn',
+ 'Test-MtCisSpoB2BIntegration',
+ 'Test-MtCisSpoDefaultSharingLink',
+ 'Test-MtCisSpoDefaultSharingLinkPermission',
+ 'Test-MtCisSpoGuestAccessExpiry',
+ 'Test-MtCisSpoGuestCannotShareUnownedItem',
+ 'Test-MtCisSpoPreventDownloadMaliciousFile',
+ 'Test-MtCisTeamsLobbyBypass',
'Test-MtCisTeamsReportSecurityConcerns', 'Test-MtCisThirdPartyAndCustomApps',
'Test-MtCisThirdPartyApplicationsDisallowed', 'Test-MtCisThirdPartyFileSharing',
'Test-MtCisThirdPartyStorageServicesRestricted', 'Test-MtCisUserOwnedAppsRestricted',
diff --git a/powershell/Maester.psm1 b/powershell/Maester.psm1
index 1cb989e9f..d6a8e35d5 100644
--- a/powershell/Maester.psm1
+++ b/powershell/Maester.psm1
@@ -14,17 +14,18 @@
## Initialize Module Variables
## Update Clear-ModuleVariable function in internal/Clear-ModuleVariable.ps1 if you add new variables here
$__MtSession = @{
- GraphCache = @{}
- GraphBaseUri = $null
- TestResultDetail = @{}
- Connections = @()
- DnsCache = @()
- ExoCache = @{}
- OrcaCache = @{}
- AIAgentInfo = $null
- DataverseApiBase = $null # Resolved Dataverse OData API base URL (e.g. https://org123.api.crm.dynamics.com/api/data/v9.2)
- DataverseResourceUrl = $null # Dataverse resource URL for token acquisition (e.g. https://org123.crm.dynamics.com)
+ GraphCache = @{}
+ GraphBaseUri = $null
+ TestResultDetail = @{}
+ Connections = @()
+ DnsCache = @()
+ ExoCache = @{}
+ OrcaCache = @{}
+ AIAgentInfo = $null
+ DataverseApiBase = $null # Resolved Dataverse OData API base URL (e.g. https://org123.api.crm.dynamics.com/api/data/v9.2)
+ DataverseResourceUrl = $null # Dataverse resource URL for token acquisition (e.g. https://org123.crm.dynamics.com)
DataverseEnvironmentId = $null # Environment identifier for display (e.g. org123.crm.dynamics.com)
+ SpoCache = @{} # Cache for SharePoint Online tenant settings retrieved via PnP
}
New-Variable -Name __MtSession -Value $__MtSession -Scope Script -Force
diff --git a/powershell/internal/Clear-ModuleVariable.ps1 b/powershell/internal/Clear-ModuleVariable.ps1
index 17879e660..cb867785b 100644
--- a/powershell/internal/Clear-ModuleVariable.ps1
+++ b/powershell/internal/Clear-ModuleVariable.ps1
@@ -9,7 +9,7 @@
This function will be called for each fresh run of Invoke-Maester.
#>
- [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification='Module variables used in other functions.')]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Module variables used in other functions.')]
param()
Clear-MtGraphCache
@@ -21,5 +21,6 @@
Clear-MtExoCache
$__MtSession.AIAgentInfo = $null
$__MtSession.AzureDevOpsConnection = $null
+ $__MtSession.SpoCache = @{}
# $__MtSession.Connections = @() # Do not clear connections as they are used to track the connection state. This module variable should only be set by Connect-Maester and Disconnect-Maester.
}
diff --git a/powershell/internal/Get-MtSkippedReason.ps1 b/powershell/internal/Get-MtSkippedReason.ps1
index 3e7c806ac..60db509ba 100644
--- a/powershell/internal/Get-MtSkippedReason.ps1
+++ b/powershell/internal/Get-MtSkippedReason.ps1
@@ -8,32 +8,33 @@
[string] $SkippedBecause
)
- switch($SkippedBecause){
- "NotConnectedAzure" { "Not connected to Azure. See [Connecting to Azure](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
- "NotConnectedExchange" { "Not connected to Exchange Online. See [Connecting to Exchange Online](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
- "NotConnectedSecurityCompliance" { "Not connected to Security & Compliance. See [Connecting to Security & Compliance](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
- "NotConnectedTeams" { "Not connected to Teams. See [Connecting to Teams](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
- "NotConnectedAzureDevOps" { "Not connected to Azure DevOps. See [Connecting to Azure DevOps](https://maester.dev/docs/connect-maester/#connect-to-azure-devops-optional)"; break}
- "NotConnectedGraph" { "Not connected to Graph. See [Connect-Maester](https://maester.dev/docs/commands/Connect-Maester#examples)"; break}
- "NotDotGovDomain" { "This test is only for federal, executive branch, departments and agencies. To override use [Test-MtCisaDmarcAggregateCisa -Force](https://maester.dev/docs/commands/Test-MtCisaDmarcAggregateCisa)"; break}
- "NotLicensedEntraIDP1" { "This test is for tenants that are licensed for Entra ID P1. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
- "NotLicensedEntraIDP2" { "This test is for tenants that are licensed for Entra ID P2. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
- "NotLicensedEntraIDGovernance" { "This test is for tenants that are licensed for Entra ID Governance. See [Entra ID Governance licensing](https://learn.microsoft.com/entra/fundamentals/licensing#microsoft-entra-id-governance)"; break}
- "NotLicensedEntraWorkloadID" { "This test is for tenants that are licensed for Entra Workload ID. See [Entra Workload ID licensing](https://learn.microsoft.com/entra/workload-id/workload-identities-faqs)"; break}
- "NotLicensedEop" { "This test is for tenants that are licensed for Exchange Online Protection. See [Exchange Online Protection service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-protection-service-description/exchange-online-protection-service-description)"; break}
- "NotLicensedExoDlp" { "This test is for tenants that are licensed for Exchange Online DLP. See [Microsoft Purview Data Loss Prevention: Data Loss Prevention (DLP) for Exchange Online, SharePoint Online, and OneDrive for Business](https://learn.microsoft.com/en-us/office365/servicedescriptions/microsoft-365-service-descriptions/microsoft-365-tenantlevel-services-licensing-guidance/microsoft-365-security-compliance-licensing-guidance#which-licenses-provide-the-rights-for-a-user-to-benefit-from-the-service-7)"; break}
- "NotLicensedMdo" { "This test is for tenants that are licensed for Defender for Office 365 Plan 2. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break}
- "NotLicensedMdoP2" { "This test is for tenants that are licensed for Defender for Office 365 Plan 2. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break}
- "NotLicensedMdoP1" { "This test is for tenants that are licensed for Defender for Office 365 Plan 1. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break}
- "NotLicensedAdvAudit" { "This test is for tenants that are licensed for Advanced Audit. See [Learn about auditing solutions in Microsoft Purview](https://learn.microsoft.com/en-us/purview/audit-solutions-overview#licensing-requirements)"; break}
- "LicensedEntraIDPremium" { "This test is for tenants that are not licensed for any Entra ID Premium license. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
- "NotSupported" { "This test relies on capabilities not currently available (e.g., cmdlets that are not available on all platforms, Resolve-DnsName)"; break}
- "NotSupportedAppPermission" { "This test relies on Graph APIs that don't support application permissions. Re-run Maester with a user signed in to view the results for this test."; break}
- "LimitedPermissions" { "This test relies on privileged (i.e., ReadWrite) permissions."; break}
- "NotLicensedDefenderXDR" { "This test is for tenants that are licensed for Microsoft Defender XDR. See [Microsoft Defender XDR prerequisites](https://learn.microsoft.com/en-us/defender-xdr/prerequisites#licensing-requirements)"; break}
- "NotLicensedIntune" { "This test is for tenants that are licensed for Microsoft Intune. See [Intune licensing](https://learn.microsoft.com/intune/intune-service/fundamentals/licenses)"; break}
- "NotAuthorized" { "This test was skipped because the user is not authorized to perform the required operation."; break}
- "Error" { $SkippedBecause; break}
- default { $SkippedBecause; break}
+ switch ($SkippedBecause) {
+ "NotConnectedAzure" { "Not connected to Azure. See [Connecting to Azure](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break }
+ "NotConnectedExchange" { "Not connected to Exchange Online. See [Connecting to Exchange Online](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break }
+ "NotConnectedSecurityCompliance" { "Not connected to Security & Compliance. See [Connecting to Security & Compliance](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break }
+ "NotConnectedTeams" { "Not connected to Teams. See [Connecting to Teams](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break }
+ "NotConnectedAzureDevOps" { "Not connected to Azure DevOps. See [Connecting to Azure DevOps](https://maester.dev/docs/connect-maester/#connect-to-azure-devops-optional)"; break }
+ "NotConnectedGraph" { "Not connected to Graph. See [Connect-Maester](https://maester.dev/docs/commands/Connect-Maester#examples)"; break }
+ "NotConnectedSharePoint" { "Not connected to SharePoint Online. See [Connecting to SharePoint Online](https://maester.dev/docs/connect-maester/)"; break }
+ "NotDotGovDomain" { "This test is only for federal, executive branch, departments and agencies. To override use [Test-MtCisaDmarcAggregateCisa -Force](https://maester.dev/docs/commands/Test-MtCisaDmarcAggregateCisa)"; break }
+ "NotLicensedEntraIDP1" { "This test is for tenants that are licensed for Entra ID P1. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break }
+ "NotLicensedEntraIDP2" { "This test is for tenants that are licensed for Entra ID P2. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break }
+ "NotLicensedEntraIDGovernance" { "This test is for tenants that are licensed for Entra ID Governance. See [Entra ID Governance licensing](https://learn.microsoft.com/entra/fundamentals/licensing#microsoft-entra-id-governance)"; break }
+ "NotLicensedEntraWorkloadID" { "This test is for tenants that are licensed for Entra Workload ID. See [Entra Workload ID licensing](https://learn.microsoft.com/entra/workload-id/workload-identities-faqs)"; break }
+ "NotLicensedEop" { "This test is for tenants that are licensed for Exchange Online Protection. See [Exchange Online Protection service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-protection-service-description/exchange-online-protection-service-description)"; break }
+ "NotLicensedExoDlp" { "This test is for tenants that are licensed for Exchange Online DLP. See [Microsoft Purview Data Loss Prevention: Data Loss Prevention (DLP) for Exchange Online, SharePoint Online, and OneDrive for Business](https://learn.microsoft.com/en-us/office365/servicedescriptions/microsoft-365-service-descriptions/microsoft-365-tenantlevel-services-licensing-guidance/microsoft-365-security-compliance-licensing-guidance#which-licenses-provide-the-rights-for-a-user-to-benefit-from-the-service-7)"; break }
+ "NotLicensedMdo" { "This test is for tenants that are licensed for Defender for Office 365 Plan 2. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break }
+ "NotLicensedMdoP2" { "This test is for tenants that are licensed for Defender for Office 365 Plan 2. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break }
+ "NotLicensedMdoP1" { "This test is for tenants that are licensed for Defender for Office 365 Plan 1. See [Microsoft Defender for Office 365 service description](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-advanced-threat-protection-service-description)"; break }
+ "NotLicensedAdvAudit" { "This test is for tenants that are licensed for Advanced Audit. See [Learn about auditing solutions in Microsoft Purview](https://learn.microsoft.com/en-us/purview/audit-solutions-overview#licensing-requirements)"; break }
+ "LicensedEntraIDPremium" { "This test is for tenants that are not licensed for any Entra ID Premium license. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break }
+ "NotSupported" { "This test relies on capabilities not currently available (e.g., cmdlets that are not available on all platforms, Resolve-DnsName)"; break }
+ "NotSupportedAppPermission" { "This test relies on Graph APIs that don't support application permissions. Re-run Maester with a user signed in to view the results for this test."; break }
+ "LimitedPermissions" { "This test relies on privileged (i.e., ReadWrite) permissions."; break }
+ "NotLicensedDefenderXDR" { "This test is for tenants that are licensed for Microsoft Defender XDR. See [Microsoft Defender XDR prerequisites](https://learn.microsoft.com/en-us/defender-xdr/prerequisites#licensing-requirements)"; break }
+ "NotLicensedIntune" { "This test is for tenants that are licensed for Microsoft Intune. See [Intune licensing](https://learn.microsoft.com/intune/intune-service/fundamentals/licenses)"; break }
+ "NotAuthorized" { "This test was skipped because the user is not authorized to perform the required operation."; break }
+ "Error" { $SkippedBecause; break }
+ default { $SkippedBecause; break }
}
}
diff --git a/powershell/public/Add-MtTestResultDetail.ps1 b/powershell/public/Add-MtTestResultDetail.ps1
index 93cff417e..e9896f3a3 100644
--- a/powershell/public/Add-MtTestResultDetail.ps1
+++ b/powershell/public/Add-MtTestResultDetail.ps1
@@ -77,7 +77,7 @@
[ValidateSet('NotConnectedAzure', 'NotConnectedExchange', 'NotConnectedGraph', 'NotDotGovDomain', 'NotLicensedEntraIDP1', 'NotConnectedSecurityCompliance', 'NotConnectedTeams',
'NotLicensedEntraIDP2', 'NotLicensedEntraIDGovernance', 'NotLicensedEntraWorkloadID', 'NotLicensedExoDlp', "LicensedEntraIDPremium", 'NotSupported', 'Custom',
'NotLicensedMdo', 'NotLicensedMdoP2', 'NotLicensedMdoP1', 'NotLicensedAdvAudit', 'NotLicensedEop', 'Error', 'NotSupportedAppPermission', 'LimitedPermissions', 'NotLicensedDefenderXDR',
- 'NotLicensedCustomerLockbox','NotAuthorized', 'NotLicensedIntune', 'NotConnectedAzureDevOps'
+ 'NotLicensedCustomerLockbox', 'NotAuthorized', 'NotLicensedIntune', 'NotConnectedAzureDevOps', 'NotConnectedSharePoint'
)]
[string] $SkippedBecause,
@@ -197,14 +197,14 @@
$TestInvestigate = $Investigate.IsPresent
$testInfo = @{
- TestTitle = $TestTitle
- TestDescription = $Description
- TestResult = $Result
- TestSkipped = $SkippedBecause
- SkippedReason = $SkippedReason
- TestInvestigate = $TestInvestigate
- Severity = $Severity
- Service = $Service
+ TestTitle = $TestTitle
+ TestDescription = $Description
+ TestResult = $Result
+ TestSkipped = $SkippedBecause
+ SkippedReason = $SkippedReason
+ TestInvestigate = $TestInvestigate
+ Severity = $Severity
+ Service = $Service
}
Write-MtProgress -Activity "Running tests" -Status $testName
diff --git a/powershell/public/Connect-Maester.ps1 b/powershell/public/Connect-Maester.ps1
index a0c1d4301..4a7e02ad9 100644
--- a/powershell/public/Connect-Maester.ps1
+++ b/powershell/public/Connect-Maester.ps1
@@ -72,6 +72,11 @@
Connects using a custom application with client ID f45ec3ad-32f0-4c06-8b69-47682afe0216
+.EXAMPLE
+ Connect-Maester -Service Graph,SharePointOnline
+
+ Connects to Microsoft Graph and SharePoint Online. The SharePoint admin URL is auto-discovered from the tenant's initial domain via the Graph API. Optionally, specify -SharePointAdminUrl to override the auto-discovered URL (e.g. for custom domain or government cloud tenants).
+
.LINK
https://maester.dev/docs/commands/Connect-Maester
#>
@@ -108,21 +113,37 @@
[ValidateSet('TeamsChina', 'TeamsGCCH', 'TeamsDOD')]
[string]$TeamsEnvironmentName = $null, #ToValidate: Don't use this parameter, this is the default.
- # The services to connect to such as Azure, Dataverse (for Copilot Studio tests), and EXO. Default is Graph.
- [ValidateSet('All', 'Azure', 'Dataverse', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'Teams')]
+ # The services to connect to such as Azure, Dataverse (for Copilot Studio tests), EXO, and SharePoint Online. Default is Graph.
+ [ValidateSet('All', 'Azure', 'Dataverse', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'Teams', 'SharePointOnline')]
[string[]]$Service = 'Graph',
# The Tenant ID to connect to, if not specified the sign-in user's default tenant is used.
[string]$TenantId,
# The Client ID of the app to connect to for Graph. If not specified, the default Graph PowerShell CLI enterprise app will be used. Reference on how to create an enterprise app: https://learn.microsoft.com/en-us/powershell/microsoftgraph/authentication-commands?view=graph-powershell-1.0#use-delegated-access-with-a-custom-application-for-microsoft-graph-powershell
- [string]$GraphClientId
+ [string]$GraphClientId,
+
+ # The Client ID of the PnP Entra ID app for SharePoint Online. Required when Service includes SharePointOnline.
+ # Use Register-PnPEntraIDAppForInteractiveLogin to create a dedicated app, or reuse an existing Maester app
+ # registration by adding an http://localhost redirect URI and AllSites.FullControl delegated SharePoint permission.
+ [string]$SharePointClientId,
+
+ # The SharePoint admin center URL to connect to when using the SharePointOnline service (e.g. https://contoso-admin.sharepoint.com).
+ # If not specified, the URL is auto-discovered from the tenant's initial domain via the Microsoft Graph API.
+ [string]$SharePointAdminUrl,
+
+ # The certificate thumbprint for app-only authentication to SharePoint Online.
+ # Use together with -SharePointClientId and -TenantId for non-interactive/automation scenarios.
+ # The certificate must be installed in the current user's certificate store.
+ [string]$SharePointCertificateThumbprint
)
$__MtSession.Connections = $Service
- $OrderedImport = Get-ModuleImportOrder -Name @('Az.Accounts', 'ExchangeOnlineManagement', 'Microsoft.Graph.Authentication', 'MicrosoftTeams')
- switch ($OrderedImport.Name) {
+ # Use an explicit module processing order so Microsoft Graph always connects before PnP.PowerShell.
+ # This avoids relying on Get-ModuleImportOrder, which may reorder modules by bundled DLL version.
+ $OrderedImport = @('Az.Accounts', 'ExchangeOnlineManagement', 'Microsoft.Graph.Authentication', 'MicrosoftTeams', 'PnP.PowerShell')
+ switch ($OrderedImport) {
'Az.Accounts' {
if ($Service -contains 'Azure' -or $Service -contains 'Dataverse' -or $Service -contains 'All') {
@@ -225,27 +246,27 @@
if ($Service -contains 'SecurityCompliance' -or $Service -contains 'All') {
$Environments = @{
- 'O365China' = @{
+ 'O365China' = @{
ConnectionUri = 'https://ps.compliance.protection.partner.outlook.cn/powershell-liveid'
AuthZEndpointUri = 'https://login.chinacloudapi.cn/common'
}
- 'O365GermanyCloud' = @{
+ 'O365GermanyCloud' = @{
ConnectionUri = 'https://ps.compliance.protection.outlook.com/powershell-liveid/'
AuthZEndpointUri = 'https://login.microsoftonline.com/common'
}
- 'O365Default' = @{
+ 'O365Default' = @{
ConnectionUri = 'https://ps.compliance.protection.outlook.com/powershell-liveid/'
AuthZEndpointUri = 'https://login.microsoftonline.com/common'
}
- 'O365USGovGCCHigh' = @{
+ 'O365USGovGCCHigh' = @{
ConnectionUri = 'https://ps.compliance.protection.office365.us/powershell-liveid/'
AuthZEndpointUri = 'https://login.microsoftonline.us/common'
}
- 'O365USGovDoD' = @{
+ 'O365USGovDoD' = @{
ConnectionUri = 'https://l5.ps.compliance.protection.office365.us/powershell-liveid/'
AuthZEndpointUri = 'https://login.microsoftonline.us/common'
}
- Default = @{
+ Default = @{
ConnectionUri = 'https://ps.compliance.protection.outlook.com/powershell-liveid/'
AuthZEndpointUri = 'https://login.microsoftonline.com/common'
}
@@ -288,8 +309,8 @@
# Remove the broken cmdlet and re-import the working EXO one.
Remove-Item -Path 'Function:\Get-AdminAuditLogConfig' -Force -ErrorAction SilentlyContinue
$ExchangeConnectionInformation | Where-Object { $_.IsEopSession -ne $true -and $_.State -eq 'Connected' } |
- Select-Object -ExpandProperty ModuleName |
- Import-Module -Function 'Get-AdminAuditLogConfig' > $null
+ Select-Object -ExpandProperty ModuleName |
+ Import-Module -Function 'Get-AdminAuditLogConfig' > $null
} catch {
Write-Error "Failed to restore Get-AdminAuditLogConfig cmdlet: $($_.Exception.Message)"
}
@@ -351,6 +372,56 @@
}
}
}
- } # end switch OrderedImport
+ 'PnP.PowerShell' {
+ # SharePoint Online via PnP — must run AFTER Graph to avoid Microsoft.Graph.Core DLL conflict
+ if ($Service -contains 'SharePointOnline' -or $Service -contains 'All') {
+ Write-Verbose 'Connecting to SharePoint Online via PnP'
+
+ if (-not $SharePointClientId) {
+ Write-Host "`nSharePointOnline requires the -SharePointClientId parameter. You can use a dedicated PnP app (Register-PnPEntraIDAppForInteractiveLogin) or add an http://localhost redirect URI and AllSites.Read delegated permission to your existing Maester app registration.`nFor more information see https://maester.dev/docs/sections/create-entra-app" -ForegroundColor Red
+ } else {
+ try {
+ # Use the provided admin URL or auto-discover from the tenant's initial domain
+ if ($SharePointAdminUrl) {
+ $spoAdminUrl = $SharePointAdminUrl
+ Write-Verbose "Using provided SharePoint admin URL: $spoAdminUrl"
+ } else {
+ $domains = Invoke-MtGraphRequest -RelativeUri "domains" -ApiVersion "v1.0"
+ $initialDomain = ($domains | Where-Object { $_.isInitial -eq $true }).id
+ $tenantPrefix = ($initialDomain -split '\.')[0]
+ $spoAdminUrl = "https://$tenantPrefix-admin.sharepoint.com"
+ Write-Verbose "Resolved SharePoint admin URL: $spoAdminUrl"
+ }
+ Import-Module PnP.PowerShell -ErrorAction Stop
+ $pnpParams = @{
+ Url = $spoAdminUrl
+ ClientId = $SharePointClientId
+ }
+ if ($SharePointCertificateThumbprint) {
+ if (-not $TenantId) {
+ Write-Host "`nThe -TenantId parameter is required when using -SharePointCertificateThumbprint." -ForegroundColor Red
+ } else {
+ $pnpParams['Thumbprint'] = $SharePointCertificateThumbprint
+ $pnpParams['Tenant'] = $TenantId
+ }
+ } else {
+ if ($UseDeviceCode) {
+ $pnpParams['DeviceLogin'] = $true
+ }
+ if ($TenantId) {
+ $pnpParams['Tenant'] = $TenantId
+ }
+ }
+ Connect-PnPOnline @pnpParams
+ } catch [Management.Automation.CommandNotFoundException] {
+ Write-Host "`nThe PnP.PowerShell module is not installed. Please install the module using the following command.`nFor more information see https://pnp.github.io/powershell/articles/installation.html" -ForegroundColor Red
+ Write-Host "`nInstall-Module PnP.PowerShell -Scope CurrentUser`n" -ForegroundColor Yellow
+ } catch {
+ Write-Host "`nFailed to connect to SharePoint Online: $($_.Exception.Message)" -ForegroundColor Red
+ }
+ }
+ }
+ }
+ }
} # end function Connect-Maester
diff --git a/powershell/public/Disconnect-Maester.ps1 b/powershell/public/Disconnect-Maester.ps1
index 8918dd355..9423a0dac 100644
--- a/powershell/public/Disconnect-Maester.ps1
+++ b/powershell/public/Disconnect-Maester.ps1
@@ -1,5 +1,5 @@
function Disconnect-Maester {
- <#
+ <#
.Synopsis
Helper method to sign out of the current Microsoft Graph session. Alternate for Disconnect-MgGraph.
@@ -27,12 +27,12 @@
[CmdletBinding()]
param()
- if($__MtSession.Connections -contains "Graph" -or $__MtSession.Connections -contains "All"){
+ if ($__MtSession.Connections -contains "Graph" -or $__MtSession.Connections -contains "All") {
Write-Verbose -Message "Disconnecting from Microsoft Graph."
Disconnect-MgGraph
}
- if($__MtSession.Connections -contains "Azure" -or $__MtSession.Connections -contains "Dataverse" -or $__MtSession.Connections -contains "All"){
+ if ($__MtSession.Connections -contains "Azure" -or $__MtSession.Connections -contains "Dataverse" -or $__MtSession.Connections -contains "All") {
Write-Verbose -Message "Disconnecting from Microsoft Azure."
try {
Disconnect-AzAccount -ErrorAction Stop | Out-Null
@@ -41,11 +41,22 @@
}
}
- if($__MtSession.Connections -contains "ExchangeOnline" -or $__MtSession.Connections -contains "SecurityCompliance" -or $__MtSession.Connections -contains "All"){
+ if ($__MtSession.Connections -contains "ExchangeOnline" -or $__MtSession.Connections -contains "SecurityCompliance" -or $__MtSession.Connections -contains "All") {
Write-Verbose -Message "Disconnecting from Microsoft Exchange Online."
Disconnect-ExchangeOnline
}
- if($__MtSession.Connections -contains "Teams" -or $__MtSession.Connections -contains "All"){
+
+ if ($__MtSession.Connections -contains "SharePointOnline" -or $__MtSession.Connections -contains "All") {
+ Write-Verbose -Message "Disconnecting from SharePoint Online (PnP)."
+ try {
+ Disconnect-PnPOnline -ErrorAction Stop
+ } catch {
+ Write-Verbose "Disconnect-PnPOnline encountered an error: $($_.Exception.Message)"
+ }
+ $__MtSession.SpoCache = @{}
+ }
+
+ if ($__MtSession.Connections -contains "Teams" -or $__MtSession.Connections -contains "All") {
Write-Verbose -Message "Disconnecting from Microsoft Teams."
Disconnect-MicrosoftTeams
}
diff --git a/powershell/public/Get-MtSpo.ps1 b/powershell/public/Get-MtSpo.ps1
new file mode 100644
index 000000000..c0fb15d5e
--- /dev/null
+++ b/powershell/public/Get-MtSpo.ps1
@@ -0,0 +1,45 @@
+function Get-MtSpo {
+ <#
+ .SYNOPSIS
+ Retrieves SharePoint Online tenant settings via PnP with session caching.
+
+ .DESCRIPTION
+ Returns the full SPO tenant configuration from Get-PnPTenant.
+ Results are cached in $__MtSession.SpoCache for the duration of the session.
+ Use -ClearCache to force a fresh retrieval.
+
+ .EXAMPLE
+ Get-MtSpo
+
+ Returns the cached (or freshly retrieved) SPO tenant settings.
+
+ .EXAMPLE
+ Get-MtSpo -ClearCache
+
+ Clears the cached settings and retrieves fresh data from Get-PnPTenant.
+
+ .LINK
+ https://maester.dev/docs/commands/Get-MtSpo
+ #>
+ [CmdletBinding()]
+ param(
+ # Clear the cached SPO tenant settings and retrieve fresh data.
+ [switch]$ClearCache
+ )
+
+ if ($ClearCache) {
+ $__MtSession.SpoCache = @{}
+ Write-Verbose "SPO cache cleared."
+ }
+
+ if ($null -eq $__MtSession.SpoCache.SpoTenant) {
+ Write-Verbose "SPO tenant settings not in cache, requesting."
+ $response = Get-PnPTenant -ErrorAction Stop
+ $__MtSession.SpoCache.SpoTenant = $response
+ } else {
+ Write-Verbose "SPO tenant settings in cache."
+ $response = $__MtSession.SpoCache.SpoTenant
+ }
+
+ return $response
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoB2BIntegration.md b/powershell/public/cis/Test-MtCisSpoB2BIntegration.md
new file mode 100644
index 000000000..0f14fe987
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoB2BIntegration.md
@@ -0,0 +1,34 @@
+7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
+
+Entra ID B2B provides authentication and management of guests. Authentication happens via one-time passcode when they don't already have a work or school account or a Microsoft account. Integration with SharePoint and OneDrive allows for more granular control of how guest user accounts are managed in the organization's AAD, unifying a similar guest experience already deployed in other Microsoft 365 services such as Teams.
+
+>Note: Global Reader role currently can't access SharePoint using PowerShell.
+
+## Rationale
+
+External users assigned guest accounts will be subject to Entra ID access policies, such as multi-factor authentication. This provides a way to manage guest identities and control access to SharePoint and OneDrive resources. Without this integration, files can be shared without account registration, making it more challenging to audit and manage who has access to the organization's data.
+
+## Impact
+
+B2B collaboration is used with other Entra services so should not be new or unusual. Microsoft also has made the experience seamless when turning on integration on SharePoint sites that already have active files shared with guest users. The referenced Microsoft article on the subject has more details on this.
+
+## Remediation
+
+1. Connect to SharePoint Online using `Connect-SPOService`
+2. Run the following command:
+
+```powershell
+Set-SPOTenant -EnableAzureADB2BIntegration $true
+```
+
+>Default Value: False
+
+## Related Links
+
+* [Enabling the integration](https://learn.microsoft.com/en-us/sharepoint/sharepoint-azureb2b-integration#enabling-the-integration)
+* [What is Microsoft Entra B2B collaboration?](https://learn.microsoft.com/en-us/entra/external-id/what-is-b2b)
+* [Set-SPOTenant](https://learn.microsoft.com/en-us/powershell/module/microsoft.online.sharepoint.powershell/set-spotenant?view=sharepoint-ps)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 368](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoB2BIntegration.ps1 b/powershell/public/cis/Test-MtCisSpoB2BIntegration.ps1
new file mode 100644
index 000000000..0e4edb24b
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoB2BIntegration.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoB2BIntegration {
+ <#
+ .SYNOPSIS
+ Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
+
+ .DESCRIPTION
+ 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoB2BIntegration
+
+ Returns true if SharePoint and OneDrive integration with Azure AD B2B is enabled
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoB2BIntegration
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing SharePoint Entra B2B integration..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.B2BIntegration.EnableAzureADB2BIntegration) {
+ $testResult = "Well done. Your SharePoint tenant is integrated with Microsoft Entra B2B."
+ } else {
+ $testResult = "Your SharePoint tenant is not integrated with Microsoft Entra B2B."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.md b/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.md
new file mode 100644
index 000000000..71e7bc700
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.md
@@ -0,0 +1,40 @@
+7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
+
+This setting sets the default link type that a user will see when sharing content in OneDrive or SharePoint. It does not restrict or exclude any other options. The recommended state is **Specific people (only the people the user specifies) or Only people in your organization** (more restrictive).
+
+## Rationale
+
+By defaulting to specific people, the user will first need to consider whether or not the content being shared should be accessible by the entire organization versus select individuals. This aids in reinforcing the concept of least privilege.
+
+## Remediation
+
+1. Navigate to [SharePoint admin center](https://admin.microsoft.com/sharepoint)
+2. Click to expand **Policies** > **Sharing**.
+3. Scroll to **File and folder links.**
+4. Set **Choose the type of link that's selected by default when users share files and folders in SharePoint and OneDrive to Specific people (only the people the user specifies) or Only people in your organization.**
+
+
+### PowerShell
+
+1. Connect to SharePoint Online using `Connect-SPOService`
+2. Run the following command:
+
+```powershell
+Set-SPOTenant -DefaultSharingLinkType Direct
+```
+
+3. Or, to set a more restrictive state:
+
+```powershell
+Set-SPOTenant -DefaultSharingLinkType Internal
+```
+
+>Default Value: Only people in your organization (Internal)
+
+## Related Links
+
+* [Set-SPOTenant](https://learn.microsoft.com/en-us/powershell/module/microsoft.online.sharepoint.powershell/set-spotenant?view=sharepoint-ps)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 381](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.ps1 b/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.ps1
new file mode 100644
index 000000000..0de2492a5
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoDefaultSharingLink.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoDefaultSharingLink {
+ <#
+ .SYNOPSIS
+ Ensure link sharing is restricted in SharePoint and OneDrive
+
+ .DESCRIPTION
+ 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoDefaultSharingLink
+
+ Returns true if link sharing is restricted in SharePoint and OneDrive
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLink
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing default sharing link type in SharePoint Online..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.DefaultSharingLinkType -eq "Direct" -or $spoTenant.DefaultSharingLinkType -eq "Internal") {
+ $testResult = "Well done. Default sharing link type is set to a restrictive option."
+ } else {
+ $testResult = "Default sharing link type is not set to a restrictive option."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.md b/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.md
new file mode 100644
index 000000000..27ce2ecbf
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.md
@@ -0,0 +1,39 @@
+7.2.11 (L1) Ensure the SharePoint default sharing link permission is set
+
+This setting configures the permission that is selected by default for sharing link from a SharePoint site.
+
+The recommended state is **View**.
+
+## Rationale
+
+Setting the view permission as the default ensures that users must deliberately select the edit permission when sharing a link. This approach reduces the risk of unintentionally granting edit privileges to a resource that only requires read access, supporting the principle of least privilege.
+
+## Impact
+
+Not applicable.
+
+## Remediation
+
+1. Navigate to [SharePoint admin center](https://admin.microsoft.com/sharepoint)
+2. Click to expand **Policies** > **Sharing**.
+3. Scroll to **File and folder links.**
+4. Ensure **Choose the permission that's selected by default for sharing links** is set to **View**.
+
+### PowerShell
+
+1. Connect to SharePoint Online using `Connect-SPOService`
+2. Run the following command:
+
+```powershell
+Set-SPOTenant -DefaultLinkPermission View
+```
+
+>Default Value: DefaultLinkPermission : Edit
+
+## Related Links
+
+* [Manage sharing settings for SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off#file-and-folder-links)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 391](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.ps1 b/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.ps1
new file mode 100644
index 000000000..7571d05fd
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoDefaultSharingLinkPermission.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoDefaultSharingLinkPermission {
+ <#
+ .SYNOPSIS
+ Ensure the SharePoint default sharing link permission is set
+
+ .DESCRIPTION
+ 7.2.11 (L1) Ensure the SharePoint default sharing link permission is set
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoDefaultSharingLinkPermission
+
+ Returns true if the SharePoint default sharing link permission is set to View
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing default sharing link permission in SharePoint Online..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.DefaultLinkPermission -eq "View") {
+ $testResult = "Well done. Default sharing link permission is set to View."
+ } else {
+ $testResult = "Default sharing link permission is not set to View."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.md b/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.md
new file mode 100644
index 000000000..f3c21dc1a
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.md
@@ -0,0 +1,44 @@
+7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
+
+This policy setting configures the expiration time for each guest that is invited to the SharePoint site or with whom users share individual files and folders with.
+
+The recommended state is **30** or less.
+
+## Rationale
+
+This setting ensures that guests who no longer need access to the site or link no longer have access after a set period of time. Allowing guest access for an indefinite amount of time could lead to loss of data confidentiality and oversight.
+
+>Note: Guest membership applies at the Microsoft 365 group level. Guests who have permission to view a SharePoint site or use a sharing link may also have access to a Microsoft Teams team or security group.
+
+## Impact
+
+Site collection administrators will have to renew access to guests who still need access after 30 days. They will receive an e-mail notification once per week about guest access that is about to expire.
+
+>Note: The guest expiration policy only applies to guests who use sharing links or guests who have direct permissions to a SharePoint site after the guest policy is enabled. The guest policy does not apply to guest users that have pre-existing permissions or access through a sharing link before the guest expiration policy is applied.
+
+## Remediation
+
+1. Navigate to [SharePoint admin center](https://admin.microsoft.com/sharepoint)
+2. Click to expand **Policies** > **Sharing**.
+3. Scroll to and expand **More external sharing settings.**
+4. Set **Guest access to a site or OneDrive will expire automatically after this many days** to ***30***
+
+### PowerShell
+
+1. Connect to SharePoint Online using `Connect-SPOService`
+2. Run the following command:
+
+```powershell
+Set-SPOTenant -ExternalUserExpireInDays 30 -ExternalUserExpirationRequired $True
+```
+
+>Default Value: ExternalUserExpirationRequired $false, ExternalUserExpireInDays 60 days
+
+## Related Links
+
+* [Manage sharing settings for SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off#change-the-organization-level-external-sharing-setting)
+* [Managing SharePoint Online Security: A Team Effort](https://learn.microsoft.com/en-us/microsoft-365/community/sharepoint-security-a-team-effort)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 385](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.ps1 b/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.ps1
new file mode 100644
index 000000000..32e71b13b
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoGuestAccessExpiry.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoGuestAccessExpiry {
+ <#
+ .SYNOPSIS
+ Ensure guest access to a site or OneDrive will expire automatically
+
+ .DESCRIPTION
+ 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoGuestAccessExpiry
+
+ Returns true if guest access expiration is enabled and set to 30 days or less
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoGuestAccessExpiry
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing guest access expiration settings in SharePoint Online..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.ExternalUserExpirationRequired -eq $true -and $spoTenant.ExternalUserExpireInDays -gt 0 -and $spoTenant.ExternalUserExpireInDays -le 30) {
+ $testResult = "Well done. Guest access expiration is enabled and set to 30 days or less ($($spoTenant.ExternalUserExpireInDays) days)."
+ } else {
+ $testResult = "Guest access expiration is not enabled or set to more than 30 days."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.md b/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.md
new file mode 100644
index 000000000..db979aae4
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.md
@@ -0,0 +1,38 @@
+7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own
+
+SharePoint gives users the ability to share files, folders, and site collections. Internal users can share with external collaborators, and with the right permissions could share to other external parties.
+
+## Rationale
+
+Sharing and collaboration are key; however, file, folder, or site collection owners should have the authority over what external users get shared with to prevent unauthorized disclosures of information.
+
+## Impact
+
+The impact associated with this change is highly dependent upon current practices. If users do not regularly share with external parties, then minimal impact is likely. However, if users do regularly share with guests/externally, minimum impacts could occur as those external users will be unable to 're-share' content.
+
+## Remediation
+
+1. Navigate to [SharePoint admin center](https://admin.microsoft.com/sharepoint)
+2. Click to expand **Policies** > **Sharing**.
+3. Scroll to and expand **More external sharing settings.**, uncheck **Allow guests to share items they don't own.**
+4. Click **Save**.
+
+### PowerShell
+
+1. Connect to SharePoint Online using `Connect-SPOService`
+2. Run the following command:
+
+```powershell
+Set-SPOTenant -PreventExternalUsersFromResharing $True
+```
+
+>Default Value: Checked (False)
+
+## Related Links
+
+* [Manage sharing settings for SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off#change-the-organization-level-external-sharing-setting)
+* [Overview of external sharing in SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/external-sharing-overview)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 376](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.ps1 b/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.ps1
new file mode 100644
index 000000000..2e576608a
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoGuestCannotShareUnownedItem.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoGuestCannotShareUnownedItem {
+ <#
+ .SYNOPSIS
+ Ensure that SharePoint guest users cannot share items they don't own
+
+ .DESCRIPTION
+ 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoGuestCannotShareUnownedItem
+
+ Returns true if SharePoint guest users cannot share items they don't own
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing that SharePoint guest users cannot share items they don't own..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.PreventExternalUsersFromResharing) {
+ $testResult = "Well done. External users cannot share items they don't own."
+ } else {
+ $testResult = "External users can share items they don't own."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.md b/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.md
new file mode 100644
index 000000000..0b8385b17
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.md
@@ -0,0 +1,35 @@
+7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
+
+By default, SharePoint online allows files that Defender for Office 365 has detected as infected to be downloaded.
+
+## Rationale
+
+Defender for Office 365 for SharePoint, OneDrive, and Microsoft Teams protects your organization from inadvertently sharing malicious files. When an infected file is detected that file is blocked so that no one can open, copy, move, or share it until further actions are taken by the organization's security team.
+
+## Impact
+
+The only potential impact associated with implementation of this setting is potential inconvenience associated with the small percentage of false positive detections that may occur.
+
+## Remediation
+
+### PowerShell
+
+1. Connect to SharePoint Online using `Connect-SPOService -Url https://tenant-admin.sharepoint.com`, replacing "tenant" with the appropriate value.
+2. Run the following PowerShell command to set the recommended value:
+
+```powershell
+Set-SPOTenant -DisallowInfectedFileDownload $true
+```
+
+>Note: The Global Reader role cannot access SharePoint using PowerShell according to Microsoft. See the reference section for more information.
+
+>Default Value: False
+
+## Related Links
+
+* [Manage sharing settings for SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/turn-external-sharing-on-or-off#change-the-organization-level-external-sharing-setting)
+* [Overview of external sharing in SharePoint and OneDrive in Microsoft 365](https://learn.microsoft.com/en-us/sharepoint/external-sharing-overview)
+* [CIS Microsoft 365 Foundations Benchmark v6.0.1 - Page 394](https://www.cisecurity.org/benchmark/microsoft_365)
+
+
+%TestResult%
diff --git a/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.ps1 b/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.ps1
new file mode 100644
index 000000000..4479f9645
--- /dev/null
+++ b/powershell/public/cis/Test-MtCisSpoPreventDownloadMaliciousFile.ps1
@@ -0,0 +1,43 @@
+function Test-MtCisSpoPreventDownloadMaliciousFile {
+ <#
+ .SYNOPSIS
+ Ensure Office 365 SharePoint infected files are disallowed for download
+
+ .DESCRIPTION
+ 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
+ CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+ .EXAMPLE
+ Test-MtCisSpoPreventDownloadMaliciousFile
+
+ Returns true if Office 365 SharePoint infected files are disallowed for download
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+ Write-Verbose "Testing malicious file download prevention in SharePoint Online..."
+
+ if (!(Test-MtConnection SharePointOnline)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ $return = $true
+ try {
+ $spoTenant = Get-MtSpo
+ if ($spoTenant.DisallowInfectedFileDownload) {
+ $testResult = "Well done. Malicious file download prevention is enabled in your SharePoint tenant."
+ } else {
+ $testResult = "Malicious file download prevention is not enabled in your SharePoint tenant."
+ $return = $false
+ }
+ Add-MtTestResultDetail -Result $testResult
+ return $return
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
\ No newline at end of file
diff --git a/powershell/public/core/Test-MtConnection.ps1 b/powershell/public/core/Test-MtConnection.ps1
index 3c2157426..9695565e8 100644
--- a/powershell/public/core/Test-MtConnection.ps1
+++ b/powershell/public/core/Test-MtConnection.ps1
@@ -7,7 +7,7 @@
Tests the connection for each service and returns $true if the session is connected to the specified service.
.PARAMETER Service
- The service to check the connection for. Valid values are 'All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'Graph', 'SecurityCompliance' (or 'EOP'), and 'Teams'. Default is 'Graph'.
+ The service to check the connection for. Valid values are 'All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'Graph', 'SecurityCompliance' (or 'EOP'), 'SharePointOnline', and 'Teams'. Default is 'Graph'.
.PARAMETER Details
Return the full details of all connections instead of just a boolean value.
@@ -15,12 +15,12 @@
.EXAMPLE
Test-MtConnection -Service All
- Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), and Microsoft Teams. Returns a Boolean value.
+ Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), SharePoint Online (PnP), and Microsoft Teams. Returns a Boolean value.
.EXAMPLE
Test-MtConnection -Service All -Details
- Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), and Microsoft Teams. Returns a custom object that contains the connection details for all services.
+ Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), SharePoint Online (PnP), and Microsoft Teams. Returns a custom object that contains the connection details for all services.
.EXAMPLE
Test-MtConnection -Service Azure
@@ -35,7 +35,7 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', 'AvoidUsingWriteHost', Justification = 'Sending colorful output to host in addition to rich object output.')]
param(
# Checks if the current session is connected to the specified service
- [ValidateSet('All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'EOP', 'Graph', 'SecurityCompliance', 'Teams')]
+ [ValidateSet('All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'EOP', 'Graph', 'SecurityCompliance', 'SharePointOnline', 'Teams')]
[Parameter(Position = 0)]
[string[]]$Service = 'Graph',
@@ -46,14 +46,15 @@
begin {
$MtConnections = [PSCustomObject]@{
- PSTypeName = 'Maester.Connections'
- Azure = $null
- AzureDevOps = $null
- Graph = $null
- ExchangeOnline = $null
+ PSTypeName = 'Maester.Connections'
+ Azure = $null
+ AzureDevOps = $null
+ Graph = $null
+ ExchangeOnline = $null
ExchangeOnlineProtection = $null
- Teams = $null
- AllConnected = $false
+ SharePoint = $null
+ Teams = $null
+ AllConnected = $false
}
# This can potentially be replaced by $MtConnections.AllConnected but all functions that reference this function would need to be updated.
@@ -147,6 +148,20 @@
}
#endregion Teams
+ #region SharePoint
+ if ($Service -contains 'SharePoint' -or $Service -contains 'All') {
+ $IsConnected = $false
+ try {
+ $MtConnections.SharePoint = Get-PnPConnection
+ $IsConnected = $null -ne ($MtConnections.SharePoint)
+ } catch {
+ Write-Debug "SharePoint: $false"
+ }
+ Write-Verbose "SharePoint: $IsConnected"
+ if (!$IsConnected) { $ConnectionState = $false }
+ }
+ #endregion SharePoint
+
#region AzureDevOps
if ($Service -contains 'AzureDevOps' -or $Service -contains 'All') {
$IsConnected = $false
diff --git a/tests/cis/Test-MtCisSpoB2BIntegration.Tests.ps1 b/tests/cis/Test-MtCisSpoB2BIntegration.Tests.ps1
new file mode 100644
index 000000000..76c0ff592
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoB2BIntegration.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "OneDrive", "CIS.M365.7.2.2", "L1", "CIS E3 Level 1", "CIS E3", "CIS E5 Level 1", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.2.2: Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled" {
+
+ $result = Test-MtCisSpoB2BIntegration
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "SharePoint and OneDrive integration with Azure AD B2B is enabled"
+ }
+ }
+}
diff --git a/tests/cis/Test-MtCisSpoDefaultSharingLink.Tests.ps1 b/tests/cis/Test-MtCisSpoDefaultSharingLink.Tests.ps1
new file mode 100644
index 000000000..f0d99e4e1
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoDefaultSharingLink.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "OneDrive", "CIS.M365.7.2.7", "L1", "CIS E3 Level 1", "CIS E3", "CIS E5 Level 1", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.2.7: Ensure link sharing is restricted in SharePoint and OneDrive" {
+
+ $result = Test-MtCisSpoDefaultSharingLink
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "Link sharing is restricted in SharePoint and OneDrive"
+ }
+ }
+}
diff --git a/tests/cis/Test-MtCisSpoDefaultSharingLinkPermission.Tests.ps1 b/tests/cis/Test-MtCisSpoDefaultSharingLinkPermission.Tests.ps1
new file mode 100644
index 000000000..997b1b744
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoDefaultSharingLinkPermission.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "OneDrive", "CIS.M365.7.2.11", "L1", "CIS E3 Level 1", "CIS E3", "CIS E5 Level 1", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.2.11: Ensure the SharePoint default sharing link permission is set" {
+
+ $result = Test-MtCisSpoDefaultSharingLinkPermission
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "The SharePoint default sharing link permission is set"
+ }
+ }
+}
diff --git a/tests/cis/Test-MtCisSpoGuestAccessExpiry.Tests.ps1 b/tests/cis/Test-MtCisSpoGuestAccessExpiry.Tests.ps1
new file mode 100644
index 000000000..5d8d9f97f
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoGuestAccessExpiry.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "OneDrive", "CIS.M365.7.2.9", "L1", "CIS E3 Level 1", "CIS E3", "CIS E5 Level 1", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.2.9: Ensure guest access to a site or OneDrive will expire automatically" {
+
+ $result = Test-MtCisSpoGuestAccessExpiry
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "Guest access to a site or OneDrive will expire automatically"
+ }
+ }
+}
diff --git a/tests/cis/Test-MtCisSpoGuestCannotShareUnownedItem.Tests.ps1 b/tests/cis/Test-MtCisSpoGuestCannotShareUnownedItem.Tests.ps1
new file mode 100644
index 000000000..28164eafe
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoGuestCannotShareUnownedItem.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "OneDrive", "CIS.M365.7.2.5", "L2", "CIS E3 Level 2", "CIS E3", "CIS E5 Level 2", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.2.5: Ensure that SharePoint guest users cannot share items they don't own" {
+
+ $result = Test-MtCisSpoGuestCannotShareUnownedItem
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "SharePoint guest users cannot share items they don't own"
+ }
+ }
+}
diff --git a/tests/cis/Test-MtCisSpoPreventDownloadMaliciousFile.Tests.ps1 b/tests/cis/Test-MtCisSpoPreventDownloadMaliciousFile.Tests.ps1
new file mode 100644
index 000000000..11de1a251
--- /dev/null
+++ b/tests/cis/Test-MtCisSpoPreventDownloadMaliciousFile.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CIS" -Tag "SharePoint Online", "CIS.M365.7.3.1", "L2", "CIS E5 Level 2", "CIS E5", "CIS", "CIS M365 v6.0.1" {
+ It "CIS.M365.7.3.1: Ensure Office 365 SharePoint infected files are disallowed for download" {
+
+ $result = Test-MtCisSpoPreventDownloadMaliciousFile
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "Office 365 SharePoint infected files are disallowed for download"
+ }
+ }
+}
diff --git a/website/docs/commands/Connect-Maester.mdx b/website/docs/commands/Connect-Maester.mdx
index fe4993d8a..4f7db824e 100644
--- a/website/docs/commands/Connect-Maester.mdx
+++ b/website/docs/commands/Connect-Maester.mdx
@@ -17,7 +17,7 @@ Helper method to connect to Microsoft Graph using Connect-MgGraph with the requi
```powershell
Connect-Maester [-SendMail] [-SendTeamsMessage] [-Privileged] [-UseDeviceCode] [[-Environment] ]
[[-AzureEnvironment] ] [[-ExchangeEnvironmentName] ] [[-TeamsEnvironmentName] ]
- [[-Service] ] [[-TenantId] ] [[-GraphClientId] ]
+ [[-Service] ] [[-TenantId] ] [[-GraphClientId] ] [[-SharePointAdminUrl] ]
[-ProgressAction ] []
```
@@ -133,6 +133,17 @@ Connect-Maester -GraphClientId 'f45ec3ad-32f0-4c06-8b69-47682afe0216'
Connects using a custom application with client ID f45ec3ad-32f0-4c06-8b69-47682afe0216
+### EXAMPLE 13
+
+```powershell
+Connect-Maester -Service Graph,SharePointOnline
+```
+
+Connects to Microsoft Graph and SharePoint Online.
+The SharePoint admin URL is auto-discovered from the tenant's initial domain via the Graph API.
+Optionally, specify -SharePointAdminUrl to override the auto-discovered URL (e.g.
+for custom domain or government cloud tenants).
+
## PARAMETERS
### -SendMail
@@ -323,6 +334,24 @@ Accept pipeline input: False
Accept wildcard characters: False
```
+### -SharePointAdminUrl
+
+The SharePoint admin center URL to connect to when using the SharePointOnline service (e.g.
+https://contoso-admin.sharepoint.com).
+If not specified, the URL is auto-discovered from the tenant's initial domain via the Microsoft Graph API.
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 8
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
### -ProgressAction
\{\{ Fill ProgressAction Description \}\}
diff --git a/website/docs/commands/Test-MtCisSpoB2BIntegration.mdx b/website/docs/commands/Test-MtCisSpoB2BIntegration.mdx
new file mode 100644
index 000000000..45c878619
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoB2BIntegration.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled"
+id: Test-MtCisSpoB2BIntegration
+title: Test-MtCisSpoB2BIntegration
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoB2BIntegration.ps1
+---
+
+## SYNOPSIS
+
+Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoB2BIntegration [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoB2BIntegration
+```
+
+Returns true if SharePoint and OneDrive integration with Azure AD B2B is enabled
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoB2BIntegration](https://maester.dev/docs/commands/Test-MtCisSpoB2BIntegration)
diff --git a/website/docs/commands/Test-MtCisSpoDefaultSharingLink.mdx b/website/docs/commands/Test-MtCisSpoDefaultSharingLink.mdx
new file mode 100644
index 000000000..edebb3ef1
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoDefaultSharingLink.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure link sharing is restricted in SharePoint and OneDrive"
+id: Test-MtCisSpoDefaultSharingLink
+title: Test-MtCisSpoDefaultSharingLink
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoDefaultSharingLink.ps1
+---
+
+## SYNOPSIS
+
+Ensure link sharing is restricted in SharePoint and OneDrive
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoDefaultSharingLink [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoDefaultSharingLink
+```
+
+Returns true if link sharing is restricted in SharePoint and OneDrive
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLink](https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLink)
diff --git a/website/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission.mdx b/website/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission.mdx
new file mode 100644
index 000000000..e1ffc815a
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure the SharePoint default sharing link permission is set"
+id: Test-MtCisSpoDefaultSharingLinkPermission
+title: Test-MtCisSpoDefaultSharingLinkPermission
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoDefaultSharingLinkPermission.ps1
+---
+
+## SYNOPSIS
+
+Ensure the SharePoint default sharing link permission is set
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoDefaultSharingLinkPermission [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.2.11 (L1) Ensure the SharePoint default sharing link permission is set
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoDefaultSharingLinkPermission
+```
+
+Returns true if the SharePoint default sharing link permission is set to View
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission](https://maester.dev/docs/commands/Test-MtCisSpoDefaultSharingLinkPermission)
diff --git a/website/docs/commands/Test-MtCisSpoGuestAccessExpiry.mdx b/website/docs/commands/Test-MtCisSpoGuestAccessExpiry.mdx
new file mode 100644
index 000000000..1480bba22
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoGuestAccessExpiry.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure guest access to a site or OneDrive will expire automatically"
+id: Test-MtCisSpoGuestAccessExpiry
+title: Test-MtCisSpoGuestAccessExpiry
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoGuestAccessExpiry.ps1
+---
+
+## SYNOPSIS
+
+Ensure guest access to a site or OneDrive will expire automatically
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoGuestAccessExpiry [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoGuestAccessExpiry
+```
+
+Returns true if guest access expiration is enabled and set to 30 days or less
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoGuestAccessExpiry](https://maester.dev/docs/commands/Test-MtCisSpoGuestAccessExpiry)
diff --git a/website/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem.mdx b/website/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem.mdx
new file mode 100644
index 000000000..fa1d8c865
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure that SharePoint guest users cannot share items they don't own"
+id: Test-MtCisSpoGuestCannotShareUnownedItem
+title: Test-MtCisSpoGuestCannotShareUnownedItem
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoGuestCannotShareUnownedItem.ps1
+---
+
+## SYNOPSIS
+
+Ensure that SharePoint guest users cannot share items they don't own
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoGuestCannotShareUnownedItem [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoGuestCannotShareUnownedItem
+```
+
+Returns true if SharePoint guest users cannot share items they don't own
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem](https://maester.dev/docs/commands/Test-MtCisSpoGuestCannotShareUnownedItem)
diff --git a/website/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile.mdx b/website/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile.mdx
new file mode 100644
index 000000000..dada85944
--- /dev/null
+++ b/website/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile.mdx
@@ -0,0 +1,68 @@
+---
+sidebar_class_name: hidden
+description: "Ensure Office 365 SharePoint infected files are disallowed for download"
+id: Test-MtCisSpoPreventDownloadMaliciousFile
+title: Test-MtCisSpoPreventDownloadMaliciousFile
+hide_title: false
+hide_table_of_contents: false
+custom_edit_url: https://github.com/maester365/maester/blob/main/powershell/public/Test-MtCisSpoPreventDownloadMaliciousFile.ps1
+---
+
+## SYNOPSIS
+
+Ensure Office 365 SharePoint infected files are disallowed for download
+
+## SYNTAX
+
+```powershell
+Test-MtCisSpoPreventDownloadMaliciousFile [-ProgressAction ] []
+```
+
+## DESCRIPTION
+
+7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
+CIS Microsoft 365 Foundations Benchmark v6.0.1
+
+## EXAMPLES
+
+### EXAMPLE 1
+
+```powershell
+Test-MtCisSpoPreventDownloadMaliciousFile
+```
+
+Returns true if Office 365 SharePoint infected files are disallowed for download
+
+## PARAMETERS
+
+### -ProgressAction
+
+\{\{ Fill ProgressAction Description \}\}
+
+```yaml
+Type: ActionPreference
+Parameter Sets: (All)
+Aliases: proga
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+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).
+
+## INPUTS
+
+## OUTPUTS
+
+### System.Boolean
+
+## NOTES
+
+## RELATED LINKS
+
+[https://maester.dev/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile](https://maester.dev/docs/commands/Test-MtCisSpoPreventDownloadMaliciousFile)
diff --git a/website/docs/commands/Test-MtConnection.mdx b/website/docs/commands/Test-MtConnection.mdx
index 9ce7cf2ed..520190261 100644
--- a/website/docs/commands/Test-MtConnection.mdx
+++ b/website/docs/commands/Test-MtConnection.mdx
@@ -31,7 +31,7 @@ Tests the connection for each service and returns $true if the session is connec
Test-MtConnection -Service All
```
-Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), and Microsoft Teams.
+Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), SharePoint Online (PnP), and Microsoft Teams.
Returns a Boolean value.
### EXAMPLE 2
@@ -40,7 +40,7 @@ Returns a Boolean value.
Test-MtConnection -Service All -Details
```
-Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), and Microsoft Teams.
+Checks if the current session is connected to all services including Azure, Microsoft Graph, Exchange Online, Exchange Online Protection (SecurityCompliance), SharePoint Online (PnP), and Microsoft Teams.
Returns a custom object that contains the connection details for all services.
### EXAMPLE 3
@@ -56,7 +56,7 @@ Checks if the current session is connected to Azure and returns a Boolean result
### -Service
The service to check the connection for.
-Valid values are 'All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'Graph', 'SecurityCompliance' (or 'EOP'), and 'Teams'.
+Valid values are 'All', 'Azure', 'AzureDevOps', 'ExchangeOnline', 'Graph', 'SecurityCompliance' (or 'EOP'), 'SharePointOnline', and 'Teams'.
Default is 'Graph'.
```yaml
diff --git a/website/docs/connect-maester/readme.md b/website/docs/connect-maester/readme.md
index b8392a9a9..dc7b54759 100644
--- a/website/docs/connect-maester/readme.md
+++ b/website/docs/connect-maester/readme.md
@@ -77,6 +77,32 @@ The `-DeviceCode` switch allows you to sign in using the device code flow. This
Connect-Maester -UseDeviceCode
```
+### Connect to SharePoint Online (optional)
+
+Maester includes SharePoint Online security tests that use the [PnP PowerShell](https://pnp.github.io/powershell/) module.
+
+Install the PnP PowerShell module if you haven't already:
+
+```powershell
+Install-Module PnP.PowerShell -Scope CurrentUser
+```
+
+A dedicated Entra ID app registration configured for PnP interactive login is required. See [Grant permissions to SharePoint Online](../sections/create-entra-app.md) for how to create or reuse one.
+
+Connect to SharePoint Online together with Microsoft Graph (the admin URL is auto-discovered from your tenant's initial domain):
+
+```powershell
+Connect-Maester -Service Graph,SharePointOnline -SharePointClientId ''
+```
+
+If auto-discovery does not work (e.g. in government or custom-domain tenants), supply the admin URL explicitly:
+
+```powershell
+Connect-Maester -Service Graph,SharePointOnline -SharePointClientId '' -SharePointAdminUrl 'https://contoso-admin.sharepoint.com'
+```
+
+If the PnP PowerShell module is not installed or there is no active connection, all SharePoint Online tests are skipped automatically.
+
### Connect to Azure, Exchange Online, Copilot Studio and Teams
`Connect-Maester` also provides options to connect to Azure, Copilot Studio (via the Dataverse API), Exchange Online and Teams for running tests that use the Azure PowerShell, Dataverse OData API, Exchange Online PowerShell or Teams PowerShell modules.
diff --git a/website/docs/installation.md b/website/docs/installation.md
index 653c9e91f..f8ae16199 100644
--- a/website/docs/installation.md
+++ b/website/docs/installation.md
@@ -28,10 +28,12 @@ To learn more about the `Invoke-Maester` cmdlet including how to filter tests, a
Maester includes optional [CISA](tests/cisa/) tests that require additional permissions and modules to run. These optional tests are skipped if the modules are not installed or there is no active connection.
-### Installing Azure, Exchange Online, and Teams modules
+> Also see [Create-entra-app](sections/create-entra-app.md)
+
+### Installing Azure, Exchange Online, Teams and PnP.PowerShell modules
```powershell
-Install-Module Az.Accounts, ExchangeOnlineManagement, MicrosoftTeams -Scope CurrentUser
+Install-Module Az.Accounts, ExchangeOnlineManagement, MicrosoftTeams, PnP.PowerShell -Scope CurrentUser
```
> The Security & Compliance PowerShell module is dependent on the ExchangeOnlineManagement `Connect-IPPSSession` cmdlet.
diff --git a/website/docs/sections/create-entra-app.md b/website/docs/sections/create-entra-app.md
index 8d17a3b8f..112fc2f96 100644
--- a/website/docs/sections/create-entra-app.md
+++ b/website/docs/sections/create-entra-app.md
@@ -43,6 +43,7 @@ The Exchange Online Role Based Access Control (RBAC) implementation utilizes ser
New-ServicePrincipal -AppId -ObjectId