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'