Dataverse for Teams (later DV4T) and Trial environments differ from regular Dataverse environments in the sense that they have an “expiry date”. A Dataverse for Teams environment will be disabled after 90 days of inactivity. If an admin takes no action, and the environment is left disabled for 30 days, it will be deleted. After deletion, the environment can be recovered in 7 days. Trials are even more straightforward: They are deleted 30 days after they’re created. The more environments there are and the more infrequent their use, the harder it becomes to stay on top of what is going to the trashcan and when. The question of an “expiry date” has come up on a few occasions so I thought I’d write a short post that lists applicable environments with their expiry – i.e. disable and delete – dates. You can find a link to a flow I’ve built together with managed and unmanaged solution files at the bottom of this post.
Listing environment expiry dates with Power Automate
Power Platform environments can be listed and accessed with the Power Platform for Admins connector’s List Environments as Admin action. A property called scheduledLifecycleOperations contains values for when a Dataverse for Teams environment is going to be disabled and deleted. For Trials, the property contains the deletion date of the environment, as Trials aren’t disabled but deleted as they expire.
The flow that I’ve created for this blogpost adds all DV4T and Trial environments to an HTML table, which can be sent in an email. Feel free to edit the flow to fit it to your own needs. Instead of emailing information about the environments, you could list them in Dataverse, in a SharePoint list, or in Excel. You could also create a custom app as an extension to your Center of Excellence Starter Kit, and then pin that to the CoE’s CoE Admin Command Center app.
Initial actions in the flow
Let’s go through the flow that lists all expiring environments. For this flow, I’ve chosen to use a recurrence trigger as by nature the flow is a background flow that can be run at nigh. An expand properties query of properties/scheduledLifecycleOperations is used to access the scheduledLifecycleOperations property, which has the expiry information we’re looking for in an array. The next step is to parse the returned JSON. I spent some time getting the schema working. You don’t have to spend time on the same challenge so grab the schema at the end of this post. After the JSON is parsed, the body of the parsed JSON is filtered with a filter array. We’re looking for items where scheduluedLifecycleOperations is not equal to null. The expression used is: @not(equals(item()?['properties']?['scheduledLifecycleOperations'], null))
.
Apply to each loop
Next, let’s loop through the filtered array. The apply to each has optional compose action for environment displayName, name, environemntSku, and scheduledLifecycleOperations to make the flow more easy to read during testing. The condition looks whether the iteration is related to a DV4T or a Trial environment by checking the length of the scheduledLifecycleOperations property. If its length is greater than 1, the iteration is related to a DV4T environment. Otherwise the iteration is related to a Trial. The environmentSku propery could be evaluated as well. I already punched in the expression for length before realizing that ¯\_(ツ)_/¯
DV4T vs. Trial in the apply to each
Based on the condition, the flow grabs values for scheduledLifecycleOperations for DV4T or Trial environments. The property is an array of objects and differs depending on whether a DV4T or Trial environment is in question. Let’s look at the JSON returned for both types of environments.
DV4T
"scheduledLifecycleOperations": [
{
"type": "Disable",
"scheduledDateTime": "2023-06-01T11:26:51Z"
},
{
"type": "Delete",
"scheduledDateTime": "2023-07-01T11:26:51Z"
}
]
The array contains keys with a value for both Disable and Delete.
Trial
"scheduledLifecycleOperations": [
{
"type": "Delete",
"scheduledDateTime": "2023-04-05T09:58:19.2531626Z"
}
]
The array contains a key with a value for Delete.
Since an array is returned, the array values need to be accessed by index. This is seen in image 4, which illustrates the steps in the loop after the condition. Compose actions are used to store the values from scheduledLifecycleOperations, and a final compose at the end of the loop then stores them in what becomes an array. I’ve used a compose at the end of the loop instead of a variable as using the Pieter’s method makes the flow faster.
Final actions
After the loop, a compose for the Pieter’s method is used to refer to the compose inside the loop. An HTML table is then created and the table is formatted with an expression that I regularly use to beautity tables. For this use case it would need some additional work but I’ll nevertheless add it here for the sake of example:
replace(replace(replace(body('Create_HTML_table'),
'<th>','<th style="text-align:center;color:white;background-color:#077D3F;padding:2px">'),
'<td>','<td style="text-align:center;color:black;padding:3px">'),
'<table>','<table style="border-collapse:collapse;width:100%">')
JSON schema for the flow’s Parse JSON action
Below is the JSON schema for the parse JSON action mentioned earlier in the post.
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"properties": {
"tenantId": {
"type": "string"
},
"azureRegion": {
"type": "string"
},
"displayName": {
"type": "string"
},
"description": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"createdBy": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"type": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"userPrincipalName": {
"type": "string"
}
}
},
"lastModifiedTime": {
"type": "string"
},
"provisioningState": {
"type": "string"
},
"provisioningDetails": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"httpStatus": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"name",
"httpStatus",
"code"
]
}
}
}
},
"creationType": {
"type": "string"
},
"environmentSku": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"clientUris": {
"type": "object",
"properties": {
"admin": {
"type": "string"
},
"maker": {
"type": "string"
}
}
},
"runtimeEndpoints": {
"type": "object",
"properties": {
"microsoft.BusinessAppPlatform": {
"type": "string"
},
"microsoft.CommonDataModel": {
"type": "string"
},
"microsoft.PowerApps": {
"type": "string"
},
"microsoft.PowerAppsAdvisor": {
"type": "string"
},
"microsoft.PowerVirtualAgents": {
"type": "string"
},
"microsoft.ApiManagement": {
"type": "string"
},
"microsoft.Flow": {
"type": "string"
}
}
},
"databaseType": {
"type": "string"
},
"linkedEnvironmentMetadata": {
"type": "object",
"properties": {
"resourceId": {
"type": "string"
},
"friendlyName": {
"type": "string"
},
"uniqueName": {
"type": "string"
},
"domainName": {
"type": "string"
},
"version": {
"type": "string"
},
"instanceUrl": {
"type": "string"
},
"instanceApiUrl": {
"type": "string"
},
"baseLanguage": {
"type": "integer"
},
"instanceState": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"backgroundOperationsState": {
"type": "string"
},
"scaleGroup": {
"type": "string"
},
"platformSku": {
"type": "string"
}
}
},
"trialScenarioType": {
"type": "string"
},
"notificationMetadata": {
"type": "object",
"properties": {
"state": {
"type": "string"
},
"branding": {
"type": "string"
}
}
},
"retentionPeriod": {
"type": "string"
},
"states": {
"type": "object",
"properties": {
"management": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
},
"runtime": {
"type": "object",
"properties": {
"runtimeReasonCode": {
"type": "string"
},
"requestedBy": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"id": {
"type": "string"
}
}
}
}
},
"updateCadence": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
},
"retentionDetails": {
"type": "object",
"properties": {
"retentionPeriod": {
"type": "string"
},
"backupsAvailableFromDateTime": {
"type": "string"
}
}
},
"protectionStatus": {
"type": "object",
"properties": {
"keyManagedBy": {
"type": "string"
}
}
},
"cluster": {
"type": "object",
"properties": {
"category": {
"type": "string"
},
"number": {
"type": "string"
},
"uriSuffix": {
"type": "string"
},
"geoShortName": {
"type": "string"
},
"environment": {
"type": "string"
}
}
},
"connectedGroups": {
"type": "array"
},
"lastActivity": {
"type": "object",
"properties": {
"lastActivity": {
"type": "object",
"properties": {
"lastActivityTime": {
"type": "string"
},
"lastUpdatedTime": {
"type": "string"
}
}
},
"environmentPartnerLastActivity": {
"type": "object",
"properties": {
"eloLastActivity": {
"type": "object",
"properties": {
"lastActivityTime": {
"type": "string"
},
"lastUpdatedTime": {
"type": "string"
}
}
}
}
}
}
},
"lifecycleOperationsEnforcement": {
"type": "object",
"properties": {
"allowedOperations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
},
"required": [
"type"
]
}
},
"disallowedOperations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
},
"reason": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
}
},
"required": [
"type",
"reason"
]
}
}
}
},
"governanceConfiguration": {
"type": "object",
"properties": {
"protectionLevel": {
"type": "string"
}
}
},
"scheduledLifecycleOperations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"scheduledDateTime": {
"type": "string"
}
},
"required": [
"type",
"scheduledDateTime"
]
}
}
}
}
},
"required": [
"id",
"type",
"location",
"name",
"properties"
]
}
}
Solution files
The managed and unmanaged solution files containing the flow can be downloaded from GitHub. They’re released as is.
- Written by: Antti Pajunen
- Posted on: 2023-03-08
- Tags: environment, Flow, governance, List Environments as Admin, Power Automate, Power Platform Governance, scheduledLifecycleOperations