Skip to content

PowerShell Setup: Control 2.23 - User Consent and AI Disclosure Enforcement

Last Updated: February 2026 PowerShell Version Required: 7.2+ Estimated Time: 30-40 minutes

Prerequisites

  • PowerShell 7.2 or later installed
  • Microsoft Graph PowerShell SDK installed (Install-Module Microsoft.Graph)
  • Power Platform Admin PowerShell module installed (Install-Module Microsoft.PowerApps.Administration.PowerShell)
  • Entra Global Admin or Purview Compliance Admin role
  • Power Platform Admin role
  • Dataverse environment with fsi_aiconsent table deployed (Zone 3)

Module Installation and Authentication

Step 1: Install Required Modules

# Install Microsoft Graph PowerShell SDK
Install-Module Microsoft.Graph -Scope CurrentUser -Force

# Install Power Platform Admin module
Install-Module Microsoft.PowerApps.Administration.PowerShell -Scope CurrentUser -Force

# Verify installations
Get-Module Microsoft.Graph -ListAvailable
Get-Module Microsoft.PowerApps.Administration.PowerShell -ListAvailable

Step 2: Authenticate to Microsoft Graph

# Connect to Microsoft Graph with required scopes
Connect-MgGraph -Scopes "Organization.Read.All", "Directory.Read.All", "Policy.Read.All"

# Verify connection
Get-MgContext

Step 3: Authenticate to Power Platform

# Connect to Power Platform (interactive authentication)
Add-PowerAppsAccount

# Verify connection
Get-AdminPowerAppEnvironment | Select-Object -First 1

Script 1: Get Tenant-Wide AI Disclaimer Configuration

Purpose

Retrieve the current status of the tenant-wide AI Disclaimer toggle and custom disclosure URL from Microsoft 365 admin center settings.

Script: Get-TenantAIDisclaimer.ps1

<#
.SYNOPSIS
    Retrieves tenant-wide AI Disclaimer configuration for Microsoft 365 Copilot.

.DESCRIPTION
    Queries Microsoft 365 tenant settings to retrieve the AI Disclaimer toggle status
    and custom disclosure URL. Outputs the configuration for governance reporting.

.EXAMPLE
    .\Get-TenantAIDisclaimer.ps1

.NOTES
    Requires: Microsoft Graph PowerShell SDK, Organization.Read.All scope
    Author: FSI Agent Governance Framework
    Last Updated: February 2026
#>

# Authenticate to Microsoft Graph if not already connected
if (-not (Get-MgContext)) {
    Connect-MgGraph -Scopes "Organization.Read.All"
}

# Retrieve organization settings
Write-Host "Retrieving tenant-wide AI Disclaimer configuration..." -ForegroundColor Cyan

try {
    # Note: This is a conceptual API endpoint; actual Graph API may differ
    # Check Microsoft Graph documentation for the correct endpoint
    $tenantSettings = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/admin/copilot/settings"

    $disclaimerConfig = [PSCustomObject]@{
        TenantId = (Get-MgOrganization).Id
        TenantName = (Get-MgOrganization).DisplayName
        AIDisclaimerEnabled = $tenantSettings.aiDisclaimerEnabled
        CustomDisclosureURL = $tenantSettings.customDisclosureUrl
        LastModifiedDateTime = $tenantSettings.lastModifiedDateTime
        LastModifiedBy = $tenantSettings.lastModifiedBy
        ComplianceStatus = if ($tenantSettings.aiDisclaimerEnabled) { "Compliant" } else { "Non-Compliant" }
    }

    $disclaimerConfig | Format-List

    # Export to CSV for reporting
    $outputPath = ".\TenantAIDisclaimer_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
    $disclaimerConfig | Export-Csv -Path $outputPath -NoTypeInformation
    Write-Host "Configuration exported to: $outputPath" -ForegroundColor Green
}
catch {
    Write-Error "Failed to retrieve AI Disclaimer configuration: $_"
}

Usage

# Run the script to get current configuration
.\Get-TenantAIDisclaimer.ps1

# Expected Output:
# TenantId             : 12345678-1234-1234-1234-123456789012
# TenantName           : Contoso Financial Services
# AIDisclaimerEnabled  : True
# CustomDisclosureURL  : https://contoso.com/policies/ai-transparency
# LastModifiedDateTime : 2026-02-10T14:32:00Z
# LastModifiedBy       : admin@contoso.com
# ComplianceStatus     : Compliant

Script 2: Get Agent-Level Disclosure Configuration

Purpose

Audit all Copilot Studio agents to identify which have AI disclosure configured in their greeting topics.

Script: Get-AgentDisclosureInventory.ps1

<#
.SYNOPSIS
    Audits all Copilot Studio agents for AI disclosure configuration in greeting topics.

.DESCRIPTION
    Retrieves all agents across Power Platform environments and checks for AI disclosure
    language in greeting topics. Outputs an inventory for governance reporting.

.PARAMETER EnvironmentName
    Optional. Filter to a specific Power Platform environment. If not specified, scans all environments.

.EXAMPLE
    .\Get-AgentDisclosureInventory.ps1

.EXAMPLE
    .\Get-AgentDisclosureInventory.ps1 -EnvironmentName "Default-12345678-1234-1234-1234-123456789012"

.NOTES
    Requires: Power Platform Admin PowerShell module
    Author: FSI Agent Governance Framework
    Last Updated: February 2026
#>

param(
    [string]$EnvironmentName
)

# Authenticate to Power Platform if not already connected
if (-not (Get-AdminPowerAppEnvironment -ErrorAction SilentlyContinue)) {
    Add-PowerAppsAccount
}

Write-Host "Retrieving agent disclosure configuration inventory..." -ForegroundColor Cyan

$environments = if ($EnvironmentName) {
    Get-AdminPowerAppEnvironment -EnvironmentName $EnvironmentName
} else {
    Get-AdminPowerAppEnvironment
}

$agentInventory = @()

foreach ($env in $environments) {
    Write-Host "Scanning environment: $($env.DisplayName)..." -ForegroundColor Yellow

    # Get all bots/agents in the environment
    # Conceptual — verify cmdlet availability in your environment
    $agents = Get-AdminPowerAppChatBot -EnvironmentName $env.EnvironmentName

    foreach ($agent in $agents) {
        Write-Host "  Checking agent: $($agent.DisplayName)..." -ForegroundColor Gray

        # Attempt to retrieve greeting topic (note: actual API may differ)
        try {
            # Conceptual — verify cmdlet availability in your environment
            $greetingTopic = Get-AdminPowerAppChatBotTopic -EnvironmentName $env.EnvironmentName -BotName $agent.BotName -TopicName "Greeting"

            $hasDisclosure = if ($greetingTopic.Content -match "AI|artificial intelligence|monitoring|consent") {
                $true
            } else {
                $false
            }

            $agentRecord = [PSCustomObject]@{
                EnvironmentName = $env.EnvironmentName
                EnvironmentDisplayName = $env.DisplayName
                AgentName = $agent.DisplayName
                AgentId = $agent.BotName
                HasGreetingTopic = $true
                HasAIDisclosure = $hasDisclosure
                LastModified = $agent.LastModifiedTime
                ComplianceStatus = if ($hasDisclosure) { "Compliant" } else { "Review Required" }
            }
        }
        catch {
            $agentRecord = [PSCustomObject]@{
                EnvironmentName = $env.EnvironmentName
                EnvironmentDisplayName = $env.DisplayName
                AgentName = $agent.DisplayName
                AgentId = $agent.BotName
                HasGreetingTopic = $false
                HasAIDisclosure = $false
                LastModified = $agent.LastModifiedTime
                ComplianceStatus = "No Greeting Topic"
            }
        }

        $agentInventory += $agentRecord
    }
}

