Fork of Power Platform Control Hub by Daniel Laskewitz, extended with a deep Copilot Studio analytics layer. All the original tenant-wide CoE capabilities are intact — this fork adds a dedicated Copilot Studio tab.
A Center of Excellence (CoE) Starter Kit dashboard replacement built as a Power Apps Code App. It uses the Power Platform Inventory API and several Power Platform admin connectors — including Microsoft Dataverse — to surface a real-time view of all resources across your tenant: canvas apps, model-driven apps, cloud flows, agent flows, code apps, Copilot Studio agents, and environments. No CoE Starter Kit solution required.
🔐 Authentication is handled entirely by the Power Apps host. No app registration or MSAL configuration is required.
The Copilot Studio tab adds per-environment depth on top of the tenant-wide inventory:
- Environment selector — pick any Dataverse environment in the tenant; data is read live from that environment's Dataverse via the cross-environment connector.
- Control Tower — fleet health ring, needs-attention inbox, cost-vs-budget, and top spenders.
- Cost — credit modeling from conversation transcripts (configurable credits/step), trend, by-agent / by-kind / by-owner rollups, distribution, and anomaly detection.
- Conversations — conversation replay with per-step orchestration trace and cost.
- Governance — orphaned / unpublished / stale / no-auth / high-failure / premium-connector findings per agent.
- Agents — agent inventory with a detail drawer (components, connections, runs).
- M365 Agents — agents built in the Microsoft 365 Copilot Agent Builder store their setup in Microsoft 365, not Dataverse. This tab reads their configuration (instructions, capabilities, knowledge, actions) via the Microsoft Graph Copilot Package Management API through a custom connector (see
connectors/m365-copilot-packages/). Requires a Microsoft Agent 365 license +CopilotPackages.Read.All.
The deep layer lives under src/copilot-studio/ and reuses Daniel's ListRecordsWithOrganization connector pattern for tenant-wide reach.
| Tab | What it shows |
|---|---|
| 🏠 Overview | Metric cards per resource type + recently created resources table |
| 📋 Resources | Sortable, searchable, filterable table of all resources across all environments. Click any canvas app, cloud flow, or agent to open a full detail panel. |
| 🌍 Environments | Card grid of every environment with type badge, managed-environment indicator, region, and resource count. Click any environment to open the environment detail view. |
| 🛡️ Tenant Policies | DLP policies (list, create, detail), billing policies, and cross-tenant connection reports |
| 🗂️ Environment Groups | Environment groups, rule-based policies, and rule sets (CRUD) |
| 🔌 Connectors | Per-environment connections, connectors, and Power Pages websites |
| 💡 Recommendations | Advisor recommendations from the admin API |
Each supported resource type has a full-page detail panel with structured sections, action bar, and Best Practice Analysis (BPA) checks.
Sections: App Details · Inventory · Governance & Sharing · Role Assignments · Best Practice Analysis
Actions: Quarantine / Unquarantine · Add owner (AAD user search) · Elevated admin access / Remove access
Best Practice Analysis — 9 checks:
| # | Severity | Check |
|---|---|---|
| 1 | ℹ️ Info | Missing app description |
| 2 | Excessive connector usage (>10 connections) | |
| 3 | ℹ️ Info | Premium connector(s) in use |
| 4 | ℹ️ Info | On-premises gateway connection(s) |
| 5 | App shared with >500 individual users | |
| 6 | ℹ️ Info | App shared with >100 individual users |
| 7 | Consent bypass is enabled | |
| 8 | App not modified in over a year (stale) | |
| 9 | ℹ️ Info | App not modified in over 6 months |
| 10 | App is shared with the entire organisation | |
| 11 | ℹ️ Info | App has no co-owners / co-developers |
Sections: Flow Details · Inventory · Triggers & Actions (recursive tree) · Owners · Run-Only Users · Best Practice Analysis
Actions: Enable · Disable · Delete · Add owner · Elevated admin access / Remove access
The Triggers & Actions section renders the complete flow graph:
- Conditions with True / False branches side-by-side
- Loops (
Apply to each,Do until), scopes, and switch/case blocks as collapsible containers - Connector actions with resolved connector name and humanised operation label
- Trigger: connector + event, schedule details, or kind label (manual/instant)
Best Practice Analysis — 26 checks:
| # | Severity | Check |
|---|---|---|
| 1 | 🔴 Critical | No error handling detected |
| 2 | ℹ️ Info | No trigger conditions set |
| 3 | Very high-frequency recurrence trigger | |
| 4 | ℹ️ Info | Trigger concurrency limit not set |
| 5 | ℹ️ Info | Many actions have default names |
| 6 | HTTP actions without timeout | |
| 7 | 🔴 Critical | Possible sensitive data in unprotected inputs |
| 8 | ℹ️ Info | "Apply to each" loops run sequentially |
| 9 | ℹ️ Info | List actions may not retrieve all items (pagination) |
| 10 | ℹ️ Info | Flow has no description |
| 11 | Nested "Apply to each" loops detected | |
| 12 | Notification or messaging actions inside a loop | |
| 13 | HTTP actions have retries disabled | |
| 14 | ℹ️ Info | Flow is very large (>50 actions) |
| 15 | Switch actions missing a default case | |
| 16 | ℹ️ Info | Recurrence trigger has no explicit start time / time zone |
| 17 | "Do Until" loops without a meaningful iteration limit | |
| 18 | ℹ️ Info | HTTP actions use hardcoded URLs |
| 19 | ℹ️ Info | Deeply nested actions detected |
| 20 | ℹ️ Info | Error-path Terminate actions do not use "Failed" status |
| 21 | ℹ️ Info | Trigger uses a default name |
| 22 | ℹ️ Info | Most steps have no description (comment) |
| 23 | 🔴 Critical | HTTP trigger has no Response action |
| 24 | Parse JSON action(s) without a schema | |
| 25 | Variables used in a concurrent flow | |
| 26 | ℹ️ Info | Empty scope(s) found |
Sections: Agent Details · Inventory · Definition (Configuration) · Best Practice Analysis
Actions: Quarantine / Unquarantine · Delete · Elevated admin access / Remove access
Agent Details includes: display name, schema name, status, language, authentication mode, access control, quarantine state, last published, created/modified, owner, environment, Dataverse URL, and agent ID (Entra + Dataverse).
Inventory section shows: orchestration type, model, authentication, channels, capabilities, sharing (viewers/editors), published date, and creator tool.
Best Practice Analysis — 13 checks:
| # | Severity | Check |
|---|---|---|
| 1 | Agent is inactive | |
| 2 | Agent has never been published | |
| 3 | Authentication mode is None or Unspecified | |
| 4 | ℹ️ Info | No configuration data found |
| 5 | ℹ️ Info | No primary language configured |
| 6 | Access control allows anyone | |
| 7 | 🔴 Critical | Group membership access control has no groups configured |
| 8 | Agent allows multi-tenant access | |
| 9 | ℹ️ Info | Agent not re-published in N months (stale) |
| 10 | ℹ️ Info | Inactive topic(s) found |
| 11 | High percentage of topics are disabled (>50%) | |
| 12 | ℹ️ Info | No knowledge sources configured |
| 13 | ℹ️ Info | No test cases defined |
Sections: Resources tab (table of all resources in the environment) · Analysis tab
Actions (Actions menu): Enable / Disable · Enable / Disable Managed Environment · Create Backup · Apply admin access · Add to / Remove from Group
The Resources table includes Name, Type, Created, Modified, Owner columns. Clicking the ↗ icon on a Canvas App, Cloud Flow, or Agent row opens that resource's full detail panel.
Best Practice Analysis — 6 checks:
| # | Severity | Check |
|---|---|---|
| 1 | This is the Default environment | |
| 2 | Trial environment will expire | |
| 3 | ℹ️ Info | Not a Managed Environment |
| 4 | ℹ️ Info | Not assigned to an Environment Group |
| 5 | ℹ️ Info | Large environment (>200 resources) |
| 6 | ℹ️ Info | Environment URL appears auto-generated |
- Full-page list of all tenant DLP policies (V2 API)
- ➕ Create page: two-stage flow — basic settings (name, scope, default classification) then connector classification (Confidential / General / Blocked buckets)
- 📄 Detail page: collapsible accordion sections for Policy Details, Connector Groups, Environments, and Advisories
- ✨ Apply Best Practices: analyses the policy against advisory rules (e.g. HTTP connector → Blocked, SharePoint → Confidential) and proposes changes before saving
- Create, edit, and delete environment groups
- Manage environment membership per group
- Rule-based policies: create, assign to groups, edit, extract rule sets
- Rule sets: full CRUD with JSON-based parameter editing
- 🌙 Light / dark mode toggle (preference saved to
localStorage) - 📱 Responsive layout (mobile hamburger menu)
- Fluent UI v9 — consistent with Microsoft 365 design language
- ♿ Accessible (WCAG-compliant contrast, ARIA labels, keyboard navigation)
- 🔔 Toast notifications for all write actions
- Inline error messages with expandable details
CoE-Code/
├── src/
│ ├── App.tsx # Root: tab navigation, theme, layout
│ ├── types/
│ │ ├── inventory.ts # Resource, Environment, ResourceCounts
│ │ └── admin.ts # Governance, DLP, billing, connector types
│ ├── hooks/
│ │ ├── useInventory.ts # Fetches resources & environments
│ │ └── useAdminData.ts # Fetches admin data (DLP, groups, policies…)
│ ├── services/
│ │ ├── inventoryApi.ts # Inventory API calls
│ │ ├── adminApi.ts # Admin V2 API calls (connectors, groups…)
│ │ ├── dlpService.ts # DLP policy CRUD (Power Platform for Admins)
│ │ ├── canvasAppAnalyzer.ts # 11 best-practice checks for canvas apps
│ │ ├── canvasAppAdminService.ts # Canvas app governance via Power Apps for Admins
│ │ ├── flowManagementService.ts # Flow enable/disable/delete
│ │ ├── flowAnalyzer.ts # 26 best-practice checks for cloud/agent flows
│ │ ├── copilotStudioService.ts # Copilot Studio bot Dataverse queries + Admin V2 actions
│ │ ├── governanceMutations.ts # Env group / policy / rule set write ops
│ │ ├── environmentMutations.ts # Environment write ops
│ │ ├── ownerCache.ts # AAD user display name resolution
│ │ ├── settingsService.ts # Environment management settings (read/write)
│ │ └── tombstoneService.ts # Soft-delete tracking for resources
│ ├── utils/
│ │ ├── errorUtils.ts # Error message extraction helpers
│ │ ├── formatDate.ts # Date formatting helpers
│ │ ├── inventoryFormatters.ts # Shared inventory data formatting helpers
│ │ └── lcidUtils.ts # LCID → language name resolution
│ └── components/
│ ├── Dashboard.tsx # Overview / metric cards
│ ├── ResourcesView.tsx # Resources table + detail panel routing
│ ├── CanvasAppDetailPanel.tsx # Canvas app detail + analysis
│ ├── CloudFlowDetailPanel.tsx # Cloud/agent flow detail + analysis
│ ├── CopilotStudioAgentDetailPanel.tsx # Copilot Studio agent detail + analysis
│ ├── EnvironmentsView.tsx # Environment cards
│ ├── EnvironmentDetailView.tsx # Single environment detail + settings + analysis
│ ├── RecommendationsView.tsx # Advisor recommendations
│ ├── GovernanceView.tsx # Tenant Policies tab (DLP, billing, reports)
│ ├── DlpPoliciesView.tsx # Full-page DLP list / create / detail
│ ├── EnvironmentGroupsView.tsx # Environment Groups tab
│ ├── ConnectorsView.tsx # Connectors / connections tab
│ ├── AddSelfAsAdminBanner.tsx # Elevated access button (inline + menu variants)
│ └── ConfirmDialog.tsx # Reusable confirmation dialog
├── generated/ # Auto-generated connector clients (gitignored)
│ ├── models/
│ └── services/
├── index.html
├── vite.config.ts
├── package.json
├── tsconfig.app.json
├── power.config.json # Code App + connector connection references
└── deploy/
├── Deploy.ps1 # Multi-environment deploy script
└── env-config.json # Connection IDs per environment (gitignored)
Data flow:
- The Power Apps host handles authentication — the app renders immediately with no login screen.
useInventoryanduseAdminDatacall the generated connector service clients on mount.- Connector calls are proxied through the Power Apps host to the respective admin APIs.
- Results are stored in React state and rendered by the view components.
| Tool | Minimum version |
|---|---|
| Node.js (LTS) | 18 |
| Git | any |
| Power Platform environment with code apps enabled | — |
| Power Platform tenant admin account | — |
This app requires five connectors. For each one, create a connection in make.powerapps.com (Connections → New connection), note the Connection ID from the URL, then run add-data-source.
| Connector | API ID | Docs |
|---|---|---|
| Power Platform Admin V2 | shared_powerplatformadminv2 |
Reference |
| Power Platform for Admins | shared_powerplatformforadmins |
Reference |
| Power Apps for Admins | shared_powerappsforadmins |
Reference |
| Flow Management | shared_flowmanagement |
Reference |
| Microsoft Dataverse | shared_commondataserviceforapps |
Reference |
Each resource type pulls from one or more connectors (and optionally a Dataverse table):
| Resource | List / inventory | Detail & mutations | Notes |
|---|---|---|---|
| Environments | Admin V2 → QueryResources |
Admin V2 → GetEnvironmentByIdForUser |
Domain/URL data lazy-fetched on Analysis tab open |
| Canvas Apps | Admin V2 → QueryResources |
Power Apps for Admins → GetApp, GetAppRoleAssignment |
Connection refs, owner, shared-with counts |
| Model-Driven Apps | Admin V2 → QueryResources |
(inventory metadata only) | No separate detail API |
| Cloud Flows | Admin V2 → QueryResources |
Flow Management → GetAdminFlowWithConnectionReferences, GetFlowPermissions |
Full trigger/action graph |
| Agent Flows | Admin V2 → QueryResources |
Flow Management (same as Cloud Flows) | |
| M365 Agent Flows | Admin V2 → QueryResources |
Flow Management (same as Cloud Flows) | |
| Code Apps | Admin V2 → QueryResources |
(inventory metadata only) | |
| Copilot Studio Agents | Admin V2 → QueryResources |
1. Dataverse bot table (admin env) via Bots connector2. Cross-env Dataverse connector → bot + botcomponent tables |
Falls back to cross-env query when agent is not in admin env |
| DLP Policies | Power Platform for Admins → ListPoliciesV2 |
Power Platform for Admins → CreatePolicyV2 / UpdatePolicyV2 / DeletePolicyV2 |
Advisory recommendations via Admin V2 |
| Connectors & Connections | Admin V2 → GetConnectorById, connections list |
Admin V2 | Per-environment view |
| Environment Groups | Admin V2 → ListEnvironmentGroups |
Admin V2 → create / update / delete | |
| Billing Policies | Admin V2 | Admin V2 | |
| Tombstones (soft-delete) | Dataverse ppa_resourcetombstone table |
Dataverse ppa_resourcetombstone table |
Falls back to localStorage when Dataverse is unavailable |
| User display names | Microsoft Dataverse → aadusers virtual table (AAD-backed virtual entity) |
— | Used to resolve owner/created-by GUIDs across all resource types |
# Install dependencies
npm install
# Initialise the Code App (creates power.config.json if missing)
npx power-apps init --display-name "Power Platform Control Hub"
# Add each connector (answer "No" when asked about connection references)
npx power-apps add-data-source -a shared_powerplatformadminv2 -c <connection-id>
npx power-apps add-data-source -a shared_powerplatformforadmins -c <connection-id>
npx power-apps add-data-source -a shared_powerappsforadmins -c <connection-id>
npx power-apps add-data-source -a shared_flowmanagement -c <connection-id>
npx power-apps add-data-source -a shared_commondataserviceforapps -c <connection-id>The src/generated/ folder is committed to source — collaborators do not need to regenerate it. You still need to run add-data-source to create your own power.config.json connection wiring, but the TypeScript model/service files are already present.
npx power-apps runThis starts a Vite dev server and opens Power Apps in local mode. The app connects to your real tenant data with hot reload enabled.
npx power-apps push --solution-id <your-solution-id>The CLI prints a Power Apps URL when the push succeeds.
A companion Dataverse solution is included in the solution/ folder. It provides the ppa_resourcetombstone table used for soft-delete / tombstone tracking of resources.
# Import the solution into your environment
pac solution import --path solution\PowerPlatformControlHub_1_0_0_0.zip --environment <environment-id>The solution source is unpacked in solution/src/ and can be re-packed after changes:
pac solution pack --zipfile solution\PowerPlatformControlHub_1_0_0_0.zip --folder solution\src --packagetype Unmanaged- Go to Power Platform admin center.
- Manage → Environments → <your environment>.
- Settings → Product → Features.
- Toggle Enable code apps → Save.
| Package | Purpose |
|---|---|
| React 18 + TypeScript | UI framework |
| Vite 6 | Build tool / dev server |
| @microsoft/power-apps | Code Apps CLI (init, run, push, add-data-source) |
| @microsoft/power-apps-vite | Vite plugin for Power Apps integration |
| @fluentui/react-components v9 | Fluent UI component library |
| @fluentui/react-icons v2 | Fluent UI icons |
- The Inventory API is automatically paginated via
skipToken— all pages are fetched transparently, so tenants with thousands of resources are fully supported. - The app requires the signed-in user to be a Power Platform tenant admin to read cross-environment data.
- The Microsoft Dataverse connector is used to query Copilot Studio bot records. The bundled solution (
solution/) imports the requiredppa_resourcetombstonetable. - DLP policy connector groups do not auto-populate — when creating or updating a policy, all connector assignments must be explicitly provided. The create page handles this by loading connectors from a selected environment. See known issues.
- Code apps are not supported in the Power Apps mobile app or Power Apps for Windows.
- Code apps do not support Power Platform Git integration.
- The
src/generated/folder is committed to source because Dataverse-table files (Aaduser*,Bots*,Ppa_resourcetombstones*) require--resource-nameflags and are not regenerated by a plainadd-data-sourcerun. Every collaborator still needs to runadd-data-sourceto wire up their own connection IDs inpower.config.json.






