Skip to content

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

  1. Go to Azure Portal
  2. Navigate to Microsoft Entra IDApp registrations
  3. 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)
  1. 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

  1. Go to Certificates & secretsClient secrets
  2. Click New client secret
  3. Description: Lab Secret
  4. Expires: 90 days (sufficient for lab)
  5. Click Add
  6. Immediately copy the secret value (shown only once)

Step 1.5: Configure API Permissions

  1. Go to API permissions
  2. Click Add a permission
  3. Select Microsoft Graph
  4. Select Application permissions
  5. Search for and add: ServiceMessage.Read.All
  6. Click Add permissions
  7. Click Grant admin consent for [tenant]
  8. 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

  1. Go to Power Apps Maker Portal
  2. Select your lab environment
  3. Go to SolutionsNew solution
  4. Configure:
  5. Display name: MessageCenterGovernance_Lab
  6. Publisher: Select default or create new
  7. Version: 1.0.0.0
  8. Click Create

Step 2.2: Create MessageCenterPost Table

  1. Open your solution
  2. Click NewTableTable
  3. Configure:
  4. Display name: Message Center Post
  5. Plural name: Message Center Posts
  6. Enable auditing: Yes
  7. 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

  1. Open MessageCenterPost table → Keys
  2. Click New key
  3. Display name: Message Center ID Key
  4. Columns: Select mc_messagecenterid
  5. Click Save

Step 2.5: Enable Table Auditing

  1. Open table → Properties (gear icon)
  2. Expand Advanced options
  3. Enable Audit changes to its data
  4. 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

  1. Go to Power Automate
  2. Click CreateScheduled cloud flow
  3. Configure:
  4. Flow name: Lab - MC Ingestion
  5. Run every: 15 minutes
  6. Click Create

Step 3.2: Add HTTP Action - Get Token

  1. Click New step → Search for HTTP
  2. 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

  1. Click New stepParse JSON
  2. Content: @body('HTTP')
  3. Schema:
    {
      "type": "object",
      "properties": {
        "access_token": { "type": "string" },
        "token_type": { "type": "string" },
        "expires_in": { "type": "integer" }
      }
    }
    

Step 3.4: Add HTTP Action - Get Messages

  1. Click New stepHTTP
  2. 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

  1. Click New stepApply to each
  2. Select output: @body('HTTP_2')?['value']

Step 3.6: Add Dataverse Upsert (inside Apply to Each)

  1. Inside the loop, click Add an action
  2. Search for DataverseUpdate or insert a record
  3. Configure:
  4. Table name: Message Center Posts
  5. Alternate Key: mc_messagecenterid = @items('Apply_to_each')?['id']
  6. mc_title: @items('Apply_to_each')?['title']
  7. mc_category: (map to choice value)
  8. mc_severity: (map to choice value)
  9. mc_services: @json(items('Apply_to_each')?['services'])
  10. mc_body: @items('Apply_to_each')?['body']?['content']
  11. mc_startdatetime: @items('Apply_to_each')?['startDateTime']
  12. 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

  1. Click Save
  2. Click TestManuallyTest
  3. 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

  1. In your solution, click NewTable
  2. Configure:
  3. Display name: Assessment Log
  4. Primary column: Assessment ID (auto-generated)
  5. Enable auditing: Yes
  6. Ownership: User or team

  7. 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)
  1. Configure relationship: Restrict delete on MessageCenterPost lookup

Step 2.2: Create DecisionLog Table

  1. Create new table:
  2. Display name: Decision Log
  3. Ownership: Organization-owned
  4. Enable auditing: Yes

  5. 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
  1. Configure relationship: Restrict delete on MessageCenterPost lookup

Phase 2: Create Model-Driven App (45 minutes)

Step 2.3: Create App

  1. In your solution, click NewAppModel-driven app
  2. Name: Message Center Governance Lab
  3. Click Create

Step 2.4: Configure Site Map

  1. In app designer, click Navigation
  2. Add area: Governance
  3. Add group: Message Center
  4. Add pages:
  5. Message Center Posts (table page)
  6. Assessment Log (table page)
  7. Decision Log (table page)

Step 2.5: Configure MessageCenterPost Form

  1. Go to PagesMessage Center PostForms
  2. Edit main form
  3. 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)

  1. Save and publish form

Step 2.6: Create Views

Create these views for MessageCenterPost:

  1. New Posts Awaiting Triage
  2. Filter: State equals New
  3. Columns: Title, Category, Severity, Start Date

  4. My Assigned Posts

  5. Filter: Owner equals current user
  6. Columns: Title, State, Category, Severity

  7. High Severity Posts

  8. Filter: Severity equals high OR critical
  9. Columns: Title, State, Owner, Action Required By

  10. Recently Closed

  11. Filter: State equals Closed
  12. Sort: Modified On descending
  13. Columns: Title, Decision, Decided By, Modified On

Step 2.7: Save and Publish App

  1. Click Save
  2. Click Publish

Phase 3: Configure Security Roles (30 minutes)

Step 2.8: Create MC Admin Role

  1. In your solution → Add existingSecurity role
  2. Click New security role
  3. Name: MC Admin Lab
  4. 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

  1. Create new role: MC Owner Lab
  2. 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

  1. Go to Power Platform Admin Center
  2. Select environment → Users
  3. Select test user → Manage security roles
  4. Assign appropriate role