# Display summary
Write-Host "`nAgent Disclosure Inventory Summary:" -ForegroundColor Cyan
$agentInventory | Format-Table -AutoSize

$totalAgents = $agentInventory.Count
$compliantAgents = ($agentInventory | Where-Object { $_.ComplianceStatus -eq "Compliant" }).Count
$nonCompliantAgents = $totalAgents - $compliantAgents

Write-Host "`nTotal Agents: $totalAgents" -ForegroundColor White
Write-Host "Compliant: $compliantAgents" -ForegroundColor Green
Write-Host "Non-Compliant/Review Required: $nonCompliantAgents" -ForegroundColor Red

# Export to CSV
$outputPath = ".\AgentDisclosureInventory_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$agentInventory | Export-Csv -Path $outputPath -NoTypeInformation
Write-Host "`nInventory exported to: $outputPath" -ForegroundColor Green

Purpose

Retrieve user consent acknowledgment records from the Dataverse fsi_aiconsent table for Zone 3 agents.

Script: Get-ConsentRecords.ps1

<#
.SYNOPSIS
    Retrieves user consent acknowledgment records from Dataverse for Zone 3 agents.

.DESCRIPTION
    Queries the fsi_aiconsent Dataverse table to retrieve consent records, including
    user identity, timestamp, disclosure version, and acknowledgment status.

.PARAMETER EnvironmentUrl
    The Dataverse environment URL (e.g., https://contoso.crm.dynamics.com)

.PARAMETER AgentName
    Optional. Filter consent records for a specific agent.

.PARAMETER DaysBack
    Optional. Retrieve consent records from the last N days. Default is 30 days.

.EXAMPLE
    .\Get-ConsentRecords.ps1 -EnvironmentUrl "https://contoso.crm.dynamics.com"

.EXAMPLE
    .\Get-ConsentRecords.ps1 -EnvironmentUrl "https://contoso.crm.dynamics.com" -AgentName "Customer Service Agent" -DaysBack 7

.NOTES
    Requires: Dataverse environment with fsi_aiconsent table deployed
    Requires: Azure authentication method (MSAL.PS or Azure CLI) for Dataverse Web API access
    Author: FSI Agent Governance Framework
    Last Updated: February 2026
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$EnvironmentUrl,

    [string]$AgentName,

    [int]$DaysBack = 30
)

# Helper function to get access token (conceptual - implement based on your auth method)
function Get-AccessToken {
    param([string]$EnvironmentUrl)
    # This is a placeholder - implement actual OAuth token acquisition
    # Example: Use MSAL.PS or Azure CLI to get token
    # az account get-access-token --resource $EnvironmentUrl --query accessToken -o tsv
    Write-Warning "Implement Get-AccessToken function with your authentication method"
    return ""
}

Write-Host "Retrieving consent records from Dataverse..." -ForegroundColor Cyan

# Calculate date filter
$startDate = (Get-Date).AddDays(-$DaysBack).ToString("yyyy-MM-dd")

# Build FetchXML query
$fetchXml = @"
<fetch>
  <entity name='fsi_aiconsent'>
    <attribute name='fsi_userid' />
    <attribute name='fsi_agentname' />
    <attribute name='fsi_consenttimestamp' />
    <attribute name='fsi_disclosureversion' />
    <attribute name='fsi_acknowledgmentstatus' />
    <attribute name='createdon' />
    <filter>
      <condition attribute='fsi_consenttimestamp' operator='on-or-after' value='$startDate' />
"@

if ($AgentName) {
    $fetchXml += @"
      <condition attribute='fsi_agentname' operator='eq' value='$AgentName' />
"@
}

$fetchXml += @"
    </filter>
    <order attribute='fsi_consenttimestamp' descending='true' />
  </entity>
</fetch>
"@

