Script PowerShell che consente a un Service Principal (SPN) di:
- Autenticarsi su Power Platform tramite client credentials
- Impostarsi come Environment Admin sull'environment target
- Assegnare un utente di servizio come owner degli oggetti (App, Flow, Connection Reference, Environment Variable, Dataflow, Custom Connector, Chatbot, Custom Page) presenti in una soluzione COE specifica
- Verificare e rimuovere il vecchio owner dagli oggetti trasferiti con successo
- PowerShell 5.1+
- Modulo:
Microsoft.PowerApps.Administration.PowerShell(installato automaticamente se mancante) - Service Principal registrato in Azure AD con:
- Permessi di Power Platform Admin (o possibilita' di assegnarsi Environment Admin)
- Application User configurato in Dataverse (per le API)
- Utente di servizio gia' presente in Dataverse come systemuser
├── Set-COEAppOwner.ps1 # Script principale
├── config.json # File di configurazione (escluso da git)
├── config.example.json # Esempio di configurazione con placeholder
├── logs/ # Directory log e report (creata automaticamente)
└── README.md
Copiare config.example.json in config.json e compilare i valori:
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
TenantId |
string | Si | GUID del tenant Azure AD |
ClientId |
string | Si | Application (client) ID del Service Principal |
ClientSecret |
string | Si | Client secret del Service Principal |
EnvironmentId |
string | Si | GUID dell'ambiente Power Platform target |
EnvironmentUrl |
string | No | URL Dataverse (es. https://orgXXX.crm4.dynamics.com/). Se vuoto viene risolto automaticamente |
SolutionName |
string | Si | Unique name della soluzione COE |
ServiceUserEmail |
string | Si | Email dell'utente di servizio da assegnare come owner |
ServiceUserObjectId |
string | No | Object ID Dataverse dell'utente di servizio. Se vuoto viene risolto automaticamente via systemuser |
OldOwnerObjectId |
string | No | GUID del vecchio owner. Se specificato, vengono processati solo gli oggetti che hanno questo owner e al termine viene rimosso |
DryRun |
bool | No | true = simulazione senza modifiche reali (default: true) |
ProcessApps |
bool | No | Processa le Canvas App e Model-Driven App (default: true) |
ProcessFlows |
bool | No | Processa i Cloud Flow, inclusi quelli disabilitati (default: true) |
ProcessConnectionReferences |
bool | No | Processa le Connection Reference (default: true) |
ProcessEnvironmentVariables |
bool | No | Processa le Environment Variable (default: true) |
ProcessDataflows |
bool | No | Processa i Dataflow (default: true) |
ProcessCustomConnectors |
bool | No | Processa i Custom Connector (default: true) |
ProcessChatbots |
bool | No | Processa i Chatbot / Copilot Studio Bot (default: true) |
ProcessPages |
bool | No | Processa le Custom Page (componenttype=300) (default: true) |
LogPath |
string | No | Percorso directory log (default: ./logs) |
AppFilter.ExcludeApps |
array | No | Nomi app da escludere |
AppFilter.IncludeOnly |
array | No | Se specificato, processa solo queste app |
FlowFilter.ExcludeFlows |
array | No | Nomi flow da escludere |
FlowFilter.IncludeOnly |
array | No | Se specificato, processa solo questi flow |
# Esecuzione con configurazione di default
.\Set-COEAppOwner.ps1
# Esecuzione con file di configurazione custom
.\Set-COEAppOwner.ps1 -ConfigPath "C:\Config\prod-config.json"
# Esecuzione non interattiva (salta la conferma)
$env:COE_FORCE = "1"
.\Set-COEAppOwner.ps1- Lettura e validazione config - Campi obbligatori, formato GUID, formato email
- Prerequisiti - Verifica/installa il modulo PowerApps Admin
- Autenticazione SPN -
Add-PowerAppsAccountcon client credentials - Environment Admin - Assegna il ruolo Environment Admin all'SPN sull'environment target
- Recupero URL Dataverse - Tre tentativi: cmdlet PS, BAP Admin API, Global Discovery Service
- Token Dataverse - Ottiene un token OAuth2 per le API Dataverse
- Risoluzione utente di servizio - Da config o lookup in Dataverse
systemuser - Query soluzione - Recupera tutti i componenti della soluzione via Dataverse API
- Applicazione filtri - IncludeOnly / Exclude su App e Flow
- Riepilogo e conferma - Mostra gli elementi trovati; in modalita' reale chiede conferma interattiva
- Assegnazione owner - Per ogni tipo di componente:
- App:
Set-AdminPowerAppOwner(cmdlet PowerApps Admin) - Flow (attivo): PATCH Dataverse
workflowsconownerid@odata.bind - Flow (disabilitato): abilitazione temporanea, PATCH owner, ripristino stato off; se le connessioni non sono accessibili dall'SPN il flow viene saltato con warning
- Custom Page: PATCH Dataverse
canvasappsconownerid@odata.bind - Connection Reference / Environment Variable / Dataflow / Custom Connector / Chatbot: PATCH Dataverse con
ownerid@odata.bind
- App:
- Verifica e rimozione vecchio owner - Per ogni oggetto con Status=Success, verifica via Dataverse che
_ownerid_valuesia il service user; se confermato rimuoveOldOwnerObjectId(role assignment per App/Page, conferma per i record Dataverse single-owner) - Report CSV - Esporta i risultati
- Riepilogo finale - Conteggi Success / Skipped / Failed
I flussi in stato "off" richiedono una strategia speciale perche' il PATCH diretto di Dataverse viene bloccato per i workflow inattivi:
- Tenta PATCH diretto (funziona per flussi attivi)
- Se fallisce, abilita temporaneamente il flusso, cambia owner, ridisabilita
- Se l'abilitazione fallisce per
ConnectionAuthorizationFailed(connessioni non accessibili dall'SPN), il flusso viene marcato comeSkippedcon dettaglio nel report
Lo script genera nella directory LogPath:
- Log file:
COE_OwnerChange_YYYYMMDD_HHmmss.log - Report CSV:
COE_OwnerChange_Report_YYYYMMDD_HHmmss.csv
| Colonna | Descrizione |
|---|---|
| Type | App, Flow, ConnectionReference, EnvironmentVariable, Dataflow, CustomConnector, Chatbot, Page |
| Name | Nome del componente |
| Id | Identificativo univoco Dataverse |
| Status | Success, Failed, Skipped, DryRun |
| OldOwner | Object ID del vecchio owner (se verificato) |
| NewOwner | Email dell'utente di servizio assegnato |
| Error | Dettaglio errore o motivo dello skip |
| Codice | Significato |
|---|---|
| 0 | Completato con successo |
| 1 | Completato con errori parziali |
| 2 | Errore critico |
config.jsondeve essere escluso da git (contiene il ClientSecret)- Lo script usa
DryRun: truecome default per prevenire modifiche accidentali - La conferma interattiva e' richiesta in modalita' reale (bypassabile con
$env:COE_FORCE=1) - Il
ClientSecretin produzione dovrebbe provenire da Azure Key Vault o variabili d'ambiente