Platform Change Governance - Hands-On Labs
Status: February 2026 - FSI-AgentGov v1.2.51 Total Duration: 5-6 hours across 3 labs
Overview
These hands-on labs guide you through building the Platform Change Governance solution from scratch. Each lab builds on the previous, creating a complete governance system by the end.
| Lab | Duration | Focus | Prerequisite |
|---|---|---|---|
| Lab 1 | 45 min | Message Center Ingestion | None |
| Lab 2 | 2 hours | Path A Baseline (Model-Driven App) | Lab 1 |
| Lab 3 | 2 hours | Path B Integration (Azure DevOps) | Lab 2 |
Lab 1: Message Center Ingestion
Objective: Build non-production ingestion pipeline from Microsoft Graph API to Dataverse
Duration: 45 minutes
Prerequisites
- Power Platform environment (Developer or Sandbox)
- Microsoft Entra ID tenant with Entra Global Admin or Entra App Admin role
- Microsoft 365 E3/E5 licenses
Phase 1: Azure App Registration (15 minutes)
Step 1.1: Navigate to Azure Portal
- Go to Azure Portal
- Navigate to Microsoft Entra ID → App registrations
- Click New registration
Step 1.2: Configure App Registration
Name: MessageCenterAPI_Lab
Supported account types: Accounts in this organizational directory only (Single tenant)
Redirect URI: (leave blank)
- Click Register
Step 1.3: Note Application Details
Record these values (you'll need them later):
| Value | Location |
|---|---|
| Application (client) ID | Overview page |
| Directory (tenant) ID | Overview page |
Step 1.4: Create Client Secret
- Go to Certificates & secrets → Client secrets
- Click New client secret
- Description:
Lab Secret - Expires: 90 days (sufficient for lab)
- Click Add
- Immediately copy the secret value (shown only once)
Step 1.5: Configure API Permissions
- Go to API permissions
- Click Add a permission
- Select Microsoft Graph
- Select Application permissions
- Search for and add:
ServiceMessage.Read.All - Click Add permissions
- Click Grant admin consent for [tenant]
- Verify status shows green checkmark
Checkpoint
You should have: Application ID, Tenant ID, Client Secret, and ServiceMessage.Read.All permission with admin consent.
Phase 2: Dataverse Schema Setup (15 minutes)
Step 2.1: Create Solution
- Go to Power Apps Maker Portal
- Select your lab environment
- Go to Solutions → New solution
- Configure:
- Display name:
MessageCenterGovernance_Lab - Publisher: Select default or create new
- Version:
1.0.0.0 - Click Create
Step 2.2: Create MessageCenterPost Table
- Open your solution
- Click New → Table → Table
- Configure:
- Display name:
Message Center Post - Plural name:
Message Center Posts - Enable auditing: Yes
- Click Save
Step 2.3: Add Columns
Add these columns to MessageCenterPost:
| Display Name | Name | Type | Length/Values |
|---|---|---|---|
| Message Center ID | mc_messagecenterid | Single line of text | 50 |
| Title | mc_title | Single line of text | 500 |
| Category | mc_category | Choice | preventOrFixIssue, planForChange, stayInformed |
| Severity | mc_severity | Choice | normal, high, critical |
| Services | mc_services | Multiple lines of text | - |
| Body | mc_body | Multiple lines of text (Rich text) | - |
| Start Date | mc_startdatetime | Date and Time | - |
| State | mc_state | Choice | New, Triage, Assess, Decide, Closed |
| Last Modified | mc_lastmodifieddatetime | Date and Time | - |
Step 2.4: Create Alternate Key
- Open MessageCenterPost table → Keys
- Click New key
- Display name:
Message Center ID Key - Columns: Select
mc_messagecenterid - Click Save
Step 2.5: Enable Table Auditing
- Open table → Properties (gear icon)
- Expand Advanced options
- Enable Audit changes to its data
- Click Save
Checkpoint
MessageCenterPost table created with all columns, alternate key, and auditing enabled.
Phase 3: Power Automate Ingestion Flow (15 minutes)
Step 3.1: Create Scheduled Flow
- Go to Power Automate
- Click Create → Scheduled cloud flow
- Configure:
- Flow name:
Lab - MC Ingestion - Run every: 15 minutes
- Click Create
Step 3.2: Add HTTP Action - Get Token
- Click New step → Search for HTTP
- Configure:
Method: POST
URI: https://login.microsoftonline.com/{YOUR_TENANT_ID}/oauth2/v2.0/token
Headers:
Content-Type: application/x-www-form-urlencoded
Body: grant_type=client_credentials&client_id={YOUR_CLIENT_ID}&client_secret={YOUR_CLIENT_SECRET}&scope=https://graph.microsoft.com/.default
Replace placeholders with your values from Phase 1.
Step 3.3: Add Parse JSON
- Click New step → Parse JSON
- Content:
@body('HTTP') - Schema:
{ "type": "object", "properties": { "access_token": { "type": "string" }, "token_type": { "type": "string" }, "expires_in": { "type": "integer" } } }
Step 3.4: Add HTTP Action - Get Messages
- Click New step → HTTP
- Configure:
Method: GET
URI: https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages?$top=10&$orderby=lastModifiedDateTime desc
Headers:
Authorization: Bearer @{body('Parse_JSON')?['access_token']}
Step 3.5: Add Apply to Each
- Click New step → Apply to each
- Select output:
@body('HTTP_2')?['value']
Step 3.6: Add Dataverse Upsert (inside Apply to Each)
- Inside the loop, click Add an action
- Search for Dataverse → Update or insert a record
- Configure:
- Table name: Message Center Posts
- Alternate Key: mc_messagecenterid =
@items('Apply_to_each')?['id'] - mc_title:
@items('Apply_to_each')?['title'] - mc_category: (map to choice value)
- mc_severity: (map to choice value)
- mc_services:
@json(items('Apply_to_each')?['services']) - mc_body:
@items('Apply_to_each')?['body']?['content'] - mc_startdatetime:
@items('Apply_to_each')?['startDateTime'] - mc_lastmodifieddatetime:
@items('Apply_to_each')?['lastModifiedDateTime']
Do not set mc_state on upsert
Setting mc_state: New on every upsert would reset records that have progressed through the triage workflow (Triage → Assess → Decide) back to New. Instead, configure a Dataverse business rule to default mc_state to New only when a record is created. This preserves workflow state for existing records on subsequent ingestion runs.
Pagination (production)
This lab uses $top=10 for simplicity. In production, implement pagination by checking for @odata.nextLink in the Graph API response. Wrap the HTTP call and Apply to Each in a Do Until loop that exits when no nextLink is returned.
Step 3.7: Save and Test
- Click Save
- Click Test → Manually → Test
- Wait for completion
Lab 1 Complete
Check your Dataverse table - you should see Message Center posts!
Lab 2: Path A Baseline
Objective: Build model-driven app for triage, assessment, and decision workflows
Duration: 2 hours
Prerequisites
- Lab 1 completed successfully
- MessageCenterPost table populated with data
Phase 1: Create Additional Tables (30 minutes)
Step 2.1: Create AssessmentLog Table
- In your solution, click New → Table
- Configure:
- Display name:
Assessment Log - Primary column:
Assessment ID(auto-generated) - Enable auditing: Yes
-
Ownership: User or team
-
Add columns:
| Display Name | Name | Type |
|---|---|---|
| Message Center Post | al_messagecenterpost | Lookup (Message Center Post) |
| Assessed By | al_assessedby | Lookup (User) |
| Assessed On | al_assessedon | Date and Time |
| Impact Assessment | al_impactassessment | Choice (None, Low, Medium, High) |
| Notes | al_notes | Multiple lines of text |
| Recommended Action | al_recommendedaction | Choice (Implement, Defer, Dismiss, Escalate) |
- Configure relationship: Restrict delete on MessageCenterPost lookup
Step 2.2: Create DecisionLog Table
- Create new table:
- Display name:
Decision Log - Ownership: Organization-owned
-
Enable auditing: Yes
-
Add columns:
| Display Name | Name | Type |
|---|---|---|
| Message Center Post | dl_messagecenterpost | Lookup (Message Center Post) |
| Decided By | dl_decidedby | Lookup (User) |
| Decided On | dl_decidedon | Date and Time |
| Decision | dl_decision | Choice (Accept, Defer, Escalate, No Action Required) |
| Decision Rationale | dl_decisionrationale | Multiple lines of text |
| External Reference | dl_externalreference | Single line of text |
- Configure relationship: Restrict delete on MessageCenterPost lookup
Phase 2: Create Model-Driven App (45 minutes)
Step 2.3: Create App
- In your solution, click New → App → Model-driven app
- Name:
Message Center Governance Lab - Click Create
Step 2.4: Configure Site Map
- In app designer, click Navigation
- Add area:
Governance - Add group:
Message Center - Add pages:
- Message Center Posts (table page)
- Assessment Log (table page)
- Decision Log (table page)
Step 2.5: Configure MessageCenterPost Form
- Go to Pages → Message Center Post → Forms
- Edit main form
- Create tabs:
Tab 1: Overview - Title, Category, Severity (section) - Services, State, Owner (section) - Start Date, Action Required By (section)
Tab 2: Content - Body (full width, rich text)
Tab 3: Assessment - Impact Assessment, Relevance (section) - Subgrid: Assessment Log (related records)
Tab 4: Decision - Decision, Decision Rationale (section) - Subgrid: Decision Log (related records)
- Save and publish form
Step 2.6: Create Views
Create these views for MessageCenterPost:
- New Posts Awaiting Triage
- Filter: State equals New
-
Columns: Title, Category, Severity, Start Date
-
My Assigned Posts
- Filter: Owner equals current user
-
Columns: Title, State, Category, Severity
-
High Severity Posts
- Filter: Severity equals high OR critical
-
Columns: Title, State, Owner, Action Required By
-
Recently Closed
- Filter: State equals Closed
- Sort: Modified On descending
- Columns: Title, Decision, Decided By, Modified On
Step 2.7: Save and Publish App
- Click Save
- Click Publish
Phase 3: Configure Security Roles (30 minutes)
Step 2.8: Create MC Admin Role
- In your solution → Add existing → Security role
- Click New security role
- Name:
MC Admin Lab - Set privileges:
| Table | Create | Read | Write | Delete |
|---|---|---|---|---|
| Message Center Post | Org | Org | Org | None |
| Assessment Log | None | Org | None | None |
| Decision Log | None | Org | None | None |
Step 2.9: Create MC Owner Role
- Create new role:
MC Owner Lab - Set privileges:
| Table | Create | Read | Write | Delete |
|---|---|---|---|---|
| Message Center Post | None | User | User | None |
| Assessment Log | User | User | User | None |
| Decision Log | User | User | None | None |
Step 2.10: Assign Roles
- Go to Power Platform Admin Center
- Select environment → Users
- Select test user → Manage security roles
- Assign appropriate role
Phase 4: End-to-End Test (15 minutes)
Step 2.11: Test Complete Workflow
- Open model-driven app
- Navigate to New Posts Awaiting Triage
- Open a post
- Triage: Assign Owner to yourself, change State to Triage
- Assess: Change State to Assess, set Impact Assessment
- Create Assessment Log record with notes
- Decide: Change State to Decide, set Decision
- Create Decision Log record with rationale (50+ characters)
- Close: Change State to Closed
- Verify audit trail shows all changes
Lab 2 Complete
You have a working governance app with triage, assessment, and decision workflows!
Lab 3: Path B Integration
Objective: Demonstrate bi-directional Dataverse ↔ Azure DevOps integration
Duration: 2 hours
Prerequisites
- Lab 2 completed successfully
- Azure DevOps organization access
- Project with Work Items enabled
Phase 1: Extend Dataverse Schema (15 minutes)
Step 3.1: Add ADO Columns to DecisionLog
- Open DecisionLog table
- Add columns:
| Display Name | Name | Type |
|---|---|---|
| ADO Work Item ID | dl_ado_workitem_id | Whole Number |
| ADO Work Item URL | dl_ado_workitem_url | URL |
| ADO State | dl_ado_state | Single line of text (100) |
| ADO Last Modified | dl_ado_lastmodified | Date and Time |
- Save and publish
Phase 2: Configure Azure DevOps (20 minutes)
Step 3.2: Create Project Area
- Go to Azure DevOps → Your project
- Project Settings → Boards → Project configuration
- Create area path:
Platform Governance\Message Center
Step 3.3: Configure Service Hook
- Project Settings → Service hooks
- Click Create subscription
- Select Web Hooks
- Trigger: Work item updated
- Filters:
- Area path: Under Platform Governance
- Leave URL blank (will update after creating flow)
- Click Finish (we'll update URL later)
Phase 3: Create Dataverse → ADO Flow (30 minutes)
Step 3.4: Create Flow
- Go to Power Automate → Create → Automated cloud flow
- Name:
Lab - Create ADO Work Item - Trigger: When a row is added (Microsoft Dataverse)
- Table name: Decision Logs
- Scope: Organization
Step 3.5: Add Get Parent Post
- Add action: Get a row by ID (Dataverse)
- Table: Message Center Posts
- Row ID:
@{triggerOutputs()?['body/_al_messagecenterpost_value']}
Step 3.6: Add Condition
- Add Condition
- Configure:
triggerOutputs()?['body/dl_decision']equalsAccept- AND
triggerOutputs()?['body/dl_ado_workitem_id']is equal tonull
Step 3.7: Add Create Work Item (If Yes branch)
- Add action: Create a work item (Azure DevOps)
- Configure:
- Organization: Your org
- Project: Your project
- Work Item Type: User Story
- Title:
[MC] @{outputs('Get_a_row_by_ID')?['body/mc_title']} - Description: (include MC ID, decision rationale)
- Area Path: Platform Governance\Message Center
- Tags:
MC:@{outputs('Get_a_row_by_ID')?['body/mc_messagecenterid']}
Step 3.8: Add Update DecisionLog
- Add action: Update a row (Dataverse)
- Table: Decision Logs
- Row ID:
@{triggerOutputs()?['body/dl_decisionlogid']} - Set:
- dl_ado_workitem_id:
@{outputs('Create_a_work_item')?['body/id']} - dl_ado_workitem_url:
@{outputs('Create_a_work_item')?['body/_links/html/href']} - dl_ado_state:
@{outputs('Create_a_work_item')?['body/fields/System.State']} -
dl_ado_lastmodified:
@{utcNow()} -
Save flow
Phase 4: Create ADO → Dataverse Webhook Flow (30 minutes)
Step 3.9: Create HTTP Trigger Flow
- Create new flow:
Lab - ADO Webhook Handler - Trigger: When a HTTP request is received
- Method: POST
Step 3.10: Add Parse JSON
- Add action: Parse JSON
- Content:
@{triggerBody()} - Use sample payload from Azure DevOps webhook documentation
Step 3.11: Add Query DecisionLog
- Add action: List rows (Dataverse)
- Table: Decision Logs
- Filter:
dl_ado_workitem_id eq @{body('Parse_JSON')?['resource']?['workItemId']}
Step 3.12: Add Update Logic
- Add Condition: Check if rows returned > 0
- If yes, add Update a row:
- Row ID: First record's ID
- dl_ado_state: New state from webhook
-
dl_ado_lastmodified:
@{utcNow()} -
Add Response action: Status 200
-
Save flow
Step 3.13: Update Service Hook
- Copy the HTTP POST URL from Power Automate
- Go to Azure DevOps → Service hooks
- Edit your webhook subscription
- Update URL with the Power Automate URL
- Save
Phase 5: End-to-End Test (15 minutes)
Step 3.14: Test Full Integration
- Open model-driven app
- Select a MessageCenterPost in Decide state
- Create new DecisionLog record:
- Decision: Accept
- Decision Rationale: "Approved for implementation in Q1 sprint. Low risk, standard update."
- Save the record
- Wait 30 seconds
- Verify in Dataverse: DecisionLog has ADO Work Item ID and URL
- Verify in Azure DevOps: Work item exists with MC tag
- In Azure DevOps: Change work item state to "Active"
- Wait 30 seconds
- Verify in Dataverse: dl_ado_state updated to "Active"
Lab 3 Complete
You have bi-directional sync between Dataverse and Azure DevOps!
Lab Cleanup
After completing labs, consider:
- Keep for reference: Leave solution for future testing
- Delete test data: Remove test MessageCenterPost records
- Disable flows: Turn off scheduled flows to prevent unnecessary API calls
- Delete app registration: Remove Microsoft Entra ID app if not needed
Troubleshooting Common Lab Issues
| Issue | Cause | Resolution |
|---|---|---|
| HTTP 401 on Graph API | Invalid token or permissions | Verify app registration, grant admin consent |
| No posts returned | No recent Message Center posts | Increase $top parameter or remove date filter |
| Upsert fails | Alternate key not configured | Verify key on mc_messagecenterid |
| ADO work item not created | Flow condition failed | Check Decision value and existing ADO ID |
| Webhook not firing | URL misconfigured | Verify URL in service hook matches flow |
FSI Agent Governance Framework v1.2.51 - February 2026