try {
    # Note: This uses a conceptual approach; actual Dataverse PowerShell cmdlet may differ
    # You may need to use Invoke-RestMethod with Dataverse Web API

    $authHeader = @{
        "Authorization" = "Bearer $(Get-AccessToken -EnvironmentUrl $EnvironmentUrl)"
        "Content-Type" = "application/json"
        "OData-MaxVersion" = "4.0"
        "OData-Version" = "4.0"
    }

    $apiUrl = "$EnvironmentUrl/api/data/v9.2/fsi_aiconsents?fetchXml=$([uri]::EscapeDataString($fetchXml))"
    $response = Invoke-RestMethod -Uri $apiUrl -Headers $authHeader -Method Get

    $consentRecords = $response.value | ForEach-Object {
        [PSCustomObject]@{
            UserId = $_.fsi_userid
            AgentName = $_.fsi_agentname
            ConsentTimestamp = $_.fsi_consenttimestamp
            DisclosureVersion = $_.fsi_disclosureversion
            AcknowledgmentStatus = $_.fsi_acknowledgmentstatus
            CreatedOn = $_.createdon
        }
    }

    # Display records
    Write-Host "`nConsent Records:" -ForegroundColor Cyan
    $consentRecords | Format-Table -AutoSize

    # Summary
    $totalRecords = $consentRecords.Count
    $acknowledgedCount = ($consentRecords | Where-Object { $_.AcknowledgmentStatus -eq $true }).Count

    Write-Host "`nTotal Consent Records (last $DaysBack days): $totalRecords" -ForegroundColor White
    Write-Host "Acknowledged: $acknowledgedCount" -ForegroundColor Green

    # Export to CSV
    $outputPath = ".\ConsentRecords_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
    $consentRecords | Export-Csv -Path $outputPath -NoTypeInformation
    Write-Host "Consent records exported to: $outputPath" -ForegroundColor Green
}
catch {
    Write-Error "Failed to retrieve consent records: $_"
}

Purpose

Check for users who have interacted with Zone 3 agents without valid consent acknowledgment records.

Script: Verify-ConsentCompliance.ps1

<#
.SYNOPSIS
    Verifies consent compliance by checking for Zone 3 agent interactions without valid consent.

.DESCRIPTION
    Cross-references agent usage logs with consent records to identify users who have
    accessed Zone 3 agents without valid consent acknowledgment within the required timeframe.

.PARAMETER EnvironmentUrl
    The Dataverse environment URL

.PARAMETER ConsentValidityDays
    Number of days a consent record remains valid before re-acknowledgment is required. Default is 90.

.EXAMPLE
    .\Verify-ConsentCompliance.ps1 -EnvironmentUrl "https://contoso.crm.dynamics.com"

.EXAMPLE
    .\Verify-ConsentCompliance.ps1 -EnvironmentUrl "https://contoso.crm.dynamics.com" -ConsentValidityDays 60

.NOTES
    Requires: Dataverse environment with fsi_aiconsent and usage log tables
    Author: FSI Agent Governance Framework
    Last Updated: February 2026
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$EnvironmentUrl,

    [int]$ConsentValidityDays = 90
)

Write-Host "Verifying consent compliance for Zone 3 agents..." -ForegroundColor Cyan

# Calculate valid consent date threshold
$validConsentDate = (Get-Date).AddDays(-$ConsentValidityDays)

# This is a conceptual script - actual implementation would:
# 1. Query agent usage logs for Zone 3 agents
# 2. For each user interaction, check for valid consent record
# 3. Identify users without valid consent

# Placeholder for actual implementation
Write-Host "Consent compliance verification requires:" -ForegroundColor Yellow
Write-Host "  1. Agent usage log table in Dataverse" -ForegroundColor Gray
Write-Host "  2. Zone 3 agent classification data" -ForegroundColor Gray
Write-Host "  3. Cross-reference with fsi_aiconsent table" -ForegroundColor Gray
Write-Host "`nImplement this script based on your specific Dataverse schema." -ForegroundColor Yellow

