scripts/local/power-platform.ps1

<#
    Raidiness — Power Platform & Copilot Studio (local, read-only)

    Why local: the inventory API only becomes reachable app-only after a
    service principal has been registered with New-PowerAppManagementApp. That
    is a write action in the customer's tenant, and Raidiness never performs
    it itself (CLAUDE.md #1). If you prefer not to, run this script once
    yourself: it reads the same data with the rights the administrator
    already has.

    Role: Power Platform Administrator (or Global Reader with AI Reader).
    Writes: nothing. Only Get-* cmdlets and a JSON file on your own disk.
    Output: JSON you upload in Raidiness under the "Power Platform" module.

    Usage:
      .\power-platform.ps1 -TenantId <tenant> -RunId <run> -OutputPath .\pp.json
      .\power-platform.ps1 -TenantId <tenant> -RunId <run> -HashUpns
#>

# psrunner-lint allow: Add-PowerAppsAccount — interactive sign-in of the administrator, not a write action

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)][string] $TenantId,
    [Parameter(Mandatory = $true)][string] $RunId,
    [string] $OutputPath = ".\raidiness-power-platform.json",
    # Replaces e-mail addresses with a hash: the report counts owners, it does
    # not need to know them by name.
    [switch] $HashUpns
)

$ErrorActionPreference = "Stop"

function Protect-Upn {
    param([string] $Value)
    if (-not $HashUpns -or [string]::IsNullOrWhiteSpace($Value)) { return $Value }
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value.ToLowerInvariant())
    $sha = [System.Security.Cryptography.SHA256]::Create()
    return ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) -join "" |
        ForEach-Object { $_.Substring(0, 16) }
}

Write-Host "Raidiness — Power Platform, read-only. Role: Power Platform Administrator." -ForegroundColor Cyan
Write-Host "Nothing will be changed in the tenant." -ForegroundColor Cyan

if (-not (Get-Module -ListAvailable -Name Microsoft.PowerApps.Administration.PowerShell)) {
    throw "The Microsoft.PowerApps.Administration.PowerShell module is missing. Install it first: Install-Module Microsoft.PowerApps.Administration.PowerShell -Scope CurrentUser"
}

Import-Module Microsoft.PowerApps.Administration.PowerShell
Add-PowerAppsAccount -TenantID $TenantId | Out-Null

$environments = Get-AdminPowerAppEnvironment | Select-Object `
    EnvironmentName, DisplayName, Location, EnvironmentType, IsDefault, CreatedTime

$dlp = Get-DlpPolicy | Select-Object `
    PolicyName, DisplayName, Environments, DefaultConnectorsClassification, ConnectorGroups

$agents = @()
foreach ($environment in $environments) {
    $apps = Get-AdminPowerApp -EnvironmentName $environment.EnvironmentName |
        Where-Object { $_.AppType -eq "CopilotStudio" -or $_.Internal.properties.appType -eq "Copilot" }

    foreach ($app in $apps) {
        $agents += [ordered]@{
            environmentName = $environment.EnvironmentName
            displayName     = $app.DisplayName
            appName         = $app.AppName
            owner           = Protect-Upn ($app.Owner.email)
            createdTime     = $app.CreatedTime
            lastModified    = $app.LastModifiedTime
            sharedWithAll   = [bool]($app.Internal.properties.sharedGroupsCount -gt 0)
            authentication  = $app.Internal.properties.authenticationType
        }
    }
}

$payload = [ordered]@{
    raidiness = [ordered]@{
        version     = "1.0"
        module      = "power-platform"
        tenantId    = $TenantId
        runId       = $RunId
        generatedAt = (Get-Date).ToUniversalTime().ToString("o")
        upnsHashed  = [bool] $HashUpns
    }
    results = [ordered]@{
        "powerplatform.environments"      = $environments
        "powerplatform.dlpPolicies"       = $dlp
        "powerplatform.inventory.agents"  = $agents
    }
}

$payload | ConvertTo-Json -Depth 8 | Out-File -FilePath $OutputPath -Encoding utf8
Write-Host "Done. Upload $OutputPath in Raidiness under the Power Platform module." -ForegroundColor Green