Skip to content

PowerShell Setup: Control 2.21 - AI Marketing Claims and Substantiation

Last Updated: January 2026 Module Requirements: PnP.PowerShell, Microsoft.Graph Estimated Time: 30-45 minutes

Prerequisites

  • PnP.PowerShell module installed: Install-Module PnP.PowerShell -Scope CurrentUser
  • Microsoft.Graph module installed: Install-Module Microsoft.Graph -Scope CurrentUser
  • SharePoint Admin or Site Collection Admin permissions
  • Governance SharePoint site URL identified

Module Installation

# Install required modules
Install-Module PnP.PowerShell -Scope CurrentUser -Force
Install-Module Microsoft.Graph -Scope CurrentUser -Force

# Import modules
Import-Module PnP.PowerShell
Import-Module Microsoft.Graph

Script 1: Create AI Claims Inventory List

<#
.SYNOPSIS
    Creates the AI Marketing Claims Inventory list in SharePoint.

.DESCRIPTION
    Sets up the claims inventory list with all required columns
    for tracking AI marketing claims per Control 2.21.

.PARAMETER SiteUrl
    The URL of the governance SharePoint site.

.EXAMPLE
    .\New-AIClaimsInventory.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/AIGovernance"
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$SiteUrl
)

# Connect to SharePoint
Connect-PnPOnline -Url $SiteUrl -Interactive

# Create the list
$listName = "AI Marketing Claims Inventory"
$list = New-PnPList -Title $listName -Template GenericList

# Add columns
Add-PnPField -List $listName -DisplayName "Claim Text" -InternalName "ClaimText" -Type Note
Add-PnPField -List $listName -DisplayName "Claim Category" -InternalName "ClaimCategory" -Type Choice `
    -Choices "Performance","Capability","Comparative","Predictive","Efficiency"
Add-PnPField -List $listName -DisplayName "Agent/Product" -InternalName "AgentProduct" -Type Text
Add-PnPField -List $listName -DisplayName "Target Channel" -InternalName "TargetChannel" -Type Choice `
    -Choices "Website","Email","Social Media","Sales Collateral","Press Release","Multiple"
Add-PnPField -List $listName -DisplayName "Governance Zone" -InternalName "GovernanceZone" -Type Choice `
    -Choices "Zone 1 - Personal","Zone 2 - Team","Zone 3 - Enterprise"
Add-PnPField -List $listName -DisplayName "Substantiation File" -InternalName "SubstantiationFile" -Type URL
Add-PnPField -List $listName -DisplayName "Status" -InternalName "ClaimStatus" -Type Choice `
    -Choices "Draft","Under Review","Approved","Rejected","Retired"
Add-PnPField -List $listName -DisplayName "Submitted By" -InternalName "SubmittedBy" -Type User
Add-PnPField -List $listName -DisplayName "Submission Date" -InternalName "SubmissionDate" -Type DateTime
Add-PnPField -List $listName -DisplayName "Compliance Reviewer" -InternalName "ComplianceReviewer" -Type User
Add-PnPField -List $listName -DisplayName "Review Date" -InternalName "ReviewDate" -Type DateTime
Add-PnPField -List $listName -DisplayName "Approval Date" -InternalName "ApprovalDate" -Type DateTime
Add-PnPField -List $listName -DisplayName "Next Review Date" -InternalName "NextReviewDate" -Type DateTime
Add-PnPField -List $listName -DisplayName "Review Comments" -InternalName "ReviewComments" -Type Note

Write-Host "AI Marketing Claims Inventory list created successfully." -ForegroundColor Green

# Disconnect
Disconnect-PnPOnline

Script 2: Create Substantiation Evidence Library

<#
.SYNOPSIS
    Creates the substantiation evidence document library.

.PARAMETER SiteUrl
    The URL of the governance SharePoint site.

.EXAMPLE
    .\New-SubstantiationLibrary.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/AIGovernance"
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$SiteUrl
)

# Connect to SharePoint
Connect-PnPOnline -Url $SiteUrl -Interactive

# Create document library
$libraryName = "AI Claims Substantiation"
New-PnPList -Title $libraryName -Template DocumentLibrary

# Enable versioning
Set-PnPList -Identity $libraryName -EnableVersioning $true -MajorVersions 50 -EnableMinorVersions $false

# Create folder structure
$categories = @(
    "Performance Claims",
    "Capability Claims",
    "Comparative Claims",
    "Predictive Claims",
    "Efficiency Claims"
)

foreach ($category in $categories) {
    Add-PnPFolder -Name $category -Folder $libraryName
    Write-Host "Created folder: $category" -ForegroundColor Cyan
}

Write-Host "Substantiation library created successfully." -ForegroundColor Green

# Disconnect
Disconnect-PnPOnline

Script 3: Export Claims Inventory Report

<#
.SYNOPSIS
    Exports the AI claims inventory to CSV for compliance reporting.

.PARAMETER SiteUrl
    The URL of the governance SharePoint site.

.PARAMETER OutputPath
    Path for the output CSV file.

.EXAMPLE
    .\Export-AIClaimsReport.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/AIGovernance" -OutputPath "C:\Reports\AIClaimsReport.csv"
#>

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

    [Parameter(Mandatory=$true)]
    [string]$OutputPath
)

# Connect to SharePoint
Connect-PnPOnline -Url $SiteUrl -Interactive

# Get all claims
$listName = "AI Marketing Claims Inventory"
$claims = Get-PnPListItem -List $listName -PageSize 500

# Build report data
$reportData = @()
foreach ($claim in $claims) {
    $reportData += [PSCustomObject]@{
        ClaimID = $claim.Id
        ClaimText = $claim.FieldValues.ClaimText
        Category = $claim.FieldValues.ClaimCategory
        AgentProduct = $claim.FieldValues.AgentProduct
        Channel = $claim.FieldValues.TargetChannel
        Zone = $claim.FieldValues.GovernanceZone
        Status = $claim.FieldValues.ClaimStatus
        SubmissionDate = $claim.FieldValues.SubmissionDate
        ApprovalDate = $claim.FieldValues.ApprovalDate
        NextReviewDate = $claim.FieldValues.NextReviewDate
        HasSubstantiation = if ($claim.FieldValues.SubstantiationFile) { "Yes" } else { "No" }
    }
}

# Export to CSV
$reportData | Export-Csv -Path $OutputPath -NoTypeInformation

Write-Host "Claims report exported to: $OutputPath" -ForegroundColor Green
Write-Host "Total claims: $($reportData.Count)" -ForegroundColor Cyan

# Summary statistics
$summary = $reportData | Group-Object -Property Status | Select-Object Name, Count
Write-Host "`nClaims by Status:" -ForegroundColor Yellow
$summary | Format-Table -AutoSize

# Disconnect
Disconnect-PnPOnline

Script 4: Identify Claims Due for Review

<#
.SYNOPSIS
    Identifies AI claims due for quarterly review.

.PARAMETER SiteUrl
    The URL of the governance SharePoint site.

.PARAMETER DaysAhead
    Number of days to look ahead for upcoming reviews. Default: 14

.EXAMPLE
    .\Get-ClaimsDueForReview.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/AIGovernance"
#>

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

    [int]$DaysAhead = 14
)

# Connect to SharePoint
Connect-PnPOnline -Url $SiteUrl -Interactive

$listName = "AI Marketing Claims Inventory"
$cutoffDate = (Get-Date).AddDays($DaysAhead)

# Build CAML query for approved claims with upcoming review dates
$camlQuery = @"
<View>
    <Query>
        <Where>
            <And>
                <Eq>
                    <FieldRef Name='ClaimStatus'/>
                    <Value Type='Choice'>Approved</Value>
                </Eq>
                <Leq>
                    <FieldRef Name='NextReviewDate'/>
                    <Value Type='DateTime'>$($cutoffDate.ToString("yyyy-MM-dd"))</Value>
                </Leq>
            </And>
        </Where>
    </Query>
</View>
"@

$claimsDue = Get-PnPListItem -List $listName -Query $camlQuery

if ($claimsDue.Count -eq 0) {
    Write-Host "No claims due for review in the next $DaysAhead days." -ForegroundColor Green
} else {
    Write-Host "Claims due for review:" -ForegroundColor Yellow
    foreach ($claim in $claimsDue) {
        Write-Host "  - ID: $($claim.Id) | $($claim.FieldValues.ClaimText.Substring(0, [Math]::Min(50, $claim.FieldValues.ClaimText.Length)))..." -ForegroundColor Cyan
        Write-Host "    Review Due: $($claim.FieldValues.NextReviewDate)" -ForegroundColor Gray
    }
}