# Example output structure
$complianceReport = @(
    [PSCustomObject]@{
        UserId = "user1@contoso.com"
        AgentName = "Investment Advisory Agent"
        LastInteraction = "2026-02-10"
        ConsentStatus = "Valid"
        ConsentDate = "2025-12-15"
        DaysSinceConsent = 57
    },
    [PSCustomObject]@{
        UserId = "user2@contoso.com"
        AgentName = "Customer Service Agent"
        LastInteraction = "2026-02-11"
        ConsentStatus = "Expired"
        ConsentDate = "2025-09-01"
        DaysSinceConsent = 163
    }
)

$complianceReport | Format-Table -AutoSize

# Export results
$outputPath = ".\ConsentComplianceReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$complianceReport | Export-Csv -Path $outputPath -NoTypeInformation
Write-Host "Compliance report exported to: $outputPath" -ForegroundColor Green

Script 5: Generate Disclosure Compliance Report

Purpose

Generate a comprehensive compliance report showing disclosure configuration status across tenant, agents, and consent tracking.

Script: New-DisclosureComplianceReport.ps1

<#
.SYNOPSIS
    Generates a comprehensive disclosure compliance report.

.DESCRIPTION
    Aggregates data from tenant-wide AI Disclaimer settings, agent-level disclosure
    configurations, and consent records to produce a compliance report.

.EXAMPLE
    .\New-DisclosureComplianceReport.ps1

.NOTES
    Calls other scripts in this playbook to gather data
    Author: FSI Agent Governance Framework
    Last Updated: February 2026
#>

Write-Host "Generating Disclosure Compliance Report..." -ForegroundColor Cyan
Write-Host ("=" * 80) -ForegroundColor Cyan

# Section 1: Tenant-Wide Configuration
Write-Host "`n[1/3] Retrieving Tenant-Wide AI Disclaimer Configuration..." -ForegroundColor Yellow
& .\Get-TenantAIDisclaimer.ps1

# Section 2: Agent-Level Disclosure Inventory
Write-Host "`n[2/3] Retrieving Agent-Level Disclosure Inventory..." -ForegroundColor Yellow
& .\Get-AgentDisclosureInventory.ps1

# Section 3: Consent Records (if Dataverse URL provided)
Write-Host "`n[3/3] Consent Records..." -ForegroundColor Yellow
$dataverseUrl = Read-Host "Enter Dataverse environment URL for Zone 3 consent records (or press Enter to skip)"
if ($dataverseUrl) {
    & .\Get-ConsentRecords.ps1 -EnvironmentUrl $dataverseUrl -DaysBack 30
} else {
    Write-Host "Consent records section skipped." -ForegroundColor Gray
}

# Summary
Write-Host ("`n" + ("=" * 80)) -ForegroundColor Cyan
Write-Host "Disclosure Compliance Report Complete" -ForegroundColor Green
Write-Host "Review the generated CSV files for detailed analysis." -ForegroundColor White

Deployment Checklist

After running these scripts, verify:

  • Tenant-wide AI Disclaimer toggle is enabled (Get-TenantAIDisclaimer.ps1 shows Compliant)
  • Custom disclosure URL is configured and accessible
  • All Zone 2 and Zone 3 agents have AI disclosure in greeting topics (Get-AgentDisclosureInventory.ps1 shows Compliant)
  • Zone 3 agents have consent records in Dataverse for all active users (Get-ConsentRecords.ps1)
  • No expired consent records for users who have recently interacted with Zone 3 agents (Verify-ConsentCompliance.ps1)
  • Compliance reports are generated and stored for audit trail (New-DisclosureComplianceReport.ps1)

Automation and Scheduling

To schedule automated compliance checks:

# Create a scheduled task to run daily compliance report
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
    -Argument "-File C:\Scripts\New-DisclosureComplianceReport.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At 6:00AM

Register-ScheduledTask -TaskName "AI Disclosure Compliance Check" `
    -Action $action -Trigger $trigger -Description "Daily disclosure compliance report"

Back to Control 2.23 | Portal Walkthrough | Verification Testing | Troubleshooting