This blog post is part of my “Automating Power BI deployments” series, and you can find a list of the other posts here.





Assigning permissions (or updating them) is by far the most “complicated” task we’re going to attempt, mostly due to the fact that we’re going to do it all in one PowerShell script. The “complication” is mostly self-inflicted because of the following:

  • The API url to add users, groups and apps (service principals) are the same, but the body of the API call will be different depending on which it is. We’ll have to build the appropriate flow in the script to account for that.
  • Users, groups or apps may already exist, and we’ll have to change the method of the API call accordingly…using the Put method to update and Post method to add.

Parameters

We’ll define the parameters for our script in the following way:

param 
(    
    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()] 
    [String]
    $WorkspaceName, 

    [Parameter(Mandatory=$false)]
    [ValidateNotNullOrEmpty()]
    [String]
    $Users, 

    [Parameter(Mandatory=$false)]
    [ValidateNotNullOrEmpty()]
    [String]
    $Groups
)

For the $Users and $Groups parameters we’ll pass account/role value pairs, with the following format: <account1>|<role1>;<account2>|<role2>

It takes a little more work to parse these account/role pairs in the script, but allows us to add or update multiple items at the same time. Another thing to point out is that we’ll need to provide the email address for individual users, but the guid for groups. I know…I don’t like this either but it has to do with the fact that you could have multiple (different) AD groups with the same display name, and using the guid will avoid a potential conflict.

We’ve also defined the $Users and $Groups parameters as optional because we won’t always want to add or update both at the same time, and doing that gives us the flexibility to exclude it if we need/want to.

Parse the user/role or group/role pairs

The built-in Split method of string objects in PowerShell handles the task of splitting the value pairs beautifully, and looks like this:

#Check if the optional parameter was provided 
if ($Users -ne "") {
    
    #Split the string and iterate through the items (users) 
    foreach ($UserRolePair in $Users.Split(";")) {

        $UserName = $UserRolePair.Split("|")[0]
        $UserRole = $UserRolePair.Split("|")[1] 
    }
}

We’re using the Split method twice here…first to extract the user/role pairs and then to get the username and role separately by splitting on the | character and grabbing the necessary element from the array, assigning it to a variable. Arrays are extremely useful in PowerShell, and definitely a skill you will want to acquire for your proverbial PowerShell toolbelt.

Needless to say, the code to split group/role pairs will look similar to what we have above.

The API body

The body of the API request will look very similar, whether we are adding/updating users or groups, with the only difference being the principalType property value.

#API request body for user permissions
$ApiRequestBody = @"
{
    "groupUserAccessRight": "$UserRole",
    "identifier": "$UserName", 
    "principalType": "User" 
}
"@ 


#API request body for group permissions
$ApiRequestBody = @"
{
    "groupUserAccessRight": "$GroupRole",
    "identifier": "$GroupGuid", 
    "principalType": "Group" 
}
"@ 

According to the official documentation you can also use the emailAddress property in the body when adding or updating users, but I’ve seen some interesting behavior when using that. The identifier attribute will work in all cases as the email address is also the identifier for user accounts, and safer to use in my opinion.

Preparation

In preparation for the API call, we need to retrieve the workspace as we’ll need its Id in the API url. Here’s how you retrieve it and then build the url:

#Retrieve the workspace
$WorkspaceObject = (Get-PowerBIWorkspace -Scope Organization -Name $WorkspaceName) 

#API url for the workspace users & groups
$ApiUrl = "groups/" + $WorkspaceObject.Id + "/users"

The word “groups” as first part of the API url is somewhat confusing. Whenever you see the word “group” in the API documentation, they are really talking about a workspace and not a security group.

Check for existence and initiate the API call

The last thing we need to do is to return a list of users (or groups, or apps), check for existence and then initiate the API call with the appropriate method dependent on whether we are adding or updating. Returning the list of users is done by calling the same API url but using the Get method, as you can see from the code snippet below (no API body required). The REST API returns data in JSON format, so we have to convert it from that to an object we can use…which is done with the ConvertFrom-Json cmdlet.

#Get a list of all workspace users, to see if we need to add or update it
$WorkspaceUsers = (Invoke-PowerBIRestMethod -Url $ApiUrl -Method Get) | ConvertFrom-Json

#See if the user already exists in the list of workspace users, and if so only update its role            
if ($UserName -cin $WorkspaceUsers.Value.identifier) {

    Write-Output "User ""$UserName"" already exists in workspace ""$WorkspaceName"". Updating its role only..." 

    #Update the user's role
    Invoke-PowerBIRestMethod -Url $ApiUrl -Method Put -Body ("$ApiRequestBody")

    Write-Output "User ""$UserName"" granted the ""$UserRole"" role..." `n 
}

#User doesn't exist, so we add it
else { 

    #Add the user and assign the role
    Invoke-PowerBIRestMethod -Url $ApiUrl -Method Post -Body ("$ApiRequestBody")

    Write-Output "User ""$UserName"" added to workspace ""$WorkspaceName"" as ""$UserRole""..." `n 
}

I’m not showing the code to add or update Active Directory groups here, but it will be very similar to what we’re doing for users.

What about service principals?

Ah, service principals…why can’t everything just be straight-forward?

Assigning permissions to service principals (apps) almost work the same as groups. I say almost because at face-value it seems to be the same (except for the use of App as the value of the principalType property), but it isn’t and here’s what you need to know…

When you create an App Registration in the Azure portal, there’s really two parts to it. The first part is the application itself, which is really the “object” that receives the permission(s) to other applications and functions. The service principal is the second part and really only the access policy that defines how the application will connect and what kinds of permissions it will have.

Why is this distinction important? Well, if you’ve ever used service principals before, you will be familiar with the application (or client) id that is used for authentication. And it would seem to be a logical assumption that you should use that application id when assigning permissions to an app in Power BI…but it’s not. You’ll have to use the object id of the application itself, and in the overview of the service principal in the Azure portal (first image below) you can click on the highlighted item to go to the application itself and use its Object Id (second image below) when calling the Power BI API.





How do we remove permissions?

The official API documentation to update permissions for users/groups/apps seem to suggest that you can use None as the role (or groupUserAccessRight property if you’re looking at the API body) to remove permissions, but that doesn’t work and you have to use a different API call for that. Although I’m not going to show the details here, the script in my GitHub repo accounts for this “feature” and using None as a role assignment in the execution of the PowerShell script will remove the user/group/app from your workspace.

Wrap it up!

That’s a wrap for this series, folks. I hope you’ve enjoyed it and found some useful bits you can use in your own environment.





Want to download the PowerShell scripts to perform all the things we covered in this series? Get it from my GitHub repo.

Leave a Reply

%d bloggers like this: