Collaboration/Get-FormsSecurityConfig.ps1
|
<# .SYNOPSIS Collects Microsoft Forms tenant security and configuration settings. .DESCRIPTION Queries Microsoft Graph for Microsoft Forms admin settings including external sharing controls, phishing protection, and respondent identity recording. Returns a structured inventory of settings with current values and CIS benchmark recommendations. Requires the following Graph API permissions: OrgSettings-Forms.Read.All .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> . .\Common\Connect-Service.ps1 PS> Connect-Service -Service Graph -Scopes 'OrgSettings-Forms.Read.All' PS> .\Collaboration\Get-FormsSecurityConfig.ps1 Displays Microsoft Forms security configuration settings. .EXAMPLE PS> .\Collaboration\Get-FormsSecurityConfig.ps1 -OutputPath '.\forms-security-config.csv' Exports Forms security configuration to CSV. .NOTES Author: Daren9m Settings checked are aligned with CIS Microsoft 365 Foundations Benchmark v6.0.1 recommendations. #> [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$OutputPath ) . (Join-Path -Path $PSScriptRoot -ChildPath '../Common/Invoke-SafeGraphRequest.ps1') # Continue on errors: non-critical checks should not block remaining assessments. $ErrorActionPreference = 'Continue' # Verify Graph connection if (-not (Assert-GraphConnection)) { return } # Capture the connected cloud so a sovereign-cloud API gap can be reported # precisely. 'USGov' = GCC High, 'USGovDoD' = DoD (#941). $graphEnvironment = try { (Get-MgContext).Environment } catch { $null } # Load shared security-config helpers $_scriptDir = if ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { $PSScriptRoot } . (Join-Path -Path $_scriptDir -ChildPath '..\Common\SecurityConfigHelper.ps1') # Initialize-SecurityConfig records the active context; the shared Add-Setting from # SecurityConfigHelper.ps1 reads it, so no local wrapper is needed (#958). $ctx = Initialize-SecurityConfig $settings = $ctx.Settings # ------------------------------------------------------------------ # 1. Microsoft Forms Admin Settings (CIS 3.6.x) # ------------------------------------------------------------------ try { Write-Verbose "Checking Microsoft Forms admin settings..." $graphParams = @{ Method = 'GET' Uri = '/beta/admin/forms/settings' ErrorAction = 'Stop' } $formsSettings = Invoke-SafeGraphRequest @graphParams if ($formsSettings) { # CIS 3.6.1 - Ensure only people in your organization can respond to forms $externalSend = $formsSettings['isExternalSendFormEnabled'] $settingParams = @{ Category = 'External Sharing' Setting = 'External Users Can Respond to Forms' CurrentValue = "$externalSend" RecommendedValue = 'False' Status = if (-not $externalSend) { 'Pass' } else { 'Fail' } CheckId = 'FORMS-CONFIG-001' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Uncheck "People outside your organization can respond".' } Add-Setting @settingParams # CIS 3.6.1 - Ensure external collaboration on forms is restricted $externalCollab = $formsSettings['isExternalShareCollaborationEnabled'] $settingParams = @{ Category = 'External Sharing' Setting = 'External Users Can Collaborate on Forms' CurrentValue = "$externalCollab" RecommendedValue = 'False' Status = if (-not $externalCollab) { 'Pass' } else { 'Fail' } CheckId = 'FORMS-CONFIG-002' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Uncheck "People outside your organization can share and collaborate on forms".' } Add-Setting @settingParams # External result sharing $externalResults = $formsSettings['isExternalShareResultEnabled'] $settingParams = @{ Category = 'External Sharing' Setting = 'External Users Can View Form Results' CurrentValue = "$externalResults" RecommendedValue = 'False' Status = if (-not $externalResults) { 'Pass' } else { 'Fail' } CheckId = 'FORMS-CONFIG-003' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Uncheck "People outside your organization can see results summary and individual responses".' } Add-Setting @settingParams # CIS 3.6.2 - Phishing protection enabled $phishingProtection = $formsSettings['isPhishingScanEnabled'] $settingParams = @{ Category = 'Security' Setting = 'Phishing Protection' CurrentValue = "$phishingProtection" RecommendedValue = 'True' Status = if ($phishingProtection) { 'Pass' } else { 'Fail' } CheckId = 'FORMS-CONFIG-004' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Enable "Internal phishing protection".' } Add-Setting @settingParams # Identity recording by default (accountability/non-repudiation) $recordIdentity = $formsSettings['isRecordIdentityByDefaultEnabled'] $settingParams = @{ Category = 'Security' Setting = 'Record Respondent Identity by Default' CurrentValue = "$recordIdentity" RecommendedValue = 'True' Status = if ($recordIdentity) { 'Pass' } else { 'Review' } CheckId = 'FORMS-CONFIG-005' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Enable "Record name by default when new forms are created".' } Add-Setting @settingParams # Bing image/video search (external content exposure) $bingSearch = $formsSettings['isBingImageVideoSearchEnabled'] $settingParams = @{ Category = 'Security' Setting = 'Bing Image and Video Search' CurrentValue = "$bingSearch" RecommendedValue = 'False' Status = if (-not $bingSearch) { 'Pass' } else { 'Review' } CheckId = 'FORMS-CONFIG-006' Remediation = 'Microsoft 365 admin center > Settings > Org settings > Microsoft Forms > Uncheck "Bing search and YouTube video".' } Add-Setting @settingParams } } catch { if ($_.Exception.Message -match '403|Forbidden|Authorization_RequestDenied|Insufficient') { Write-Warning "Insufficient permissions to read Forms settings. Requires OrgSettings-Forms.Read.All scope. Skipping Forms security checks." $settingParams = @{ Category = 'External Sharing' Setting = 'External Users Can Respond to Forms' CurrentValue = 'Permission denied -- OrgSettings-Forms.Read.All required' RecommendedValue = 'False' Status = 'Review' CheckId = 'FORMS-CONFIG-001' Remediation = 'Reconnect with the OrgSettings-Forms.Read.All permission scope to check Microsoft Forms settings.' } Add-Setting @settingParams } elseif ($_.Exception.Message -match '400|BadRequest|MissingProvider') { # The /beta/admin/forms/settings endpoint returns BadRequest in sovereign # clouds where Forms admin APIs are not served (#941). Record a Skipped # result so the gap surfaces in the report's not-assessed group instead of # silently vanishing. $cloudNote = if ($graphEnvironment -in @('USGov', 'USGovDoD')) { "Microsoft Forms admin settings are not available in the $graphEnvironment sovereign cloud." } else { 'Microsoft Forms admin settings endpoint returned BadRequest; the API may be unavailable in this environment.' } Write-Warning $cloudNote $settingParams = @{ Category = 'External Sharing' Setting = 'External Users Can Respond to Forms' CurrentValue = $cloudNote RecommendedValue = 'False' Status = 'Skipped' CheckId = 'FORMS-CONFIG-001' Remediation = 'No action available -- the Microsoft Forms admin settings API is not served in this cloud. Verify Forms sharing settings manually in the Microsoft 365 admin center.' } Add-Setting @settingParams } else { Write-Warning "Could not retrieve Microsoft Forms settings: $($_.Exception.Message)" } } # ------------------------------------------------------------------ # Output # ------------------------------------------------------------------ Export-SecurityConfigReport -Settings $settings -OutputPath $OutputPath -ServiceLabel 'Microsoft Forms' |