Phase 4: End-to-End Test (15 minutes)

Step 2.11: Test Complete Workflow

  1. Open model-driven app
  2. Navigate to New Posts Awaiting Triage
  3. Open a post
  4. Triage: Assign Owner to yourself, change State to Triage
  5. Assess: Change State to Assess, set Impact Assessment
  6. Create Assessment Log record with notes
  7. Decide: Change State to Decide, set Decision
  8. Create Decision Log record with rationale (50+ characters)
  9. Close: Change State to Closed
  10. 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

  1. Open DecisionLog table
  2. 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
  1. Save and publish

Phase 2: Configure Azure DevOps (20 minutes)

Step 3.2: Create Project Area

  1. Go to Azure DevOps → Your project
  2. Project SettingsBoardsProject configuration
  3. Create area path: Platform Governance\Message Center

Step 3.3: Configure Service Hook

  1. Project SettingsService hooks
  2. Click Create subscription
  3. Select Web Hooks
  4. Trigger: Work item updated
  5. Filters:
  6. Area path: Under Platform Governance
  7. Leave URL blank (will update after creating flow)
  8. Click Finish (we'll update URL later)

Phase 3: Create Dataverse → ADO Flow (30 minutes)

Step 3.4: Create Flow

  1. Go to Power Automate → CreateAutomated cloud flow
  2. Name: Lab - Create ADO Work Item
  3. Trigger: When a row is added (Microsoft Dataverse)
  4. Table name: Decision Logs
  5. Scope: Organization

Step 3.5: Add Get Parent Post

  1. Add action: Get a row by ID (Dataverse)
  2. Table: Message Center Posts
  3. Row ID: @{triggerOutputs()?['body/_al_messagecenterpost_value']}

Step 3.6: Add Condition

  1. Add Condition
  2. Configure:
  3. triggerOutputs()?['body/dl_decision'] equals Accept
  4. AND triggerOutputs()?['body/dl_ado_workitem_id'] is equal to null

Step 3.7: Add Create Work Item (If Yes branch)

  1. Add action: Create a work item (Azure DevOps)
  2. Configure:
  3. Organization: Your org
  4. Project: Your project
  5. Work Item Type: User Story
  6. Title: [MC] @{outputs('Get_a_row_by_ID')?['body/mc_title']}
  7. Description: (include MC ID, decision rationale)
  8. Area Path: Platform Governance\Message Center
  9. Tags: MC:@{outputs('Get_a_row_by_ID')?['body/mc_messagecenterid']}

Step 3.8: Add Update DecisionLog

  1. Add action: Update a row (Dataverse)
  2. Table: Decision Logs
  3. Row ID: @{triggerOutputs()?['body/dl_decisionlogid']}
  4. Set:
  5. dl_ado_workitem_id: @{outputs('Create_a_work_item')?['body/id']}
  6. dl_ado_workitem_url: @{outputs('Create_a_work_item')?['body/_links/html/href']}
  7. dl_ado_state: @{outputs('Create_a_work_item')?['body/fields/System.State']}
  8. dl_ado_lastmodified: @{utcNow()}

  9. Save flow


Phase 4: Create ADO → Dataverse Webhook Flow (30 minutes)

Step 3.9: Create HTTP Trigger Flow

  1. Create new flow: Lab - ADO Webhook Handler
  2. Trigger: When a HTTP request is received
  3. Method: POST

Step 3.10: Add Parse JSON

  1. Add action: Parse JSON
  2. Content: @{triggerBody()}
  3. Use sample payload from Azure DevOps webhook documentation

Step 3.11: Add Query DecisionLog

  1. Add action: List rows (Dataverse)
  2. Table: Decision Logs
  3. Filter: dl_ado_workitem_id eq @{body('Parse_JSON')?['resource']?['workItemId']}

Step 3.12: Add Update Logic

  1. Add Condition: Check if rows returned > 0
  2. If yes, add Update a row:
  3. Row ID: First record's ID
  4. dl_ado_state: New state from webhook
  5. dl_ado_lastmodified: @{utcNow()}

  6. Add Response action: Status 200

  7. Save flow

Step 3.13: Update Service Hook

  1. Copy the HTTP POST URL from Power Automate
  2. Go to Azure DevOps → Service hooks
  3. Edit your webhook subscription
  4. Update URL with the Power Automate URL
  5. Save

Phase 5: End-to-End Test (15 minutes)

Step 3.14: Test Full Integration

  1. Open model-driven app
  2. Select a MessageCenterPost in Decide state
  3. Create new DecisionLog record:
  4. Decision: Accept
  5. Decision Rationale: "Approved for implementation in Q1 sprint. Low risk, standard update."
  6. Save the record
  7. Wait 30 seconds
  8. Verify in Dataverse: DecisionLog has ADO Work Item ID and URL
  9. Verify in Azure DevOps: Work item exists with MC tag
  10. In Azure DevOps: Change work item state to "Active"
  11. Wait 30 seconds
  12. 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:

  1. Keep for reference: Leave solution for future testing
  2. Delete test data: Remove test MessageCenterPost records
  3. Disable flows: Turn off scheduled flows to prevent unnecessary API calls
  4. 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