Automating Changing Solution Owner in the Power Platform
Tags: powerplatfrom, powerapps, powerautomate, lowcode
Published: 2023-05-29 06:40:11

I wrote here [Change-Solution-Owner](https://dev.to/wyattdave/how-to-change-solution-owner-in-the-power-platform-1jnb) how it is possible to manually change the owner of a solution. But the Power Platform is all about efficiency and automation, so let's see how to automate it.
This also links into [Create-Your-Own-Pipeline](https://dev.to/wyattdave/deploying-between-environments-with-power-automate-instead-of-pipelines-3fm0), as we need to be able to automatically change the solution we imported back to its original owner.

---

Before we start we need to understand how the Dataverse tables we need and their relationships. We need to use the following tables

- Solutions
- Solution Components
- Users
- Process
- Canvas Apps
- Connections
- Environment Variable Definition

But this can be extended to other parts of solutions, like the Connectors table for Custom Connectors.
_There is also the Environment Variable Value table, which is part of the solution but I have never needed to change the owner to the environment variable value._

The User table is because the owner(s) is a relationship to this table, so we need to lookup the userid guid based on the new owners email address.

The Solution table is the obvious start point, we need this to find the solutionid (the table guid), as we just have the solution name. If you look in the flow you see solutionid field, but that is a red herring. This is a system solution (as a flow can be in multiple solutions). So instead there is the Solution Component table. Every time you add something to a solution you add a row to that table. So we go Solution to Solution Components (1 to many) to the Components(many to 1).

![database relationships](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j79o5w8x0zgklncomc75.png)

---

## Get User
As I said we need the user table guid when we set the new owner. We List rows with the email address as the filter, there are a few email address but I've had success with internalemailaddress field.

**Filter**
```
internalemailaddress eq '@{triggerBody()['text_1']}'
```

![list users](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ilequoowgm7cj23amcq.png)

## Solution to Solution Components

So the flow trigger includes the solution name (not display name). We use the Dataverse List rows but filter to the solution name, using the uniquename field:

**Filter**
```
uniquename eq '@{triggerBody()['text']}'
```

Now we need to return all the components in the solution from the Solution Components table. To avoid an annoying ForAll (because List Rows returns an array) I use the array position 0. 
The filter query will be

**Filter**
```
_solutionid_value eq '@{outputs('List_rows_to_find_solution')?['body/value'][0]?['solutionid']}'
```

The flow will look like this:

![solution list rows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gs6p9c6dji0x6l876zr1.png)

## Looping the Components

The Solution Components includes everything in the solution, so we need a way to identify which component it is and which table it is in. This can be done by the componenttype field. I use a switch to select the table based on the component type. To add a little complexity this is an integer, but we can see the display name with this field:

```
 items('Apply_to_each')?['componenttype@OData.Community.Display.V1.FormattedValue']
```

| Component Type | Dataverse Table            |
|----------------|----------------------------|
| 29             | Process (flows)            |
| 300            | Canvs Apps                 |
| 380            | Environment Variable Def   |
| 381            | Environment Variable Value |
| 10039          | Connection References      |
| 372            | Connector                  |

## Flows (Process Table)

The Process table is used to update our flows with the update item action, the row id is the worflowid. The Component field that holds this value is the objected, so we get:

**Row ID**
```
items('Apply_to_each')?['objected']}
```

We then have to update the owner, and as its a flow turn on the flow.

**Owner** (List_rows_Find_User is output from user table)
```
outputs('List_rows_Find_User')?['body/value'][0]?['@odata.id']
```

**Status** Activated (turn on flow)


![update item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7vko7xilge5eayaubhn.png)

## Canvas Apps 
The Canvas Apps table its a little different, as the objected from the Solution Component table isn't the row id. So we need to do a List rows with a filter trick.

The objected is the uiquecanvaseappid, but the row id is the canvasappid.

**Filter**
```
uniquecanvasappid eq '@{items('Apply_to_each')?['objected']}'
```
**Row ID**
```
outputs('List_rows_canvasapps')?['body/value'][0]?['canvasappid']
```

And we update the owner the same way as the flows, but we don't need to turn on anything.

![list apps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0swi7bebtphpwqjfb1d1.png)

_**Some interesting has started happening, now even though the app owner is changing, it is not shared with the new owner. To fix it you can use the 'Set App Owner' action. The new owner id is in the user dataverse table, it's the azureactivedirectoryobjectid field.**_

![set app ower](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhpjn7gcf96712kb41g0.png)

**New PowerApp Owner**
```
outputs('List_rows_Find_User')?['body/value'][0]?['azureactivedirectoryobjectid']
```

## Environment Variable (Definitions)

The Environment Variable Definitions table is pretty much identical to the flows process, without the need to turn on (so just change owner). The **Row ID** is `environmentvariabledefinitionid` 
which is the objected again.

![update item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31nrqhkpopwpr3cwy6kt.png)

## Connection References
This time identical to Environment Variable Definitions, we just update the owner and the **Row ID** is `connectionreferenceid`  which is the objectid.
_Incase you missed it, you can see the row id / guide is normally just the table name and id_

![update item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3cd1vz4b6oey8x37m5x.png)

## Exceptions
This is an optional extra but one I recommend. For each Update Item I add an exception catcher, the reason is it's quite possible most will succeed but the odd one will fail. In my experience the only fails are for the flows (but seems good practice to do catch it for them all). A couple of examples I had were:

- Connections used instead of connection references
- Connection Reference empty
- Custom Connector missing from environment

To grab the exception we use the standard actions expression, and append it to a string variable.

```
actions('Update_a_process')
```

![exception handling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ygb1uq3nywytd8yt6up.png)

---

And that's it, your flow should look something like this

![full flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygfkdv502b0k9y186zgu.png)