Category Archives: PowerApps

M365AutoRevocate

Offboarding has always been an interesting case, and one I rarely see customers do 100% right. The will is there, often focused on keeping access to data or reclaiming licenses.

But execution is a whole different world. Automation for onboarding often exists, offboarding rarely, and if it exists, it is usually still triggered manually, and more often than not only for managed accounts (admins, guests, service accounts etc are totally forgotten), and then what is actually done upon offboarding?

Microsoft Graph has an option to subscribe to changes, specifically for user objects. This means we can listen to deactivation, idle (x days inactive) or deletion events and act accordingly. E.g. unshare onedrive, notify a manager of flows and powerapps or teams the user still owns, reclaim licenses, disable upon inactve, etc etc.

So I co-wrote a little PowerShell solution, of course perfectly based on best practises around MI auth, least privileges and assume breach.

It lets you configure WHAT (loads of things, expandable) to do WHEN (inactive, delete, deactivate), and runs at almost no cost in your own azure subscription, as usual, open source and a oneliner to install: https://github.com/jflieben/M365AutoRevocate

Inviting an external user to a PowerApp programmatically

Another week, another use case for Managed Identities in Automation Accounts!

The scenario today concerns a PowerApp and connected resources that should be shared with external identities, automatically of course. For each user this requires a guest account in the host / resource tenant, and a license. The license can be applied in the home tenant of the guest, or in your tenant.

Key points:

  1. Runbook that invites a user and adds the resulting guest account to a security group
  2. Security group gives access to the PowerApp and underlying (SpO) resources, and uses Group Based Licensing to license the guest for PowerApps and Sharepoint Online
  3. Logic App that is triggered by the PowerApp (trigger on create item in a sharepoint list), and starts the runbook
  4. When the invited user (guest) redeems the invitation, they are directed to a Sharepoint page first so Sharepoint syncs their profile. Otherwise, the PowerApp will not have access to any lists in Sharepoint Online as Guests are not synced to SpO until they access SpO directly.

I may demo the PowerApp, Logic App and Sharepoint lists at some point, but the main thing I wanted to share today is the Azure Runbook that creates the Guest invitation and adds the Guest to a security group using the Managed Identity of the Automation account, instead of service accounts or other pre-2021 solutions:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/invite-guestUser.ps1

Using runbooks interactively in PowerApps (Build your own app in < 10 minutes!)

Mostly, the users of my PowerShell scripts are themselves PowerShell users. Sometimes though, the audience is less tech-savvy. In this blog post (with my first EVER video tutorial!) I’ll show you how to give your users a super user friendly interface to your scripts: Microsoft PowerApps.

You’ll need a PowerApps trial or license to follow this tutorial.

When you use the Azure AD group that was created to publish your app to when it is ready for distribution, your users will automatically be granted the correct permissions in Azure to start a runbook, as PowerApps does not use its own identity when interacting with connectors, it impersonates the user identity.

The source code for the runbook is:

Param(
    [String]$searchParameter
)

$uri = "https://techcommunity.microsoft.com/t5/forums/searchpage/tab/message?advanced=false&allow_punctuation=false&q=$searchParameter"

Start-Sleep -s 2

Write-Output "Runbook started, searching for $searchParameter..."

$res = Invoke-WebRequest -Uri $uri -UseBasicParsing -Method GET -ErrorAction Stop

Start-Sleep -s 2

Write-Output "found some results, analyzing...."

$firstHit = $res.Links | where-object {$_.outerHTML -like "*lia-link-navigation*" -and $_.href -like "/t5/*"} | select href -First 1 -ExpandProperty href
$firstHit = "https://techcommunity.microsoft.com/$firstHit"

Start-Sleep -s 2

Write-Output "Retrieving first 100 characters of first result..."

$res = Invoke-WebRequest -Uri $firstHit -UseBasicParsing -Method GET -ErrorAction Stop
$excerpt = $res.Content.Substring(($res.Content.IndexOf("class=`"lia-message-body-content`"")+64),100) -Replace('<[^>]+>','')

Start-Sleep -s 2

Write-Output "Result:"
Write-Output $excerpt
write-Output ""
write-Output ""
write-Output ""
write-Output "source: $firstHit"

The app screen’s OnStart property’s function is:

Set(runbookOutput,Blank());Set(runbookJobId,Blank());Set(runbookActive,false);Set(runbookResult,Blank())

The search button’s function is:

Set(runbookResult,Blank());Set(runbookOutput,Blank());Set(runbookJobId,Blank());Set(runbookActive,true);Set(runbookJobId,'new-searchQuery'.Run(TextInput2.Text).jobid)

The status label’s function is:

If(IsBlank(runbookResult) && runbookActive = false," ",If(runbookActive,"Please wait for job to complete…",Concatenate("Job result: ",runbookResult)))

The timer OnTimerStart function is:

If(runbookActive && Len(runbookJobId) > 5,Set(runbookOutput,'get-searchQueryOutput'.Run(runbookJobId).joboutput))

The timer OnTimerEnd function is:

If(runbookActive && Len(runbookJobId) > 5,Set(runbookResult,'get-searchQueryStatus'.Run(runbookJobId).jobstatus));If(runbookResult = "Completed" Or runbookResult = "Suspended" Or runbookResult = "Stopped",Set(runbookActive,false));