# Disconnect
Disconnect-PnPOnline

Validation Script

<#
.SYNOPSIS
    Validates Control 2.21 configuration.

.PARAMETER SiteUrl
    The URL of the governance SharePoint site.
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$SiteUrl
)

Connect-PnPOnline -Url $SiteUrl -Interactive

$results = @()

# Check 1: Claims Inventory List exists
$claimsList = Get-PnPList -Identity "AI Marketing Claims Inventory" -ErrorAction SilentlyContinue
$results += [PSCustomObject]@{
    Check = "Claims Inventory List exists"
    Status = if ($claimsList) { "PASS" } else { "FAIL" }
}

# Check 2: Substantiation Library exists
$substLib = Get-PnPList -Identity "AI Claims Substantiation" -ErrorAction SilentlyContinue
$results += [PSCustomObject]@{
    Check = "Substantiation Library exists"
    Status = if ($substLib) { "PASS" } else { "FAIL" }
}

# Check 3: Required columns exist
$requiredColumns = @("ClaimText", "ClaimCategory", "ClaimStatus", "SubstantiationFile", "NextReviewDate")
$listFields = Get-PnPField -List "AI Marketing Claims Inventory"
$missingColumns = $requiredColumns | Where-Object { $_ -notin $listFields.InternalName }
$results += [PSCustomObject]@{
    Check = "Required columns configured"
    Status = if ($missingColumns.Count -eq 0) { "PASS" } else { "FAIL - Missing: $($missingColumns -join ', ')" }
}

# Display results
Write-Host "`nControl 2.21 Validation Results:" -ForegroundColor Yellow
$results | Format-Table -AutoSize

Disconnect-PnPOnline

Complete Configuration Script

<#
.SYNOPSIS
    Complete AI marketing claims configuration for Control 2.21

.DESCRIPTION
    Executes end-to-end marketing claims setup including:
    - Claims inventory list creation
    - Substantiation evidence library setup
    - Folder structure creation
    - Claims status export

.PARAMETER SiteUrl
    SharePoint site URL for governance

.PARAMETER OutputPath
    Path for output reports

.EXAMPLE
    .\Configure-Control-2.21.ps1 -SiteUrl "https://contoso.sharepoint.com/sites/AIGovernance" -OutputPath ".\MarketingClaims"

.NOTES
    Last Updated: January 2026
    Related Control: Control 2.21 - AI Marketing Claims and Substantiation
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$SiteUrl,
    [string]$OutputPath = ".\MarketingClaims-Report"
)

