Initial Commit
This commit is contained in:
3
helpers/Build-Help.ps1
Normal file
3
helpers/Build-Help.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
Import-Module .\output\module\M365FoundationsCISReport\*\*.psd1
|
||||
.\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir docs -template ".\helpers\psDoc-master\src\out-html-template.ps1"
|
||||
.\helpers\psDoc-master\src\psDoc.ps1 -moduleName M365FoundationsCISReport -outputDir ".\" -template ".\helpers\psDoc-master\src\out-markdown-template.ps1" -fileName ".\README.md"
|
@@ -0,0 +1,59 @@
|
||||
function Test-AntiPhishingPolicy_2.1.7_E5L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
|
||||
# Retrieve and validate the anti-phishing policies
|
||||
$antiPhishPolicies = Get-AntiPhishPolicy
|
||||
$validatedPolicies = $antiPhishPolicies | Where-Object {
|
||||
$_.Enabled -eq $true -and
|
||||
$_.PhishThresholdLevel -ge 2 -and
|
||||
$_.EnableMailboxIntelligenceProtection -eq $true -and
|
||||
$_.EnableMailboxIntelligence -eq $true -and
|
||||
$_.EnableSpoofIntelligence -eq $true
|
||||
}
|
||||
|
||||
# Check if there is at least one policy that meets the requirements
|
||||
$isCompliant = $validatedPolicies.Count -gt 0
|
||||
|
||||
# Prepare failure details if policies are not compliant
|
||||
$failureDetails = if (-not $isCompliant) {
|
||||
"No anti-phishing policy is fully compliant with CIS benchmark requirements."
|
||||
} else {
|
||||
"Compliant Anti-Phish Policy Names: " + ($validatedPolicies.Name -join ', ')
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.7"
|
||||
$auditResult.RecDescription = "Ensure that an anti-phishing policy has been created"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = $failureDetails
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "Anti-phishing policies do not meet CIS benchmark requirements." } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-AuditDisabledFalse_6.1.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.1.1 (L1) Ensure 'AuditDisabled' organizationally is set to 'False'
|
||||
# Pass if AuditDisabled is False. Fail otherwise.
|
||||
$auditDisabledConfig = Get-OrganizationConfig | Select-Object AuditDisabled
|
||||
$auditNotDisabled = -not $auditDisabledConfig.AuditDisabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditNotDisabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.1"
|
||||
$auditResult.RecDescription = "Ensure 'AuditDisabled' organizationally is set to 'False'"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditNotDisabled
|
||||
$auditResult.Details = if ($auditNotDisabled) { "Audit is not disabled organizationally" } else { "Audit is disabled organizationally" }
|
||||
$auditResult.FailureReason = if (-not $auditNotDisabled) { "AuditDisabled is set to True" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-AuditLogSearch_3.1.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 3.1.1 (L1) Ensure Microsoft 365 audit log search is Enabled
|
||||
# Pass if UnifiedAuditLogIngestionEnabled is True. Fail otherwise.
|
||||
$auditLogConfig = Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled
|
||||
$auditLogResult = $auditLogConfig.UnifiedAuditLogIngestionEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($auditLogResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "3.1.1"
|
||||
$auditResult.RecDescription = "Ensure Microsoft 365 audit log search is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect Audit Logs"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $auditLogResult
|
||||
$auditResult.Details = "UnifiedAuditLogIngestionEnabled: $($auditLogConfig.UnifiedAuditLogIngestionEnabled)"
|
||||
$auditResult.FailureReason = if (-not $auditLogResult) { "Audit log search is not enabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-BlockChannelEmails_8.1.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.1.2 (L1) Ensure users can't send emails to a channel email address
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$teamsClientConfig = Get-CsTeamsClientConfiguration -Identity Global
|
||||
$allowEmailIntoChannel = $teamsClientConfig.AllowEmailIntoChannel
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # This control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.1.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the benchmark
|
||||
$auditResult.IG2 = $false # Set based on the benchmark
|
||||
$auditResult.IG3 = $false # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure users can't send emails to a channel email address"
|
||||
$auditResult.Result = -not $allowEmailIntoChannel
|
||||
$auditResult.Details = "AllowEmailIntoChannel is set to $allowEmailIntoChannel"
|
||||
$auditResult.FailureReason = if ($allowEmailIntoChannel) { "Emails can be sent to a channel email address" } else { "N/A" }
|
||||
$auditResult.Status = if (-not $allowEmailIntoChannel) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
function Test-BlockMailForwarding_6.2.1_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "6.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure all forms of mail forwarding are blocked and/or disabled"
|
||||
}
|
||||
|
||||
process {
|
||||
# Verify that no rules are forwarding the email to external domains
|
||||
$transportRules = Get-TransportRule | Where-Object { $_.RedirectMessageTo -ne $null }
|
||||
$forwardingBlocked = $transportRules.Count -eq 0
|
||||
|
||||
$auditResult.Result = $forwardingBlocked
|
||||
$auditResult.Details = if ($transportRules.Count -gt 0) {
|
||||
$transportRules | ForEach-Object {
|
||||
"$($_.Name) redirects to $($_.RedirectMessageTo)"
|
||||
} -join " | "
|
||||
} else {
|
||||
"Step 1: No forwarding rules found. Please proceed with Step 2 described in CIS Benchmark."
|
||||
}
|
||||
$auditResult.FailureReason = if (-not $forwardingBlocked) {
|
||||
"Mail forwarding rules found: $($transportRules.Name -join ', ')"
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
$auditResult.Status = if ($forwardingBlocked) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return the result object
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,47 @@
|
||||
function Test-BlockSharedMailboxSignIn_1.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.2.2 (L1) Ensure sign-in to shared mailboxes is blocked
|
||||
# Pass if all shared mailboxes have AccountEnabled set to False.
|
||||
# Fail if any shared mailbox has AccountEnabled set to True.
|
||||
|
||||
$MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox
|
||||
$sharedMailboxDetails = $MBX | ForEach-Object { Get-AzureADUser -ObjectId $_.ExternalDirectoryObjectId }
|
||||
$enabledMailboxes = $sharedMailboxDetails | Where-Object { $_.AccountEnabled } | ForEach-Object { $_.DisplayName }
|
||||
$allBlocked = $enabledMailboxes.Count -eq 0
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Control is explicitly not mapped
|
||||
$auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8"
|
||||
$auditResult.Rec = "1.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Control is not mapped, hence IG1 is false
|
||||
$auditResult.IG2 = $false # Control is not mapped, hence IG2 is false
|
||||
$auditResult.IG3 = $false # Control is not mapped, hence IG3 is false
|
||||
$auditResult.RecDescription = "Ensure sign-in to shared mailboxes is blocked"
|
||||
$auditResult.Result = $allBlocked
|
||||
$auditResult.Details = "Enabled Mailboxes: $($enabledMailboxes -join ', ')"
|
||||
$auditResult.FailureReason = if ($allBlocked) { "N/A" } else { "Some mailboxes have sign-in enabled: $($enabledMailboxes -join ', ')" }
|
||||
$auditResult.Status = if ($allBlocked) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-CommonAttachmentFilter_2.1.2_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.2 (L1) Ensure the Common Attachment Types Filter is enabled
|
||||
# Pass if EnableFileFilter is set to True. Fail otherwise.
|
||||
|
||||
$attachmentFilter = Get-MalwareFilterPolicy -Identity Default | Select-Object EnableFileFilter
|
||||
$result = $attachmentFilter.EnableFileFilter
|
||||
$details = "File Filter Enabled: $($attachmentFilter.EnableFileFilter)"
|
||||
$failureReason = if ($result) { "N/A" } else { "Common Attachment Types Filter is disabled" }
|
||||
$status = if ($result) { "Pass" } else { "Fail" }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = $status
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.2"
|
||||
$auditResult.RecDescription = "Ensure the Common Attachment Types Filter is enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.6"
|
||||
$auditResult.CISDescription = "Block Unnecessary File Types"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
function Test-CustomerLockbox_1.3.6_E5L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.6 (L2) Ensure the customer lockbox feature is enabled
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object CustomerLockBoxEnabled
|
||||
$customerLockboxEnabled = $orgConfig.CustomerLockBoxEnabled
|
||||
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($customerLockboxEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "1.3.6"
|
||||
$auditResult.RecDescription = "Ensure the customer lockbox feature is enabled"
|
||||
$auditResult.CISControlVer = 'v8'
|
||||
$auditResult.CISControl = "0.0" # As per the snapshot provided, this is explicitly not mapped
|
||||
$auditResult.CISDescription = "Control not mapped to CIS Controls v7 or v8"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $customerLockboxEnabled
|
||||
$auditResult.Details = "Customer Lockbox Enabled: $customerLockboxEnabled"
|
||||
$auditResult.FailureReason = if ($customerLockboxEnabled) { "N/A" } else { "Customer lockbox feature is not enabled." }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-DialInBypassLobby_8.5.4_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.4 (L1) Ensure users dialing in can't bypass the lobby
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyPSTN = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowPSTNUsersToBypassLobby
|
||||
$PSTNBypassDisabled = -not $CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure users dialing in can't bypass the lobby"
|
||||
$auditResult.Result = $PSTNBypassDisabled
|
||||
$auditResult.Details = "AllowPSTNUsersToBypassLobby is set to $($CsTeamsMeetingPolicyPSTN.AllowPSTNUsersToBypassLobby)"
|
||||
$auditResult.FailureReason = if ($PSTNBypassDisabled) { "N/A" } else { "Users dialing in can bypass the lobby" }
|
||||
$auditResult.Status = if ($PSTNBypassDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-DisallowInfectedFilesDownload_7.3.1_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.3.1 (L2) Ensure Office 365 SharePoint infected files are disallowed for download
|
||||
$SPOTenantDisallowInfectedFileDownload = Get-SPOTenant | Select-Object DisallowInfectedFileDownload
|
||||
$isDisallowInfectedFileDownloadEnabled = $SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software"
|
||||
|
||||
$auditResult.Rec = "7.3.1"
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure Office 365 SharePoint infected files are disallowed for download"
|
||||
|
||||
$auditResult.Result = $isDisallowInfectedFileDownloadEnabled
|
||||
$auditResult.Details = "DisallowInfectedFileDownload: $($SPOTenantDisallowInfectedFileDownload.DisallowInfectedFileDownload)"
|
||||
$auditResult.FailureReason = if (-not $isDisallowInfectedFileDownloadEnabled) { "Downloading infected files is not disallowed." } else { "N/A" }
|
||||
$auditResult.Status = if ($isDisallowInfectedFileDownloadEnabled) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-EnableDKIM_2.1.9_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 2.1.9 (L1) Ensure DKIM is enabled for all Exchange Online Domains
|
||||
# Pass if Enabled is True for all domains. Fail if any domain has Enabled set to False.
|
||||
$dkimConfig = Get-DkimSigningConfig | Select-Object Domain, Enabled
|
||||
$dkimResult = ($dkimConfig | ForEach-Object { $_.Enabled }) -notcontains $false
|
||||
$dkimFailedDomains = $dkimConfig | Where-Object { -not $_.Enabled } | ForEach-Object { $_.Domain }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($dkimResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.9"
|
||||
$auditResult.RecDescription = "Ensure that DKIM is enabled for all Exchange Online Domains"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.5"
|
||||
$auditResult.CISDescription = "Implement DMARC"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $dkimResult
|
||||
$auditResult.Details = if (-not $dkimResult) { "DKIM not enabled for: $($dkimFailedDomains -join ', ')" } else { "All domains have DKIM enabled" }
|
||||
$auditResult.FailureReason = if (-not $dkimResult) { "DKIM is not enabled for some domains" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-ExternalNoControl_8.5.7_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.7 (L1) Ensure external participants can't give or request control
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyControl = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowExternalParticipantGiveRequestControl
|
||||
$externalControlRestricted = -not $CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure external participants can't give or request control"
|
||||
$auditResult.Result = $externalControlRestricted
|
||||
$auditResult.Details = "AllowExternalParticipantGiveRequestControl is set to $($CsTeamsMeetingPolicyControl.AllowExternalParticipantGiveRequestControl)"
|
||||
$auditResult.FailureReason = if ($externalControlRestricted) { "N/A" } else { "External participants can give or request control" }
|
||||
$auditResult.Status = if ($externalControlRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
function Test-ExternalSharingCalendars_1.3.3_E3L2_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.3 (L2) Ensure 'External sharing' of calendars is not available (Automated)
|
||||
$sharingPolicies = Get-SharingPolicy | Where-Object { $_.Domains -like '*CalendarSharing*' }
|
||||
|
||||
# Check if calendar sharing is disabled in all applicable policies
|
||||
$isExternalSharingDisabled = $true
|
||||
$sharingPolicyDetails = @()
|
||||
foreach ($policy in $sharingPolicies) {
|
||||
if ($policy.Enabled -eq $true) {
|
||||
$isExternalSharingDisabled = $false
|
||||
$sharingPolicyDetails += "$($policy.Name): Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "1.3.3"
|
||||
$auditResult.RecDescription = "Ensure 'External sharing' of calendars is not available"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
# The following IG values are placeholders. Replace with actual values when known.
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.CISControlVer = "v8"
|
||||
# Placeholder for CIS Control, to be replaced with the actual value when available
|
||||
$auditResult.CISControl = "4.8"
|
||||
$auditResult.CISDescription = "Uninstall or Disable Unnecessary Services on Enterprise Assets and Software"
|
||||
$auditResult.Result = $isExternalSharingDisabled
|
||||
$auditResult.Details = if ($isExternalSharingDisabled) { "Calendar sharing with external users is disabled." } else { "Enabled Sharing Policies: $($sharingPolicyDetails -join ', ')" }
|
||||
$auditResult.FailureReason = if ($isExternalSharingDisabled) { "N/A" } else { "Calendar sharing with external users is enabled in one or more policies." }
|
||||
$auditResult.Status = if ($isExternalSharingDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-GlobalAdminsCount_1.1.3_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.1.3 (L1) Ensure that between two and four global admins are designated
|
||||
# Pass if the count of global admins is between 2 and 4. Fail otherwise.
|
||||
|
||||
$globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'"
|
||||
$globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id
|
||||
$globalAdminCount = $globalAdmins.AdditionalProperties.Count
|
||||
$globalAdminUsernames = ($globalAdmins | ForEach-Object { $_.AdditionalProperties["displayName"] }) -join ', '
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "5.1"
|
||||
$auditResult.CISDescription = "Establish and Maintain an Inventory of Accounts"
|
||||
$auditResult.Rec = "1.1.3"
|
||||
$auditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.)
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true # Set based on the benchmark
|
||||
$auditResult.IG2 = $true # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure that between two and four global admins are designated"
|
||||
$auditResult.Result = $globalAdminCount -ge 2 -and $globalAdminCount -le 4
|
||||
$auditResult.Details = "Count: $globalAdminCount; Users: $globalAdminUsernames"
|
||||
$auditResult.FailureReason = if ($globalAdminCount -lt 2) { "Less than 2 global admins: $globalAdminUsernames" } elseif ($globalAdminCount -gt 4) { "More than 4 global admins: $globalAdminUsernames" } else { "N/A" }
|
||||
$auditResult.Status = if ($globalAdminCount -ge 2 -and $globalAdminCount -le 4) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-GuestAccessExpiration_7.2.9_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.9 (L1) Ensure guest access to a site or OneDrive will expire automatically
|
||||
$SPOTenantGuestAccess = Get-SPOTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays
|
||||
$isGuestAccessExpirationConfiguredCorrectly = $SPOTenantGuestAccess.ExternalUserExpirationRequired -and $SPOTenantGuestAccess.ExternalUserExpireInDays -le 30
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.9"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure guest access to a site or OneDrive will expire automatically"
|
||||
|
||||
$auditResult.Result = $isGuestAccessExpirationConfiguredCorrectly
|
||||
$auditResult.Details = "ExternalUserExpirationRequired: $($SPOTenantGuestAccess.ExternalUserExpirationRequired); ExternalUserExpireInDays: $($SPOTenantGuestAccess.ExternalUserExpireInDays)"
|
||||
$auditResult.FailureReason = if (-not $isGuestAccessExpirationConfiguredCorrectly) { "Guest access expiration is not configured to automatically expire within 30 days or less." } else { "N/A" }
|
||||
$auditResult.Status = if ($isGuestAccessExpirationConfiguredCorrectly) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
function Test-IdentifyExternalEmail_6.2.3_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.2.3 (L1) Ensure email from external senders is identified
|
||||
# Requirement is to have external sender tagging enabled
|
||||
|
||||
$externalInOutlook = Get-ExternalInOutlook
|
||||
$externalTaggingEnabled = ($externalInOutlook | ForEach-Object { $_.Enabled }) -contains $true
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($externalTaggingEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.2.3"
|
||||
$auditResult.RecDescription = "Ensure email from external senders is identified"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $externalTaggingEnabled
|
||||
$auditResult.Details = "Enabled: $($externalTaggingEnabled); AllowList: $($externalInOutlook.AllowList)"
|
||||
$auditResult.FailureReason = if (-not $externalTaggingEnabled) { "External sender tagging is disabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
function Test-LinkSharingRestrictions_7.2.7_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
# Test behavior in prod
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.7 (L1) Ensure link sharing is restricted in SharePoint and OneDrive
|
||||
$SPOTenantLinkSharing = Get-SPOTenant | Select-Object DefaultSharingLinkType
|
||||
$isLinkSharingRestricted = $SPOTenantLinkSharing.DefaultSharingLinkType -eq 'Direct' # Or 'SpecificPeople' as per the recommendation
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.7"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure link sharing is restricted in SharePoint and OneDrive"
|
||||
|
||||
$auditResult.Result = $isLinkSharingRestricted
|
||||
$auditResult.Details = "DefaultSharingLinkType: $($SPOTenantLinkSharing.DefaultSharingLinkType)"
|
||||
$auditResult.FailureReason = if (-not $isLinkSharingRestricted) { "Link sharing is not restricted to 'Specific people'. Current setting: $($SPOTenantLinkSharing.DefaultSharingLinkType)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isLinkSharingRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-MailTipsEnabled_6.5.2_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.5.2 (L2) Ensure MailTips are enabled for end users
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold
|
||||
$allTipsEnabled = $orgConfig.MailTipsAllTipsEnabled -and $orgConfig.MailTipsGroupMetricsEnabled -and $orgConfig.MailTipsLargeAudienceThreshold -eq 25
|
||||
$externalRecipientsTipsEnabled = $orgConfig.MailTipsExternalRecipientsTipsEnabled
|
||||
|
||||
# Since there is no direct CIS Control mapping, the control will be set as not applicable.
|
||||
$auditResult.CISControl = "N/A"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "6.5.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure MailTips are enabled for end users"
|
||||
|
||||
$auditResult.Result = $allTipsEnabled -and $externalRecipientsTipsEnabled
|
||||
$auditResult.Details = "MailTipsAllTipsEnabled: $($orgConfig.MailTipsAllTipsEnabled); MailTipsExternalRecipientsTipsEnabled: $($orgConfig.MailTipsExternalRecipientsTipsEnabled); MailTipsGroupMetricsEnabled: $($orgConfig.MailTipsGroupMetricsEnabled); MailTipsLargeAudienceThreshold: $($orgConfig.MailTipsLargeAudienceThreshold)"
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "One or more MailTips settings are not configured as required." } else { "N/A" }
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,111 @@
|
||||
function Test-MailboxAuditingE3_6.1.2_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$e3SkuPartNumbers = @("ENTERPRISEPACK", "OFFICESUBSCRIPTION")
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.2"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E3 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
Write-Verbose "Skipping already processed user: $($user.UserPrincipalName)"
|
||||
continue
|
||||
}
|
||||
try {
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE3 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e3SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E3 license."
|
||||
|
||||
if ($hasOfficeE3) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
# Mark the user as processed
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E3 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
function Test-MailboxAuditingE5_6.1.3_E5L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$AdminActions = @("ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$DelegateActions = @("ApplyRecord", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$OwnerActions = @("ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules")
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "6.1.3"
|
||||
$auditResult.RecDescription = "Ensure mailbox auditing for Office E5 users is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "8.2"
|
||||
$auditResult.CISDescription = "Collect audit logs."
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
|
||||
$allFailures = @()
|
||||
$allUsers = Get-AzureADUser -All $true
|
||||
$processedUsers = @{} # Dictionary to track processed users
|
||||
}
|
||||
|
||||
process {
|
||||
foreach ($user in $allUsers) {
|
||||
if ($processedUsers.ContainsKey($user.UserPrincipalName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
# Define SKU Part Numbers for Office E5 licenses
|
||||
$e5SkuPartNumbers = @("SPE_E5", "ENTERPRISEPREMIUM", "OFFICEE5")
|
||||
$licenseDetails = Get-MgUserLicenseDetail -UserId $user.UserPrincipalName
|
||||
$hasOfficeE5 = ($licenseDetails | Where-Object { $_.SkuPartNumber -in $e5SkuPartNumbers }).Count -gt 0
|
||||
Write-Verbose "Evaluating user $($user.UserPrincipalName) for Office E5 license."
|
||||
if ($hasOfficeE5) {
|
||||
$userUPN = $user.UserPrincipalName
|
||||
|
||||
$mailbox = Get-EXOMailbox -Identity $userUPN -PropertySets Audit
|
||||
|
||||
$missingActions = @()
|
||||
if ($mailbox.AuditEnabled) {
|
||||
foreach ($action in $AdminActions) {
|
||||
if ($mailbox.AuditAdmin -notcontains $action) { $missingActions += "Admin action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $DelegateActions) {
|
||||
if ($mailbox.AuditDelegate -notcontains $action) { $missingActions += "Delegate action '$action' missing" }
|
||||
}
|
||||
foreach ($action in $OwnerActions) {
|
||||
if ($mailbox.AuditOwner -notcontains $action) { $missingActions += "Owner action '$action' missing" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$allFailures += "$userUPN`: AuditEnabled - False"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($missingActions) {
|
||||
$formattedActions = Format-MissingActions $missingActions
|
||||
$allFailures += "$userUPN`: AuditEnabled - True; $formattedActions"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "User $($user.UserPrincipalName) passed the mailbox audit checks."
|
||||
}
|
||||
$processedUsers[$user.UserPrincipalName] = $true
|
||||
}
|
||||
else {
|
||||
# Adding verbose output to indicate the user does not have an E5 license
|
||||
Write-Verbose "User $($user.UserPrincipalName) does not have an Office E5 license."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not retrieve license details for user $($user.UserPrincipalName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
if ($allFailures.Count -eq 0) {
|
||||
Write-Verbose "All evaluated E5 users have correct mailbox audit settings."
|
||||
}
|
||||
$auditResult.Result = $allFailures.Count -eq 0
|
||||
$auditResult.Status = if ($auditResult.Result) { "Pass" } else { "Fail" }
|
||||
$auditResult.Details = if ($auditResult.Result) { "All Office E5 users have correct mailbox audit settings." } else { $allFailures -join " | " }
|
||||
$auditResult.FailureReason = if (-not $auditResult.Result) { "Audit issues detected." } else { "N/A" }
|
||||
}
|
||||
|
||||
end {
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
function Format-MissingActions {
|
||||
param ([array]$missingActions)
|
||||
|
||||
$actionGroups = @{
|
||||
"Admin" = @()
|
||||
"Delegate" = @()
|
||||
"Owner" = @()
|
||||
}
|
||||
|
||||
foreach ($action in $missingActions) {
|
||||
if ($action -match "(Admin|Delegate|Owner) action '([^']+)' missing") {
|
||||
$type = $matches[1]
|
||||
$actionName = $matches[2]
|
||||
$actionGroups[$type] += $actionName
|
||||
}
|
||||
}
|
||||
|
||||
$formattedResults = @()
|
||||
foreach ($type in $actionGroups.Keys) {
|
||||
if ($actionGroups[$type].Count -gt 0) {
|
||||
$formattedResults += "$($type) actions missing: $($actionGroups[$type] -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedResults -join '; '
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
function Test-ManagedApprovedPublicGroups_1.2.1_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.2.1 (L2) Ensure that only organizationally managed/approved public groups exist (Automated)
|
||||
|
||||
$allGroups = Get-MgGroup -All | Where-Object { $_.Visibility -eq "Public" } | Select-Object DisplayName, Visibility
|
||||
|
||||
# Check if there are public groups and if they are organizationally managed/approved
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "1.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true # Based on the provided CIS Control image, IG3 is not applicable
|
||||
$auditResult.RecDescription = "Ensure that only organizationally managed/approved public groups exist"
|
||||
|
||||
if ($null -eq $allGroups -or $allGroups.Count -eq 0) {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Details = "No public groups found."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
$auditResult.Status = "Pass"
|
||||
}
|
||||
else {
|
||||
$groupDetails = $allGroups | ForEach-Object { $_.DisplayName + " (" + $_.Visibility + ")" }
|
||||
$detailsString = $groupDetails -join ', '
|
||||
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Details = "Public groups found: $detailsString"
|
||||
$auditResult.FailureReason = "There are public groups present that are not organizationally managed/approved."
|
||||
$auditResult.Status = "Fail"
|
||||
}
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
function Test-MeetingChatNoAnonymous_8.5.5_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.5 (L2) Ensure meeting chat does not allow anonymous users
|
||||
# Name doesn't match profile level in benchmarks either.
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyChat = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property MeetingChatEnabledType
|
||||
$chatAnonDisabled = $CsTeamsMeetingPolicyChat.MeetingChatEnabledType -eq 'EnabledExceptAnonymous'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.5"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure meeting chat does not allow anonymous users"
|
||||
$auditResult.Result = $chatAnonDisabled
|
||||
$auditResult.Details = "MeetingChatEnabledType is set to $($CsTeamsMeetingPolicyChat.MeetingChatEnabledType)"
|
||||
$auditResult.FailureReason = if ($chatAnonDisabled) { "N/A" } else { "Meeting chat allows anonymous users" }
|
||||
$auditResult.Status = if ($chatAnonDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
function Test-ModernAuthExchangeOnline_6.5.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$CISAuditResult = [CISAuditResult]::new()
|
||||
# Initialization code
|
||||
}
|
||||
|
||||
process {
|
||||
try {
|
||||
# Ensuring the ExchangeOnlineManagement module is available
|
||||
|
||||
|
||||
# 6.5.1 (L1) Ensure modern authentication for Exchange Online is enabled
|
||||
$orgConfig = Get-OrganizationConfig | Select-Object -Property Name, OAuth2ClientProfileEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
|
||||
$CISAuditResult.CISControlVer = "v8"
|
||||
$CISAuditResult.CISControl = "3.10"
|
||||
$CISAuditResult.CISDescription = "Encrypt Sensitive Data in Transit"
|
||||
$CISAuditResult.IG1 = $false # As per CIS Control v8 mapping for IG1
|
||||
$CISAuditResult.IG2 = $true # As per CIS Control v8 mapping for IG2
|
||||
$CISAuditResult.IG3 = $true # As per CIS Control v8 mapping for IG3
|
||||
$CISAuditResult.ELevel = "E3" # Based on your environment (E3, E5, etc.)
|
||||
$CISAuditResult.Profile = "L1"
|
||||
$CISAuditResult.Rec = "6.5.1"
|
||||
$CISAuditResult.RecDescription = "Ensure modern authentication for Exchange Online is enabled (Automated)"
|
||||
$CISAuditResult.Result = $orgConfig.OAuth2ClientProfileEnabled
|
||||
$CISAuditResult.Details = $orgConfig | Out-String
|
||||
$CISAuditResult.FailureReason = if (-not $orgConfig.OAuth2ClientProfileEnabled) { "Modern authentication is disabled" } else { "N/A" }
|
||||
$CISAuditResult.Status = if ($orgConfig.OAuth2ClientProfileEnabled) { "Pass" } else { "Fail" }
|
||||
|
||||
|
||||
}
|
||||
catch {
|
||||
Write-Error "An error occurred while testing modern authentication for Exchange Online: $_"
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $CISAuditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,39 @@
|
||||
function Test-ModernAuthSharePoint_7.2.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.1 (L1) Ensure modern authentication for SharePoint applications is required
|
||||
$SPOTenant = Get-SPOTenant | Select-Object -Property LegacyAuthProtocolsEnabled
|
||||
$modernAuthForSPRequired = -not $SPOTenant.LegacyAuthProtocolsEnabled
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.10"
|
||||
$auditResult.CISDescription = "Encrypt Sensitive Data in Transit"
|
||||
$auditResult.Rec = "7.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Modern Authentication for SharePoint Applications"
|
||||
$auditResult.Result = $modernAuthForSPRequired
|
||||
$auditResult.Details = "LegacyAuthProtocolsEnabled: $($SPOTenant.LegacyAuthProtocolsEnabled)"
|
||||
$auditResult.FailureReason = if (-not $modernAuthForSPRequired) { "Legacy authentication protocols are enabled" } else { "N/A" }
|
||||
$auditResult.Status = if ($modernAuthForSPRequired) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-NoAnonymousMeetingJoin_8.5.1_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.1 (L2) Ensure anonymous users can't join a meeting
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$teamsMeetingPolicy = Get-CsTeamsMeetingPolicy -Identity Global
|
||||
$allowAnonymousUsersToJoinMeeting = $teamsMeetingPolicy.AllowAnonymousUsersToJoinMeeting
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure anonymous users can't join a meeting"
|
||||
$auditResult.Result = -not $allowAnonymousUsersToJoinMeeting
|
||||
$auditResult.Details = "AllowAnonymousUsersToJoinMeeting is set to $allowAnonymousUsersToJoinMeeting"
|
||||
$auditResult.FailureReason = if ($allowAnonymousUsersToJoinMeeting) { "Anonymous users are allowed to join meetings" } else { "N/A" }
|
||||
$auditResult.Status = if (-not $allowAnonymousUsersToJoinMeeting) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-NoAnonymousMeetingStart_8.5.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.2 (L1) Ensure anonymous users and dial-in callers can't start a meeting
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyAnonymous = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AllowAnonymousUsersToStartMeeting
|
||||
$anonymousStartDisabled = -not $CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure anonymous users and dial-in callers can't start a meeting"
|
||||
$auditResult.Result = $anonymousStartDisabled
|
||||
$auditResult.Details = "AllowAnonymousUsersToStartMeeting is set to $($CsTeamsMeetingPolicyAnonymous.AllowAnonymousUsersToStartMeeting)"
|
||||
$auditResult.FailureReason = if ($anonymousStartDisabled) { "N/A" } else { "Anonymous users and dial-in callers can start a meeting" }
|
||||
$auditResult.Status = if ($anonymousStartDisabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
function Test-NoWhitelistDomains_6.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.2.2 (L1) Ensure mail transport rules do not whitelist specific domains
|
||||
$whitelistedRules = Get-TransportRule | Where-Object { $_.SetSCL -eq -1 -and $_.SenderDomainIs -ne $null }
|
||||
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "6.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure mail transport rules do not whitelist specific domains"
|
||||
|
||||
if ($whitelistedRules) {
|
||||
$ruleDetails = $whitelistedRules | ForEach-Object { "{0}: {1}" -f $_.Name, ($_.SenderDomainIs -join ', ') }
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Details = "Whitelisted Rules: $($ruleDetails -join '; ')"
|
||||
$auditResult.FailureReason = "There are transport rules whitelisting specific domains."
|
||||
$auditResult.Status = "Fail"
|
||||
} else {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Details = "No transport rules whitelisting specific domains found."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
$auditResult.Status = "Pass"
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
function Test-NotifyMalwareInternal_2.1.3_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all 'Custom' malware filter policies and check notification settings
|
||||
$malwareNotifications = Get-MalwareFilterPolicy | Where-Object { $_.RecommendedPolicyType -eq 'Custom' }
|
||||
$policiesToReport = @()
|
||||
|
||||
foreach ($policy in $malwareNotifications) {
|
||||
if ($policy.EnableInternalSenderAdminNotifications -ne $true) {
|
||||
$policiesToReport += "$($policy.Identity): Notifications Disabled"
|
||||
}
|
||||
}
|
||||
|
||||
# Determine the result based on the presence of custom policies without notifications
|
||||
$result = $policiesToReport.Count -eq 0
|
||||
$details = if ($result) { "All custom malware policies have notifications enabled." } else { "Misconfigured Policies: $($policiesToReport -join ', ')" }
|
||||
$failureReason = if ($result) { "N/A" } else { "Some custom policies do not have notifications for internal users sending malware enabled." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.3"
|
||||
$auditResult.RecDescription = "Ensure notifications for internal users sending malware is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "17.5"
|
||||
$auditResult.CISDescription = "Assign Key Roles and Responsibilities"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-OneDriveContentRestrictions_7.2.4_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.4 (L2) Ensure OneDrive content sharing is restricted
|
||||
$SPOTenant = Get-SPOTenant | Select-Object OneDriveSharingCapability
|
||||
$isOneDriveSharingRestricted = $SPOTenant.OneDriveSharingCapability -eq 'Disabled'
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure OneDrive content sharing is restricted"
|
||||
|
||||
$auditResult.Result = $isOneDriveSharingRestricted
|
||||
$auditResult.Details = "OneDriveSharingCapability: $($SPOTenant.OneDriveSharingCapability)"
|
||||
$auditResult.FailureReason = if (-not $isOneDriveSharingRestricted) { "OneDrive content sharing is not restricted to 'Disabled'. Current setting: $($SPOTenant.OneDriveSharingCapability)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isOneDriveSharingRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-OneDriveSyncRestrictions_7.3.2_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.3.2 (L2) Ensure OneDrive sync is restricted for unmanaged devices
|
||||
$SPOTenantSyncClientRestriction = Get-SPOTenantSyncClientRestriction | Select-Object TenantRestrictionEnabled, AllowedDomainList
|
||||
$isSyncRestricted = $SPOTenantSyncClientRestriction.TenantRestrictionEnabled -and $SPOTenantSyncClientRestriction.AllowedDomainList
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.3.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure OneDrive sync is restricted for unmanaged devices"
|
||||
|
||||
$auditResult.Result = $isSyncRestricted
|
||||
$auditResult.Details = "TenantRestrictionEnabled: $($SPOTenantSyncClientRestriction.TenantRestrictionEnabled); AllowedDomainList: $($SPOTenantSyncClientRestriction.AllowedDomainList -join ', ')"
|
||||
$auditResult.FailureReason = if (-not $isSyncRestricted) { "OneDrive sync is not restricted to managed devices. TenantRestrictionEnabled should be True and AllowedDomainList should contain trusted domains GUIDs." } else { "N/A" }
|
||||
$auditResult.Status = if ($isSyncRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-OrgOnlyBypassLobby_8.5.3_E3L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.3 (L1) Ensure only people in my org can bypass the lobby
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyLobby = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property AutoAdmittedUsers
|
||||
$lobbyBypassRestricted = $CsTeamsMeetingPolicyLobby.AutoAdmittedUsers -eq 'EveryoneInCompanyExcludingGuests'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "6.8"
|
||||
$auditResult.CISDescription = "Define and Maintain Role-Based Access Control"
|
||||
$auditResult.Rec = "8.5.3"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $true # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure only people in my org can bypass the lobby"
|
||||
$auditResult.Result = $lobbyBypassRestricted
|
||||
$auditResult.Details = "AutoAdmittedUsers is set to $($CsTeamsMeetingPolicyLobby.AutoAdmittedUsers)"
|
||||
$auditResult.FailureReason = if ($lobbyBypassRestricted) { "N/A" } else { "External participants can bypass the lobby" }
|
||||
$auditResult.Status = if ($lobbyBypassRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-OrganizersPresent_8.5.6_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.5.6 (L2) Ensure only organizers and co-organizers can present
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$CsTeamsMeetingPolicyPresenters = Get-CsTeamsMeetingPolicy -Identity Global | Select-Object -Property DesignatedPresenterRoleMode
|
||||
$presenterRoleRestricted = $CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode -eq 'OrganizerOnlyUserOverride'
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.5.6"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure only organizers and co-organizers can present"
|
||||
$auditResult.Result = $presenterRoleRestricted
|
||||
$auditResult.Details = "DesignatedPresenterRoleMode is set to $($CsTeamsMeetingPolicyPresenters.DesignatedPresenterRoleMode)"
|
||||
$auditResult.FailureReason = if ($presenterRoleRestricted) { "N/A" } else { "Others besides organizers and co-organizers can present" }
|
||||
$auditResult.Status = if ($presenterRoleRestricted) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
function Test-PasswordHashSync_5.1.8.1_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 5.1.8.1 (L1) Ensure password hash sync is enabled for hybrid deployments
|
||||
# Pass if OnPremisesSyncEnabled is True. Fail otherwise.
|
||||
$passwordHashSync = Get-MgOrganization | Select-Object OnPremisesSyncEnabled
|
||||
$hashSyncResult = $passwordHashSync.OnPremisesSyncEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($hashSyncResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "5.1.8.1"
|
||||
$auditResult.RecDescription = "Ensure password hash sync is enabled for hybrid deployments"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "6.7"
|
||||
$auditResult.CISDescription = "Centralize Access Control"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $hashSyncResult
|
||||
$auditResult.Details = "OnPremisesSyncEnabled: $($passwordHashSync.OnPremisesSyncEnabled)"
|
||||
$auditResult.FailureReason = if (-not $hashSyncResult) { "Password hash sync for hybrid deployments is not enabled" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
function Test-PasswordNeverExpirePolicy_1.3.1_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$DomainName # DomainName parameter is now mandatory
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 1.3.1 (L1) Ensure the 'Password expiration policy' is set to 'Set passwords to never expire'
|
||||
# Pass if PasswordValidityPeriodInDays is 0.
|
||||
# Fail otherwise.
|
||||
|
||||
$passwordPolicy = Get-MgDomain -DomainId $DomainName | Select-Object PasswordValidityPeriodInDays
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Rec = "1.3.1"
|
||||
$auditResult.RecDescription = "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire'"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "5.2"
|
||||
$auditResult.CISDescription = "Use Unique Passwords"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true # All are true
|
||||
$auditResult.Result = $passwordPolicy.PasswordValidityPeriodInDays -eq 0
|
||||
$auditResult.Details = "Validity Period: $($passwordPolicy.PasswordValidityPeriodInDays) days"
|
||||
$auditResult.FailureReason = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "N/A" } else { "Password expiration is not set to never expire" }
|
||||
$auditResult.Status = if ($passwordPolicy.PasswordValidityPeriodInDays -eq 0) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-ReauthWithCode_7.2.10_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.10 (L1) Ensure reauthentication with verification code is restricted
|
||||
$SPOTenantReauthentication = Get-SPOTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays
|
||||
$isReauthenticationRestricted = $SPOTenantReauthentication.EmailAttestationRequired -and $SPOTenantReauthentication.EmailAttestationReAuthDays -le 15
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.10"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure reauthentication with verification code is restricted"
|
||||
|
||||
$auditResult.Result = $isReauthenticationRestricted
|
||||
$auditResult.Details = "EmailAttestationRequired: $($SPOTenantReauthentication.EmailAttestationRequired); EmailAttestationReAuthDays: $($SPOTenantReauthentication.EmailAttestationReAuthDays)"
|
||||
$auditResult.FailureReason = if (-not $isReauthenticationRestricted) { "Reauthentication with verification code does not require reauthentication within 15 days or less." } else { "N/A" }
|
||||
$auditResult.Status = if ($isReauthenticationRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-ReportSecurityInTeams_8.6.1_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.6.1 (L1) Ensure users can report security concerns in Teams
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
# Connect to Exchange Online PowerShell using Connect-ExchangeOnline
|
||||
|
||||
$CsTeamsMessagingPolicy = Get-CsTeamsMessagingPolicy -Identity Global | Select-Object -Property AllowSecurityEndUserReporting
|
||||
$ReportSubmissionPolicy = Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportChatMessageToCustomizedAddressEnabled
|
||||
|
||||
$securityReportEnabled = $CsTeamsMessagingPolicy.AllowSecurityEndUserReporting -and
|
||||
$ReportSubmissionPolicy.ReportJunkToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportPhishToCustomizedAddress -and
|
||||
$ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.6.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure users can report security concerns in Teams"
|
||||
$auditResult.Result = $securityReportEnabled
|
||||
$auditResult.Details = "AllowSecurityEndUserReporting: $($CsTeamsMessagingPolicy.AllowSecurityEndUserReporting); " +
|
||||
"ReportJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportJunkToCustomizedAddress); " +
|
||||
"ReportNotJunkToCustomizedAddress: $($ReportSubmissionPolicy.ReportNotJunkToCustomizedAddress); " +
|
||||
"ReportPhishToCustomizedAddress: $($ReportSubmissionPolicy.ReportPhishToCustomizedAddress); " +
|
||||
"ReportChatMessageToCustomizedAddressEnabled: $($ReportSubmissionPolicy.ReportChatMessageToCustomizedAddressEnabled)"
|
||||
$auditResult.FailureReason = if (-not $securityReportEnabled) { "Users cannot report security concerns in Teams due to one or more incorrect settings" } else { "N/A" }
|
||||
$auditResult.Status = if ($securityReportEnabled) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
function Test-RestrictCustomScripts_7.3.4_E3L1_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# .TODO Test behavior in Prod
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# CIS 2.7 Ensure custom script execution is restricted on site collections
|
||||
# Pass if DenyAddAndCustomizePages is set to true. Fail otherwise.
|
||||
|
||||
# Connect to SharePoint Online using Connect-SPOService
|
||||
|
||||
$SPOSitesCustomScript = Get-SPOSite | Select-Object Title, Url, DenyAddAndCustomizePages
|
||||
$customScriptDisabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -eq 'Enabled' }
|
||||
$customScriptEnabledSites = $SPOSitesCustomScript | Where-Object { $_.DenyAddAndCustomizePages -ne 'Enabled' }
|
||||
$customScriptDisabledResult = $customScriptEnabledSites.Count -eq 0
|
||||
|
||||
# Correctly gathering details for sites with custom scripts enabled
|
||||
$customScriptEnabledDetails = $customScriptEnabledSites | ForEach-Object { "$($_.Title) ($($_.Url)): Custom Script - $($_.DenyAddAndCustomizePages)" }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "2.7"
|
||||
$auditResult.CISDescription = "Allowlist Authorized Scripts"
|
||||
$auditResult.Rec = "7.3.4"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false # Set based on the benchmark
|
||||
$auditResult.IG2 = $false # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure custom script execution is restricted on site collections"
|
||||
$auditResult.Result = $customScriptDisabledResult
|
||||
$auditResult.Details = if (-not $customScriptDisabledResult) { $customScriptEnabledDetails -join "; " } else { "All site collections have custom script execution restricted" }
|
||||
$auditResult.FailureReason = if (-not $customScriptDisabledResult) { "The following site collections have custom script execution enabled: " + ($customScriptEnabledDetails -join "; ") } else { "N/A" }
|
||||
$auditResult.Status = if ($customScriptDisabledResult) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-RestrictExternalSharing_7.2.3_E3L1_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.3 (L1) Ensure external content sharing is restricted
|
||||
$SPOTenantSharingCapability = Get-SPOTenant | Select-Object SharingCapability
|
||||
$isRestricted = $SPOTenantSharingCapability.SharingCapability -in @('ExternalUserSharingOnly', 'ExistingExternalUserSharingOnly', 'Disabled')
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.3"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure external content sharing is restricted"
|
||||
|
||||
$auditResult.Result = $isRestricted
|
||||
$auditResult.Details = "SharingCapability: $($SPOTenantSharingCapability.SharingCapability)"
|
||||
$auditResult.FailureReason = if (-not $isRestricted) { "External content sharing is not adequately restricted. Current setting: $($SPOTenantSharingCapability.SharingCapability)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
function Test-RestrictOutlookAddins_6.3.1_E3L2_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters could include credentials or other necessary data
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$customPolicyFailures = @()
|
||||
$defaultPolicyFailureDetails = @()
|
||||
$relevantRoles = @('My Custom Apps', 'My Marketplace Apps', 'My ReadWriteMailbox Apps')
|
||||
}
|
||||
|
||||
process {
|
||||
# Main functionality
|
||||
# 6.3.1 (L2) Ensure users installing Outlook add-ins is not allowed
|
||||
|
||||
# Check all mailboxes for custom policies with unallowed add-ins
|
||||
$roleAssignmentPolicies = Get-EXOMailbox | Select-Object -Unique RoleAssignmentPolicy
|
||||
|
||||
if ($roleAssignmentPolicies.RoleAssignmentPolicy) {
|
||||
foreach ($policy in $roleAssignmentPolicies) {
|
||||
if ($policy.RoleAssignmentPolicy) {
|
||||
$rolePolicyDetails = Get-RoleAssignmentPolicy -Identity $policy.RoleAssignmentPolicy
|
||||
$foundRoles = $rolePolicyDetails.AssignedRoles | Where-Object { $_ -in $relevantRoles }
|
||||
if ($foundRoles) {
|
||||
$customPolicyFailures += "Policy: $($policy.RoleAssignmentPolicy): Roles: $($foundRoles -join ', ')"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check Default Role Assignment Policy
|
||||
$defaultPolicy = Get-RoleAssignmentPolicy "Default Role Assignment Policy"
|
||||
$defaultPolicyRoles = $defaultPolicy.AssignedRoles | Where-Object { $_ -in $relevantRoles }
|
||||
if ($defaultPolicyRoles) {
|
||||
$defaultPolicyFailureDetails = $defaultPolicyRoles
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
# Prepare result object
|
||||
$auditResult.Rec = "6.3.1"
|
||||
$auditResult.CISControl = "9.4"
|
||||
$auditResult.CISDescription = "Restrict Unnecessary or Unauthorized Browser and Email Client Extensions"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure users installing Outlook add-ins is not allowed"
|
||||
|
||||
$detailsString = ""
|
||||
if ($customPolicyFailures) {
|
||||
$detailsString += "Custom Policy Failures: `n"
|
||||
foreach ($failure in $customPolicyFailures) {
|
||||
$detailsString += "`t$failure`n"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$detailsString += "Custom Policy Failures: None`n"
|
||||
}
|
||||
|
||||
$detailsString += "Default Role Assignment Policy: "
|
||||
if ($defaultPolicyFailureDetails) {
|
||||
$detailsString += "$($defaultPolicyFailureDetails -join ', ')"
|
||||
}
|
||||
else {
|
||||
$detailsString += "Compliant"
|
||||
}
|
||||
|
||||
if ($customPolicyFailures -or $defaultPolicyFailureDetails) {
|
||||
$auditResult.Result = $false
|
||||
$auditResult.Status = "Fail"
|
||||
$auditResult.Details = $detailsString
|
||||
$auditResult.FailureReason = "Unauthorized Outlook add-ins found in custom or default policies."
|
||||
}
|
||||
else {
|
||||
$auditResult.Result = $true
|
||||
$auditResult.Status = "Pass"
|
||||
$auditResult.Details = "No unauthorized Outlook add-ins found in custom or default policies."
|
||||
$auditResult.FailureReason = "N/A"
|
||||
}
|
||||
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
function Test-RestrictStorageProvidersOutlook_6.5.3_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 6.5.3 (L2) Ensure additional storage providers are restricted in Outlook on the web
|
||||
$owaPolicies = Get-OwaMailboxPolicy
|
||||
$allPoliciesRestricted = $owaPolicies | ForEach-Object { $_.AdditionalStorageProvidersAvailable } | ForEach-Object { -not $_ }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "6.5.3"
|
||||
$auditResult.ELevel = "E3" # Based on your environment
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure additional storage providers are restricted in Outlook on the web"
|
||||
$auditResult.Result = $allPoliciesRestricted
|
||||
$auditResult.Details = if($allPoliciesRestricted) {
|
||||
"All OwaMailbox policies restrict AdditionalStorageProvidersAvailable"
|
||||
} else {
|
||||
$nonCompliantPolicies = $owaPolicies | Where-Object { $_.AdditionalStorageProvidersAvailable } | Select-Object -ExpandProperty Name
|
||||
"Non-compliant OwaMailbox policies: $($nonCompliantPolicies -join ', ')"
|
||||
}
|
||||
$auditResult.FailureReason = if(-not $allPoliciesRestricted) { "One or more OwaMailbox policies allow AdditionalStorageProvidersAvailable." } else { "N/A" }
|
||||
$auditResult.Status = if($allPoliciesRestricted) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Additional helper functions (if any)
|
||||
|
@@ -0,0 +1,43 @@
|
||||
function Test-RestrictTenantCreation_5.1.2.3_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 5.1.2.3 (L1) Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'
|
||||
# Pass if AllowedToCreateTenants is False. Fail otherwise.
|
||||
$tenantCreationPolicy = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object AllowedToCreateTenants
|
||||
$tenantCreationResult = -not $tenantCreationPolicy.AllowedToCreateTenants
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($tenantCreationResult) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "5.1.2.3"
|
||||
$auditResult.RecDescription = "Ensure 'Restrict non-admin users from creating tenants' is set to 'Yes'"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.Result = $tenantCreationResult
|
||||
$auditResult.Details = "AllowedToCreateTenants: $($tenantCreationPolicy.AllowedToCreateTenants)"
|
||||
$auditResult.FailureReason = if (-not $tenantCreationResult) { "Non-admin users can create tenants" } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
function Test-SafeAttachmentsPolicy_2.1.4_E5L2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all Safe Attachment policies where Enable is set to True
|
||||
$safeAttachmentPolicies = Get-SafeAttachmentPolicy | Where-Object { $_.Enable -eq $true }
|
||||
|
||||
# If there are any enabled policies, the result is Pass. If not, it's Fail.
|
||||
$result = $safeAttachmentPolicies -ne $null -and $safeAttachmentPolicies.Count -gt 0
|
||||
$details = if ($result) {
|
||||
"Enabled Safe Attachments Policies: $($safeAttachmentPolicies.Name -join ', ')"
|
||||
} else {
|
||||
"No Safe Attachments Policies are enabled."
|
||||
}
|
||||
$failureReason = if ($result) { "N/A" } else { "Safe Attachments policy is not enabled." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.4"
|
||||
$auditResult.RecDescription = "Ensure Safe Attachments policy is enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-SafeAttachmentsTeams_2.1.5_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve the ATP policies for Office 365 and check Safe Attachments settings
|
||||
$atpPolicies = Get-AtpPolicyForO365
|
||||
|
||||
# Check if the required ATP policies are enabled
|
||||
$atpPolicyResult = $atpPolicies | Where-Object {
|
||||
$_.EnableATPForSPOTeamsODB -eq $true -and
|
||||
$_.EnableSafeDocs -eq $true -and
|
||||
$_.AllowSafeDocsOpen -eq $false
|
||||
}
|
||||
|
||||
# Determine the result based on the ATP policy settings
|
||||
$result = $null -ne $atpPolicyResult
|
||||
$details = if ($result) {
|
||||
"ATP for SharePoint, OneDrive, and Teams is enabled with correct settings."
|
||||
} else {
|
||||
"ATP for SharePoint, OneDrive, and Teams is not enabled with correct settings."
|
||||
}
|
||||
$failureReason = if ($result) { "N/A" } else { "ATP policy for SharePoint, OneDrive, and Microsoft Teams is not correctly configured." }
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.5"
|
||||
$auditResult.RecDescription = "Ensure Safe Attachments for SharePoint, OneDrive, and Microsoft Teams is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "9.7, 10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Email Server Anti-Malware Protections, Deploy and Maintain Anti-Malware Software"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = $failureReason
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
function Test-SafeLinksOfficeApps_2.1.1_E5L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Retrieve all Safe Links policies
|
||||
$policies = Get-SafeLinksPolicy
|
||||
|
||||
# Initialize the details collection
|
||||
$misconfiguredDetails = @()
|
||||
|
||||
foreach ($policy in $policies) {
|
||||
# Get the detailed configuration of each policy
|
||||
$policyDetails = Get-SafeLinksPolicy -Identity $policy.Name
|
||||
|
||||
# Check each required property and record failures
|
||||
$failures = @()
|
||||
if ($policyDetails.EnableSafeLinksForEmail -ne $true) { $failures += "EnableSafeLinksForEmail: False" }
|
||||
if ($policyDetails.EnableSafeLinksForTeams -ne $true) { $failures += "EnableSafeLinksForTeams: False" }
|
||||
if ($policyDetails.EnableSafeLinksForOffice -ne $true) { $failures += "EnableSafeLinksForOffice: False" }
|
||||
if ($policyDetails.TrackClicks -ne $true) { $failures += "TrackClicks: False" }
|
||||
if ($policyDetails.AllowClickThrough -ne $false) { $failures += "AllowClickThrough: True" }
|
||||
if ($policyDetails.ScanUrls -ne $true) { $failures += "ScanUrls: False" }
|
||||
if ($policyDetails.EnableForInternalSenders -ne $true) { $failures += "EnableForInternalSenders: False" }
|
||||
if ($policyDetails.DeliverMessageAfterScan -ne $true) { $failures += "DeliverMessageAfterScan: False" }
|
||||
if ($policyDetails.DisableUrlRewrite -ne $false) { $failures += "DisableUrlRewrite: True" }
|
||||
|
||||
# Only add details for policies that have misconfigurations
|
||||
if ($failures.Count -gt 0) {
|
||||
$misconfiguredDetails += "Policy: $($policy.Name); Failures: $($failures -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
# Prepare the final result
|
||||
$result = $misconfiguredDetails.Count -eq 0
|
||||
$details = if ($result) { "All Safe Links policies are correctly configured." } else { $misconfiguredDetails -join ' | ' }
|
||||
|
||||
# Create the audit result object
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($result) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E5"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.Rec = "2.1.1"
|
||||
$auditResult.RecDescription = "Ensure Safe Links for Office Applications is Enabled"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "10.1"
|
||||
$auditResult.CISDescription = "Deploy and Maintain Anti-Malware Software"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $result
|
||||
$auditResult.Details = $details
|
||||
$auditResult.FailureReason = if ($result) { "N/A" } else { "The following Safe Links policies settings do not meet the recommended configuration: $($misconfiguredDetails -join ' | ')" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
function Test-SharePointAADB2B_7.2.2_E3L1 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.2 (L1) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled
|
||||
$SPOTenantAzureADB2B = Get-SPOTenant | Select-Object EnableAzureADB2BIntegration
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0"
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
|
||||
$auditResult.Rec = "7.2.2"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $false
|
||||
$auditResult.IG3 = $false
|
||||
$auditResult.RecDescription = "Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled"
|
||||
|
||||
$auditResult.Result = $SPOTenantAzureADB2B.EnableAzureADB2BIntegration
|
||||
$auditResult.Details = "EnableAzureADB2BIntegration: $($SPOTenantAzureADB2B.EnableAzureADB2BIntegration)"
|
||||
$auditResult.FailureReason = if (-not $SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Azure AD B2B integration is not enabled" } else { "N/A" }
|
||||
$auditResult.Status = if ($SPOTenantAzureADB2B.EnableAzureADB2BIntegration) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-SharePointExternalSharingDomains_7.2.6_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.6 (L2) Ensure SharePoint external sharing is managed through domain whitelist/blacklists
|
||||
$SPOTenant = Get-SPOTenant | Select-Object SharingDomainRestrictionMode, SharingAllowedDomainList
|
||||
$isDomainRestrictionConfigured = $SPOTenant.SharingDomainRestrictionMode -eq 'AllowList'
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.6"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure SharePoint external sharing is managed through domain whitelist/blacklists"
|
||||
|
||||
$auditResult.Result = $isDomainRestrictionConfigured
|
||||
$auditResult.Details = "SharingDomainRestrictionMode: $($SPOTenant.SharingDomainRestrictionMode); SharingAllowedDomainList: $($SPOTenant.SharingAllowedDomainList)"
|
||||
$auditResult.FailureReason = if (-not $isDomainRestrictionConfigured) { "Domain restrictions for SharePoint external sharing are not configured to 'AllowList'. Current setting: $($SPOTenant.SharingDomainRestrictionMode)" } else { "N/A" }
|
||||
$auditResult.Status = if ($isDomainRestrictionConfigured) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
function Test-SharePointGuestsItemSharing_7.2.5_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Define your parameters here
|
||||
)
|
||||
|
||||
begin {
|
||||
# Initialization code
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
}
|
||||
|
||||
process {
|
||||
# 7.2.5 (L2) Ensure that SharePoint guest users cannot share items they don't own
|
||||
$SPOTenant = Get-SPOTenant | Select-Object PreventExternalUsersFromResharing
|
||||
$isGuestResharingPrevented = $SPOTenant.PreventExternalUsersFromResharing
|
||||
|
||||
# Populate the auditResult object with the required properties
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
|
||||
$auditResult.Rec = "7.2.5"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.RecDescription = "Ensure that SharePoint guest users cannot share items they don't own"
|
||||
|
||||
$auditResult.Result = $isGuestResharingPrevented
|
||||
$auditResult.Details = "PreventExternalUsersFromResharing: $isGuestResharingPrevented"
|
||||
$auditResult.FailureReason = if (-not $isGuestResharingPrevented) { "Guest users can reshare items they don't own." } else { "N/A" }
|
||||
$auditResult.Status = if ($isGuestResharingPrevented) { "Pass" } else { "Fail" }
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResult
|
||||
return $auditResult
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-SpamPolicyAdminNotify_2.1.6_E3L1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# Get the default hosted outbound spam filter policy
|
||||
$hostedOutboundSpamFilterPolicy = Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.IsDefault -eq $true }
|
||||
|
||||
# Check if both settings are enabled
|
||||
$bccSuspiciousOutboundMailEnabled = $hostedOutboundSpamFilterPolicy.BccSuspiciousOutboundMail
|
||||
$notifyOutboundSpamEnabled = $hostedOutboundSpamFilterPolicy.NotifyOutboundSpam
|
||||
$areSettingsEnabled = $bccSuspiciousOutboundMailEnabled -and $notifyOutboundSpamEnabled
|
||||
|
||||
# Prepare failure details if any setting is not enabled
|
||||
$failureDetails = @()
|
||||
if (-not $bccSuspiciousOutboundMailEnabled) {
|
||||
$failureDetails += "BccSuspiciousOutboundMail is not enabled."
|
||||
}
|
||||
if (-not $notifyOutboundSpamEnabled) {
|
||||
$failureDetails += "NotifyOutboundSpam is not enabled."
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.Status = if ($areSettingsEnabled) { "Pass" } else { "Fail" }
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L1"
|
||||
$auditResult.Rec = "2.1.6"
|
||||
$auditResult.RecDescription = "Ensure Exchange Online Spam Policies are set to notify administrators"
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "17.5"
|
||||
$auditResult.CISDescription = "Assign Key Roles and Responsibilities"
|
||||
$auditResult.IG1 = $false
|
||||
$auditResult.IG2 = $true
|
||||
$auditResult.IG3 = $true
|
||||
$auditResult.Result = $areSettingsEnabled
|
||||
$auditResult.Details = if ($areSettingsEnabled) { "Both BccSuspiciousOutboundMail and NotifyOutboundSpam are enabled." } else { $failureDetails -join ' ' }
|
||||
$auditResult.FailureReason = if (-not $areSettingsEnabled) { "One or both spam policies are not set to notify administrators." } else { "N/A" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,52 @@
|
||||
function Test-TeamsExternalAccess_8.2.1_E3L2 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be defined here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.2.1 (L1) Ensure 'external access' is restricted in the Teams admin center
|
||||
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
$externalAccessConfig = Get-CsTenantFederationConfiguration
|
||||
|
||||
$allowedDomainsLimited = $false
|
||||
if ($externalAccessConfig.AllowFederatedUsers -and $externalAccessConfig.AllowedDomains -and $externalAccessConfig.AllowedDomains.AllowedDomain.Count -gt 0) {
|
||||
$allowedDomainsLimited = $true
|
||||
}
|
||||
|
||||
# Check if the configurations are as recommended
|
||||
$isCompliant = -not $externalAccessConfig.AllowTeamsConsumer -and -not $externalAccessConfig.AllowPublicUsers -and (-not $externalAccessConfig.AllowFederatedUsers -or $allowedDomainsLimited)
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "0.0" # The control is Explicitly Not Mapped as per the image provided
|
||||
$auditResult.CISDescription = "Explicitly Not Mapped"
|
||||
$auditResult.Rec = "8.2.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG2 = $false # Set based on the CIS Controls image
|
||||
$auditResult.IG3 = $false # Set based on the CIS Controls image
|
||||
$auditResult.RecDescription = "Ensure 'external access' is restricted in the Teams admin center"
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = "AllowTeamsConsumer: $($externalAccessConfig.AllowTeamsConsumer); AllowPublicUsers: $($externalAccessConfig.AllowPublicUsers); AllowFederatedUsers: $($externalAccessConfig.AllowFederatedUsers); AllowedDomains limited: $allowedDomainsLimited"
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "One or more external access configurations are not compliant." } else { "N/A" }
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
function Test-TeamsExternalFileSharing_8.1.1_E3L2_IG1_IG2_IG3 {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
# Parameters can be added here if needed
|
||||
)
|
||||
|
||||
begin {
|
||||
# Dot source the class script
|
||||
. ".\source\Classes\CISAuditResult.ps1"
|
||||
$auditResults = @()
|
||||
}
|
||||
|
||||
process {
|
||||
# 8.1.1 (L2) Ensure external file sharing in Teams is enabled for only approved cloud storage services
|
||||
# Connect to Teams PowerShell using Connect-MicrosoftTeams
|
||||
|
||||
# Assuming that 'approvedProviders' is a list of approved cloud storage service names
|
||||
# This list must be defined according to your organization's approved cloud storage services
|
||||
$approvedProviders = @("AllowDropBox", "AllowBox", "AllowGoogleDrive", "AllowShareFile", "AllowEgnyte")
|
||||
$clientConfig = Get-CsTeamsClientConfiguration
|
||||
|
||||
$isCompliant = $true
|
||||
$nonCompliantProviders = @()
|
||||
|
||||
foreach ($provider in $approvedProviders) {
|
||||
if (-not $clientConfig.$provider) {
|
||||
$isCompliant = $false
|
||||
$nonCompliantProviders += $provider
|
||||
}
|
||||
}
|
||||
|
||||
# Create an instance of CISAuditResult and populate it
|
||||
$auditResult = [CISAuditResult]::new()
|
||||
$auditResult.CISControlVer = "v8"
|
||||
$auditResult.CISControl = "3.3"
|
||||
$auditResult.CISDescription = "Configure Data Access Control Lists"
|
||||
$auditResult.Rec = "8.1.1"
|
||||
$auditResult.ELevel = "E3"
|
||||
$auditResult.Profile = "L2"
|
||||
$auditResult.IG1 = $true # Set based on the benchmark
|
||||
$auditResult.IG2 = $true # Set based on the benchmark
|
||||
$auditResult.IG3 = $true # Set based on the benchmark
|
||||
$auditResult.RecDescription = "Ensure external file sharing in Teams is enabled for only approved cloud storage services"
|
||||
$auditResult.Result = $isCompliant
|
||||
$auditResult.Details = if (-not $isCompliant) { "Non-approved providers enabled: $($nonCompliantProviders -join ', ')" } else { "All cloud storage services are approved providers" }
|
||||
$auditResult.FailureReason = if (-not $isCompliant) { "The following non-approved providers are enabled: $($nonCompliantProviders -join ', ')" } else { "N/A" }
|
||||
$auditResult.Status = if ($isCompliant) { "Pass" } else { "Fail" }
|
||||
|
||||
$auditResults += $auditResult
|
||||
}
|
||||
|
||||
end {
|
||||
# Return auditResults
|
||||
return $auditResults
|
||||
}
|
||||
}
|
60
helpers/psDoc-master/ADAuditTasks.md
Normal file
60
helpers/psDoc-master/ADAuditTasks.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
Module Name: ADAuditTasks
|
||||
Module Guid: '7ddb359a-e07f-4be0-b63a-a81f44c61fff'
|
||||
Download Help Link: https://audittaskshelpfiles.blob.core.windows.net/helpfiles/
|
||||
Help Version: 1.0.0.5
|
||||
Locale: en-US
|
||||
---
|
||||
|
||||
# ADAuditTasks Module
|
||||
## Description
|
||||
{{ Fill in the Description }}
|
||||
|
||||
## ADAuditTasks Cmdlets
|
||||
### [Convert-NmapXMLToCSV](Convert-NmapXMLToCSV)
|
||||
Converts an Nmap XML scan output file to a CSV file.
|
||||
|
||||
### [Get-ADActiveUserAudit](Get-ADActiveUserAudit)
|
||||
Gets active but stale AD User accounts that haven't logged in within the last 90 days by default.
|
||||
|
||||
### [Get-ADHostAudit](Get-ADHostAudit)
|
||||
Active Directory Server and Workstation Audit with Report export option (Can also be piped to CSV if Report isn't specified).
|
||||
|
||||
### [Get-ADUserLogonAudit](Get-ADUserLogonAudit)
|
||||
Retrieves the most recent LastLogon timestamp for a specified Active Directory user
|
||||
account from all domain controllers and outputs it as a DateTime object.
|
||||
|
||||
### [Get-ADUserPrivilegeAudit](Get-ADUserPrivilegeAudit)
|
||||
Produces three object outputs: PrivilegedGroups, AdExtendedRights, and possible service accounts.
|
||||
|
||||
### [Get-ADUserWildCardAudit](Get-ADUserWildCardAudit)
|
||||
Takes a search string to find commonly named accounts.
|
||||
|
||||
### [Get-HostTag](Get-HostTag)
|
||||
Creates a host name or tag based on predetermined criteria for as many as 999 hosts at a time.
|
||||
|
||||
### [Get-NetworkAudit](Get-NetworkAudit)
|
||||
Discovers the local network and runs port scans on all hosts found for specific or default sets of ports, displaying MAC ID vendor info.
|
||||
|
||||
### [Get-QuickPing](Get-QuickPing)
|
||||
Performs a quick ping on a range of IP addresses and returns an array of IP addresses
|
||||
that responded to the ping and an array of IP addresses that failed to respond.
|
||||
|
||||
### [Get-WebCertAudit](Get-WebCertAudit)
|
||||
Retrieves the certificate information for a web server.
|
||||
|
||||
### [Join-CSVFile](Join-CSVFile)
|
||||
Joins multiple CSV files with the same headers into a single CSV file.
|
||||
|
||||
### [Merge-ADAuditZip](Merge-ADAuditZip)
|
||||
Combines multiple audit report files into a single compressed ZIP file.
|
||||
|
||||
### [Merge-NmapToADHostAudit](Merge-NmapToADHostAudit)
|
||||
Merges Nmap network audit data with Active Directory host audit data.
|
||||
|
||||
### [Send-AuditEmail](Send-AuditEmail)
|
||||
This is a wrapper function for Send-MailKitMessage and takes string arrays as input.
|
||||
|
||||
### [Submit-FTPUpload](Submit-FTPUpload)
|
||||
Uploads a file to an FTP server using the WinSCP module.
|
||||
|
21
helpers/psDoc-master/LICENSE
Normal file
21
helpers/psDoc-master/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Microsoft Public License (MS-PL)
|
||||
This license governs use of the accompanying software. If you use the software, you
|
||||
accept this license. If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
20
helpers/psDoc-master/readme.md
Normal file
20
helpers/psDoc-master/readme.md
Normal file
@@ -0,0 +1,20 @@
|
||||
psDoc is a Powershell Help Document generator.
|
||||
|
||||
----
|
||||
### Using psDoc ###
|
||||
|
||||
To generate documentation off of your module, simply import your module
|
||||
|
||||
```
|
||||
Import-Module MySpecialModule
|
||||
```
|
||||
|
||||
And generate the documentation
|
||||
|
||||
```
|
||||
.\psDoc.ps1 -moduleName MySpecialModule
|
||||
```
|
||||
|
||||
### License ###
|
||||
|
||||
[Microsoft Public License (Ms-PL)](https://opensource.org/licenses/MS-PL)
|
194
helpers/psDoc-master/src/out-confluence-markup-template.ps1
Normal file
194
helpers/psDoc-master/src/out-confluence-markup-template.ps1
Normal file
@@ -0,0 +1,194 @@
|
||||
function TrimAllLines([string] $str) {
|
||||
$lines = $str -split "`n"
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] = $lines[$i].Trim()
|
||||
}
|
||||
|
||||
# Trim EOL.
|
||||
($lines | Out-String).Trim()
|
||||
}
|
||||
|
||||
function FixMarkdownString([string] $in = '', [bool] $includeBreaks = $false, [bool]$BlankStringToSpace = $False) {
|
||||
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
if($in -eq "" -and $BlankStringToSpace ) { return " " }
|
||||
|
||||
$replacements = @{
|
||||
'\' = '\\'
|
||||
'`' = '\`'
|
||||
'*' = '\*'
|
||||
'_' = '\_'
|
||||
'{' = '\{'
|
||||
'}' = '\}'
|
||||
'[' = '\['
|
||||
']' = '\]'
|
||||
'(' = '\('
|
||||
')' = '\)'
|
||||
'#' = '\#'
|
||||
'+' = '\+'
|
||||
'!' = '\!'
|
||||
}
|
||||
|
||||
$rtn = $in.Trim()
|
||||
foreach ($key in $replacements.Keys) {
|
||||
$rtn = $rtn.Replace($key, $replacements[$key])
|
||||
}
|
||||
|
||||
$rtn = TrimAllLines $rtn
|
||||
|
||||
if ($includeBreaks) {
|
||||
$crlf = [Environment]::NewLine
|
||||
$rtn = $rtn.Replace($crlf, " $crlf")
|
||||
}
|
||||
$rtn
|
||||
}
|
||||
|
||||
function FixMarkdownCodeString([string] $in) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
TrimAllLines $in
|
||||
}
|
||||
|
||||
function IncludeTableOfContents {
|
||||
|
||||
return "{toc:printable=true|style=square|maxLevel=2|indent=5px|minLevel=2|class=bigpink|exclude=[1//2]|type=list|outline=true|include=.*}"
|
||||
}
|
||||
|
||||
|
||||
@"
|
||||
h1. $moduleName
|
||||
|
||||
$(IncludeTableOfContents)
|
||||
|
||||
\\
|
||||
\\
|
||||
"@
|
||||
|
||||
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
h2. $(FixMarkdownString($_.Name))
|
||||
"@
|
||||
$synopsis = $_.synopsis.Trim()
|
||||
$syntax = $_.syntax | out-string
|
||||
if(-not ($synopsis -ilike "$($_.Name.Trim())*")){
|
||||
$tmp = $synopsis
|
||||
$synopsis = $syntax
|
||||
$syntax = $tmp
|
||||
@"
|
||||
h3. Synopsis
|
||||
$(FixMarkdownString($syntax))
|
||||
"@
|
||||
}
|
||||
|
||||
|
||||
@"
|
||||
h3. Description
|
||||
$(FixMarkdownString $(($_.Description | out-string).Trim()) $true)
|
||||
"@
|
||||
|
||||
@"
|
||||
h3. Syntax
|
||||
{code:theme=Confluence|linenumbers=false|language=Powershell|firstline=0001|collapse=false}
|
||||
$(TrimAllLines $synopsis)
|
||||
{code}
|
||||
"@
|
||||
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
h3. $($_.Name) Aliases
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
- $($_.Name)
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if($_.parameters){
|
||||
@"
|
||||
h3. Parameters
|
||||
|
||||
||Name||Alias||Description||Required?||Pipeline Input||Default Value||
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
|$(FixMarkdownString $_.Name $false $true)|$(FixMarkdownString $_.Aliases $false $true)|$(FixMarkdownString $($_.Description | out-string).Trim() $true $true)|$(FixMarkdownString $_.Required $false $true)|$(FixMarkdownString $_.PipelineInput $false $true)|$(FixMarkdownString $_.DefaultValue $false $true)|
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
|
||||
"@
|
||||
}
|
||||
$inputTypes = $(FixMarkdownString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
h3. Inputs
|
||||
- $inputTypes
|
||||
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixMarkdownString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
h3. Outputs
|
||||
- $returnValues
|
||||
|
||||
"@
|
||||
}
|
||||
$notes = $(FixMarkdownString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Note
|
||||
$notes
|
||||
|
||||
"@
|
||||
}
|
||||
if(($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Examples
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
{code:title=$(FixMarkdownString($_.title.Trim(('-',' '))))|theme=Confluence|linenumbers=true|language=Powershell|firstline=0001|collapse=false}
|
||||
$(FixMarkdownCodeString($_.code | out-string ))
|
||||
{code}
|
||||
|
||||
$(FixMarkdownString($_.remarks | out-string ) $true)
|
||||
"@
|
||||
}
|
||||
}
|
||||
if(($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
h3. Links
|
||||
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
- [$($_.name)|$($_.link)]
|
||||
"@
|
||||
}
|
||||
}
|
||||
|
||||
@"
|
||||
|
||||
\\
|
||||
\\
|
||||
\\
|
||||
----
|
||||
\\
|
||||
\\
|
||||
\\
|
||||
|
||||
"@
|
||||
|
||||
}
|
346
helpers/psDoc-master/src/out-html-template.ps1
Normal file
346
helpers/psDoc-master/src/out-html-template.ps1
Normal file
@@ -0,0 +1,346 @@
|
||||
@"
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
<auto-generated>
|
||||
<synopsis>
|
||||
This code was generated by a tool. on: $(Get-Date)
|
||||
</synopsis>
|
||||
<description>
|
||||
If you'd like to regenerate the documentation, please open up powershell and run
|
||||
|
||||
> .\psDoc.ps1 -moduleName NameOfYourModule
|
||||
|
||||
If the documentation is incomplete, or eronious,
|
||||
please edit the comments at the top of the module method within it's respecive .ps1 file.
|
||||
</description>
|
||||
</auto-generated>
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>$moduleName Documentation</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCoreDefault.min.css" rel="stylesheet" charset="utf-8">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" charset="utf-8">
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
.syntaxhighlighter {
|
||||
overflow-y: hidden !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
pre {
|
||||
min-height: 30px;
|
||||
}
|
||||
.navbar-nav {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.form-group {
|
||||
padding-top: 12px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.sidebar-nav .navbar-header {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar li a {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-nav .navbar .navbar-collapse {
|
||||
padding: 0;
|
||||
max-height: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul {
|
||||
float: none;
|
||||
}
|
||||
.sidebar-nav .navbar ul:not {
|
||||
display: block;
|
||||
}
|
||||
.sidebar-nav .navbar li {
|
||||
float: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div><h1>$moduleName</h1></div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="col-lg-3 col-md-4 col-sm-5 col-xs-12">
|
||||
<div class="sidebar-nav">
|
||||
<div class="navbar navbar-default" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-navbar-collapse">
|
||||
<span class="sr-only">Toggle</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<span class="visible-xs navbar-brand">click menu to open</span>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse sidebar-navbar-collapse">
|
||||
|
||||
<div class="form-group">
|
||||
<input class="form-control" id="searchinput" type="search" placeholder="Filter..." />
|
||||
</div>
|
||||
|
||||
<ul class="nav navbar-nav list-group" id="searchList">
|
||||
"@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Navigation'
|
||||
$progress++
|
||||
" <li class=`"nav-menu list-group-item`"><a href=`"#$($_.Name)`">$($_.Name)</a></li>"
|
||||
}
|
||||
@'
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8 col-sm-7 col-xs-12">
|
||||
'@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
<div id=`"$(FixString($_.Name))`" class="toggle_container">
|
||||
<div class="page-header">
|
||||
<h2> $(FixString($_.Name)) </h2>
|
||||
"@
|
||||
$syn = FixString($_.synopsis)
|
||||
if(!($syn).StartsWith($(FixString($_.Name)))){
|
||||
@"
|
||||
<p>$syn</p>
|
||||
<p>$(FixString(($_.Description | out-string).Trim()) $true)</p>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
"@
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'>
|
||||
<h3 class='panel-title'> $($_.Name) Aliases </h3>
|
||||
</div>
|
||||
<div class='panel-body'>
|
||||
<ul>
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
<li>$($_.Name)</li>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if (!($_.syntax | Out-String ).Trim().Contains('syntaxItem')) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Syntax </h3>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class='panel-body'>
|
||||
<pre class="brush: ps">$(FixString($_.syntax | out-string))</pre>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if($_.parameters){
|
||||
@"
|
||||
<div>
|
||||
<h3> Parameters </h3>
|
||||
<table class="table table-striped table-bordered table-condensed visible-on">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="visible-lg visible-md">Alias</th>
|
||||
<th>Description</th>
|
||||
<th class="visible-lg visible-md">Required?</th>
|
||||
<th class="visible-lg">Pipeline Input</th>
|
||||
<th class="visible-lg">Default Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
<tr>
|
||||
<td><nobr>-$(FixString($_.Name))</nobr></td>
|
||||
<td class="visible-lg visible-md">$(FixString($_.Aliases))</td>
|
||||
<td>$(FixString(($_.Description | out-string).Trim()) $true)</td>
|
||||
<td class="visible-lg visible-md">$(FixString($_.Required))</td>
|
||||
<td class="visible-lg">$(FixString($_.PipelineInput))</td>
|
||||
<td class="visible-lg">$(FixString($_.DefaultValue))</td>
|
||||
</tr>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$inputTypes = $(FixString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Inputs </h3>
|
||||
<p>The input type is the type of the objects that you can pipe to the cmdlet.</p>
|
||||
<ul><li>$inputTypes</li></ul>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Outputs </h3>
|
||||
<p>The output type is the type of the objects that the cmdlet emits.</p>
|
||||
<ul><li>$returnValues</li></ul>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
$notes = $(FixString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'>
|
||||
<h3 class='panel-title'> Note </h3>
|
||||
</div>
|
||||
<div class='panel-body'>$notes</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
if(($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Examples </h3>
|
||||
</div>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-body'>
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
<strong>$(FixString($_.title.Trim(('-',' '))))</strong>
|
||||
<pre class="brush: ps">$(FixString($_.code | out-string ).Trim())</pre>
|
||||
<div>$(FixString($_.remarks | out-string ).Trim())</div>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
</div>
|
||||
<p class='pull-right'><a onclick='document.body.scrollTop = document.documentElement.scrollTop = 0;' style='cursor: pointer;'>Top of page</a>
|
||||
"@
|
||||
}
|
||||
if(($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
<div>
|
||||
<h3> Links </h3>
|
||||
<div>
|
||||
<ul>
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
<li class='$($_.cssClass)'><a href='$($_.link)' target='$($_.target)'>$($_.name)</a></li>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
@"
|
||||
</div>
|
||||
"@
|
||||
}
|
||||
@'
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.min.js" charset="utf-8"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shBrushPowerShell.min.js" charset="utf-8"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(".toggle_container").hide();
|
||||
var previousId;
|
||||
if(location.hash) {
|
||||
var id = location.hash.slice(1); //Get rid of the # mark
|
||||
var elementToShow = $("#" + id); //Save local reference
|
||||
if(elementToShow.length) { //Check if the element exists
|
||||
elementToShow.slideToggle('fast'); //Show the element
|
||||
elementToShow.addClass("check_list_selected"); //Add class to element (the link)
|
||||
}
|
||||
previousId = id;
|
||||
}
|
||||
|
||||
$('.nav-menu a, .psLink a').click(function() {
|
||||
$(".sidebar-navbar-collapse").collapse('hide');
|
||||
$('.toggle_container').hide(); // Hide all
|
||||
var elem = $(this).prop("hash");
|
||||
$(elem).toggle('fast'); // Show HREF/to/ID one
|
||||
history.pushState({}, '', $(this).attr("href"));
|
||||
window.scrollTo(0, 0);
|
||||
return false;
|
||||
});
|
||||
SyntaxHighlighter.defaults['toolbar'] = false;
|
||||
SyntaxHighlighter.defaults['gutter'] = false;
|
||||
SyntaxHighlighter.all();
|
||||
|
||||
$('#searchList').btsListFilter('#searchinput', {itemChild: 'a', initial: false, resetOnBlur: true});
|
||||
|
||||
$(document).keyup(function( e ) {
|
||||
if(!$('#searchinput').is(':focus') && e.which >= 65 && e.which <= 90 ){
|
||||
$('#searchinput').val(String.fromCharCode(e.keyCode));
|
||||
$('#searchinput').focus();
|
||||
}
|
||||
});
|
||||
$(document).click(function() {
|
||||
$('#searchinput').blur();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- bootstrap-list-filter.min.js - removed as external resource and added as content -->
|
||||
<script>
|
||||
/*
|
||||
* bootstrap-list-filter v0.1.7 - 2015-03-30
|
||||
*
|
||||
* Copyright 2015 Stefano Cudini
|
||||
* stefano.cudini@gmail.com
|
||||
* http://labs.easyblog.it/
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Demos:
|
||||
* http://labs.easyblog.it/bootstrap-list-filter/
|
||||
*
|
||||
* Source:
|
||||
* git@github.com:stefanocudini/bootstrap-list-filter.git
|
||||
*
|
||||
*/
|
||||
!function(a){a.fn.btsListFilter=function(b,c){function d(a,b){return a.replace(/\{ *([\w_]+) *\}/g,function(a,c){return b[c]||""})}function e(a,b){var c;return b=b||300,function(){var d=this,e=arguments;clearTimeout(c),c=setTimeout(function(){a.apply(d,Array.prototype.slice.call(e))},b)}}var f,g=this,h=a(this),i=a(b),j=h;return c=a.extend({delay:300,minLength:1,initial:!0,eventKey:"keyup",resetOnBlur:!0,sourceData:null,sourceTmpl:'<a class="list-group-item" href="#"><span>{title}</span></a>',sourceNode:function(a){return d(c.sourceTmpl,a)},emptyNode:function(){return'<a class="list-group-item well" href="#"><span>No Results</span></a>'},itemEl:".list-group-item",itemChild:null,itemFilter:function(b,d){d=d&&d.replace(new RegExp("[({[^.$*+?\\]})]","g"),"");var e=a(b).text(),f=c.initial?"^":"",g=new RegExp(f+d,"i");return g.test(e)}},c),i.on(c.eventKey,e(function(){var b=a(this).val();c.itemEl&&(j=h.find(c.itemEl)),c.itemChild&&(j=j.find(c.itemChild));var d=j.filter(function(){return c.itemFilter.call(g,this,b)}),e=j.not(d);c.itemChild&&(d=d.parents(c.itemEl),e=e.parents(c.itemEl).hide()),""!==b&&b.length>=c.minLength?(d.show(),e.hide(),"function"===a.type(c.sourceData)?(d.hide(),e.hide(),f&&(a.isFunction(f.abort)?f.abort():a.isFunction(f.stop)&&f.stop()),f=c.sourceData.call(g,b,function(b){if(f=null,d.hide(),e.hide(),h.find(".bts-dynamic-item").remove(),b&&0!==b.length)for(var i in b)a(c.sourceNode.call(g,b[i])).addClass("bts-dynamic-item").appendTo(h);else a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)})):0===d.length&&a(c.emptyNode.call(g)).addClass("bts-dynamic-item").appendTo(h)):(d.show(),e.show(),h.find(".bts-dynamic-item").remove())},c.delay)),c.resetOnBlur&&i.on("blur",function(){a(this).val("").trigger(c.eventKey)}),h}}(jQuery);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'@
|
159
helpers/psDoc-master/src/out-markdown-template.ps1
Normal file
159
helpers/psDoc-master/src/out-markdown-template.ps1
Normal file
@@ -0,0 +1,159 @@
|
||||
function TrimAllLines([string] $str) {
|
||||
$lines = $str -split "`n"
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] = $lines[$i].Trim()
|
||||
}
|
||||
|
||||
# Trim EOL.
|
||||
($lines | Out-String).Trim()
|
||||
}
|
||||
|
||||
function FixMarkdownString([string] $in = '', [bool] $includeBreaks = $false) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
$replacements = @{
|
||||
'\' = '\\'
|
||||
'`' = '\`'
|
||||
'*' = '\*'
|
||||
'_' = '\_'
|
||||
'{' = '\{'
|
||||
'}' = '\}'
|
||||
'[' = '\['
|
||||
']' = '\]'
|
||||
'(' = '\('
|
||||
')' = '\)'
|
||||
'#' = '\#'
|
||||
'+' = '\+'
|
||||
'!' = '\!'
|
||||
'<' = '\<'
|
||||
'>' = '\>'
|
||||
}
|
||||
|
||||
$rtn = $in.Trim()
|
||||
foreach ($key in $replacements.Keys) {
|
||||
$rtn = $rtn.Replace($key, $replacements[$key])
|
||||
}
|
||||
|
||||
$rtn = TrimAllLines $rtn
|
||||
$crlf = [Environment]::NewLine
|
||||
if ($includeBreaks) {
|
||||
$rtn = $rtn.Replace($crlf, " $crlf")
|
||||
}
|
||||
else {
|
||||
$rtn = $rtn.Replace($crlf, " ").Trim()
|
||||
}
|
||||
$rtn
|
||||
}
|
||||
|
||||
function FixMarkdownCodeString([string] $in) {
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
TrimAllLines $in
|
||||
}
|
||||
|
||||
@"
|
||||
# $moduleName Module
|
||||
"@
|
||||
$progress = 0
|
||||
$commandsHelp | % {
|
||||
Update-Progress $_.Name 'Documentation'
|
||||
$progress++
|
||||
@"
|
||||
## $(FixMarkdownString($_.Name))
|
||||
"@
|
||||
$synopsis = $_.synopsis.Trim()
|
||||
$syntax = $_.syntax | out-string
|
||||
if (-not ($synopsis -ilike "$($_.Name.Trim())*")) {
|
||||
$tmp = $synopsis
|
||||
$synopsis = $syntax
|
||||
$syntax = $tmp
|
||||
@"
|
||||
### Synopsis
|
||||
$(FixMarkdownString($syntax))
|
||||
"@
|
||||
}
|
||||
@"
|
||||
### Syntax
|
||||
``````powershell
|
||||
$($synopsis)
|
||||
``````
|
||||
"@
|
||||
|
||||
if (!($_.alias.Length -eq 0)) {
|
||||
@"
|
||||
### $($_.Name) Aliases
|
||||
"@
|
||||
$_.alias | % {
|
||||
@"
|
||||
- $($_.Name)
|
||||
"@
|
||||
}
|
||||
@"
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
if ($_.parameters) {
|
||||
@"
|
||||
### Parameters
|
||||
| Name | Alias | Description | Required? | Pipeline Input | Default Value |
|
||||
| - | - | - | - | - | - |
|
||||
"@
|
||||
$_.parameters.parameter | % {
|
||||
@"
|
||||
| <nobr>$(FixMarkdownString($_.Name))</nobr> | $(FixMarkdownString($_.Aliases)) | $(FixMarkdownString(($_.Description | out-string).Trim())) | $(FixMarkdownString($_.Required)) | $(FixMarkdownString($_.PipelineInput)) | $(FixMarkdownString($_.DefaultValue)) |
|
||||
"@
|
||||
}
|
||||
}
|
||||
$inputTypes = $(FixMarkdownString($_.inputTypes | out-string))
|
||||
if ($inputTypes.Length -gt 0 -and -not $inputTypes.Contains('inputType')) {
|
||||
@"
|
||||
### Inputs
|
||||
- $inputTypes
|
||||
|
||||
"@
|
||||
}
|
||||
$returnValues = $(FixMarkdownString($_.returnValues | out-string))
|
||||
if ($returnValues.Length -gt 0 -and -not $returnValues.StartsWith("returnValue")) {
|
||||
@"
|
||||
### Outputs
|
||||
- $returnValues
|
||||
|
||||
"@
|
||||
}
|
||||
$notes = $(FixMarkdownString($_.alertSet | out-string))
|
||||
if ($notes.Trim().Length -gt 0) {
|
||||
@"
|
||||
### Note
|
||||
$notes
|
||||
|
||||
"@
|
||||
}
|
||||
if (($_.examples | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
### Examples
|
||||
"@
|
||||
$_.examples.example | % {
|
||||
@"
|
||||
**$(FixMarkdownString($_.title.Trim(('-',' '))))**
|
||||
``````powershell
|
||||
$(FixMarkdownCodeString($_.code | out-string ))
|
||||
``````
|
||||
$(FixMarkdownString($_.remarks | out-string ) $true)
|
||||
|
||||
"@
|
||||
}
|
||||
}
|
||||
if (($_.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
@"
|
||||
### Links
|
||||
|
||||
"@
|
||||
$_.links | % {
|
||||
@"
|
||||
- [$($_.name)]($($_.link))
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
63
helpers/psDoc-master/src/psDoc.ps1
Normal file
63
helpers/psDoc-master/src/psDoc.ps1
Normal file
@@ -0,0 +1,63 @@
|
||||
param(
|
||||
[parameter(Mandatory=$true, Position=0)] [string] $moduleName,
|
||||
[parameter(Mandatory=$false, Position=1)] [string] $template = "./out-html-template.ps1",
|
||||
[parameter(Mandatory=$false, Position=2)] [string] $outputDir = './help',
|
||||
[parameter(Mandatory=$false, Position=3)] [string] $fileName = 'index.html'
|
||||
)
|
||||
|
||||
function FixString ($in = '', [bool]$includeBreaks = $false){
|
||||
if ($in -eq $null) { return }
|
||||
|
||||
$rtn = $in.Replace('&', '&').Replace('<', '<').Replace('>', '>').Trim()
|
||||
|
||||
if($includeBreaks){
|
||||
$rtn = $rtn.Replace([Environment]::NewLine, '<br>')
|
||||
}
|
||||
return $rtn
|
||||
}
|
||||
|
||||
function Update-Progress($name, $action){
|
||||
Write-Progress -Activity "Rendering $action for $name" -CurrentOperation "Completed $progress of $totalCommands." -PercentComplete $(($progress/$totalCommands)*100)
|
||||
}
|
||||
$i = 0
|
||||
$commandsHelp = (Get-Command -module $moduleName) | get-help -full | Where-Object {! $_.name.EndsWith('.ps1')}
|
||||
|
||||
foreach ($h in $commandsHelp){
|
||||
$cmdHelp = (Get-Command $h.Name)
|
||||
|
||||
# Get any aliases associated with the method
|
||||
$alias = get-alias -definition $h.Name -ErrorAction SilentlyContinue
|
||||
if($alias){
|
||||
$h | Add-Member Alias $alias
|
||||
}
|
||||
|
||||
# Parse the related links and assign them to a links hashtable.
|
||||
if(($h.relatedLinks | Out-String).Trim().Length -gt 0) {
|
||||
$links = $h.relatedLinks.navigationLink | % {
|
||||
if($_.uri){ @{name = $_.uri; link = $_.uri; target='_blank'} }
|
||||
if($_.linkText){ @{name = $_.linkText; link = "#$($_.linkText)"; cssClass = 'psLink'; target='_top'} }
|
||||
}
|
||||
$h | Add-Member Links $links
|
||||
}
|
||||
|
||||
# Add parameter aliases to the object.
|
||||
foreach($p in $h.parameters.parameter ){
|
||||
$paramAliases = ($cmdHelp.parameters.values | where name -like $p.name | select aliases).Aliases
|
||||
if($paramAliases){
|
||||
$p | Add-Member Aliases "$($paramAliases -join ', ')" -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create the output directory if it does not exist
|
||||
if (-Not (Test-Path $outputDir)) {
|
||||
New-Item -Path $outputDir -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
$totalCommands = $commandsHelp.Count
|
||||
if (!$totalCommands) {
|
||||
$totalCommands = 1
|
||||
}
|
||||
|
||||
$template = Get-Content $template -raw -force
|
||||
Invoke-Expression $template > "$outputDir\$fileName"
|
Reference in New Issue
Block a user