try {
    Write-Host "=== Control 2.21: AI Marketing Claims Configuration ===" -ForegroundColor Cyan

    # Connect to SharePoint
    Connect-PnPOnline -Url $SiteUrl -Interactive

    # Ensure output directory exists
    New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null

    # Step 1: Create or verify Claims Inventory List
    Write-Host "`n[Step 1] Configuring Claims Inventory List..." -ForegroundColor Cyan
    $listName = "AI Marketing Claims Inventory"
    $claimsList = Get-PnPList -Identity $listName -ErrorAction SilentlyContinue

    if (-not $claimsList) {
        Write-Host "  Creating list: $listName" -ForegroundColor Yellow
        New-PnPList -Title $listName -Template GenericList

        # Add columns
        Add-PnPField -List $listName -DisplayName "Claim Text" -InternalName "ClaimText" -Type Note
        Add-PnPField -List $listName -DisplayName "Claim Category" -InternalName "ClaimCategory" -Type Choice `
            -Choices "Performance","Capability","Comparative","Predictive","Efficiency"
        Add-PnPField -List $listName -DisplayName "Agent/Product" -InternalName "AgentProduct" -Type Text
        Add-PnPField -List $listName -DisplayName "Target Channel" -InternalName "TargetChannel" -Type Choice `
            -Choices "Website","Email","Social Media","Sales Collateral","Press Release","Multiple"
        Add-PnPField -List $listName -DisplayName "Governance Zone" -InternalName "GovernanceZone" -Type Choice `
            -Choices "Zone 1 - Personal","Zone 2 - Team","Zone 3 - Enterprise"
        Add-PnPField -List $listName -DisplayName "Substantiation File" -InternalName "SubstantiationFile" -Type URL
        Add-PnPField -List $listName -DisplayName "Status" -InternalName "ClaimStatus" -Type Choice `
            -Choices "Draft","Under Review","Approved","Rejected","Retired"
        Add-PnPField -List $listName -DisplayName "Submitted By" -InternalName "SubmittedBy" -Type User
        Add-PnPField -List $listName -DisplayName "Submission Date" -InternalName "SubmissionDate" -Type DateTime
        Add-PnPField -List $listName -DisplayName "Compliance Reviewer" -InternalName "ComplianceReviewer" -Type User
        Add-PnPField -List $listName -DisplayName "Review Date" -InternalName "ReviewDate" -Type DateTime
        Add-PnPField -List $listName -DisplayName "Approval Date" -InternalName "ApprovalDate" -Type DateTime
        Add-PnPField -List $listName -DisplayName "Next Review Date" -InternalName "NextReviewDate" -Type DateTime
        Add-PnPField -List $listName -DisplayName "Review Comments" -InternalName "ReviewComments" -Type Note

        Write-Host "  [CREATED] $listName with all columns" -ForegroundColor Green
    } else {
        Write-Host "  [EXISTS] $listName" -ForegroundColor Yellow
    }

    # Step 2: Create or verify Substantiation Library
    Write-Host "`n[Step 2] Configuring Substantiation Library..." -ForegroundColor Cyan
    $libraryName = "AI Claims Substantiation"
    $substLib = Get-PnPList -Identity $libraryName -ErrorAction SilentlyContinue

    if (-not $substLib) {
        Write-Host "  Creating library: $libraryName" -ForegroundColor Yellow
        New-PnPList -Title $libraryName -Template DocumentLibrary

        # Enable versioning
        Set-PnPList -Identity $libraryName -EnableVersioning $true -MajorVersions 50

        # Create folder structure
        $categories = @(
            "Performance Claims",
            "Capability Claims",
            "Comparative Claims",
            "Predictive Claims",
            "Efficiency Claims"
        )

        foreach ($category in $categories) {
            Add-PnPFolder -Name $category -Folder $libraryName
            Write-Host "    Created folder: $category" -ForegroundColor Cyan
        }

        Write-Host "  [CREATED] $libraryName with folder structure" -ForegroundColor Green
    } else {
        Write-Host "  [EXISTS] $libraryName" -ForegroundColor Yellow
    }

    # Step 3: Export current claims status
    Write-Host "`n[Step 3] Exporting claims inventory..." -ForegroundColor Cyan
    $claims = Get-PnPListItem -List $listName -PageSize 500 -ErrorAction SilentlyContinue

    if ($claims -and $claims.Count -gt 0) {
        $claimsExport = $claims | ForEach-Object {
            [PSCustomObject]@{
                ClaimID = $_.Id
                ClaimText = $_.FieldValues.ClaimText
                Category = $_.FieldValues.ClaimCategory
                AgentProduct = $_.FieldValues.AgentProduct
                Channel = $_.FieldValues.TargetChannel
                Zone = $_.FieldValues.GovernanceZone
                Status = $_.FieldValues.ClaimStatus
                SubmissionDate = $_.FieldValues.SubmissionDate
                ApprovalDate = $_.FieldValues.ApprovalDate
                NextReviewDate = $_.FieldValues.NextReviewDate
                HasSubstantiation = if ($_.FieldValues.SubstantiationFile) { "Yes" } else { "No" }
            }
        }

        $claimsExport | Export-Csv -Path "$OutputPath\ClaimsInventory.csv" -NoTypeInformation
        Write-Host "  Exported $($claimsExport.Count) claims" -ForegroundColor Cyan

        # Summary by status
        Write-Host "`n=== Claims Summary ===" -ForegroundColor Cyan
        $claimsExport | Group-Object -Property Status | ForEach-Object {
            Write-Host "  $($_.Name): $($_.Count)"
        }
    } else {
        Write-Host "  No claims found in inventory" -ForegroundColor Yellow
    }

    Write-Host "`n[PASS] Control 2.21 configuration completed successfully" -ForegroundColor Green
}
catch {
    Write-Host "[FAIL] Error: $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "[INFO] Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Yellow
    exit 1
}
finally {
    # Cleanup connections
    Disconnect-PnPOnline -ErrorAction SilentlyContinue
}

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