Category Archives: Azure

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&amp;allow_punctuation=false&amp;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));

More licenses and features

Pivot table of all Microsoft cloud suites and their features

I’ve updated the Microsoft cloud suites feature comparison page with all other suites Microsoft including all their features. I’ve also added all Education sku’s. You can use the pivot table to sort / mix / match according to your exact needs. If you need any assistance with Microsoft 365, don’t be a stranger 🙂

Azure update management error

For those googling this error in the Update Management console in Azure:

System.Runtime.InteropServices.COMException (0x80240438): Exception from HRESULT: 0x80240438    at Microsoft.EnterpriseManagement.Mom.Modules.ChangeTracking.WUA.IUpdateSearcher2.EndSearch(ISearchJob searchJob)    at Microsoft.EnterpriseManagement.Advisor.PatchManagement.WindowsUpdateHelper.GetUpdateSnapshot(TimeSpan timeout, Boolean onlineSearch, DateTime lastTimeUpdateApplied, IAutomaticUpdates2 automaticUpdates, UpdateModuleState state)

Fix: Exempt the server from group policies (or alternative solutions) that configure Windows Update. GPO’s override Azure Update Management and block the Azure agent from searching for updates.

Azure AD sign in and audit log retention

Often we, as cloud admins, need our audit or sign in logs. Usually, we need real-time data because, for example, we’re debugging why that one user has conditional access issues. But sometimes, we need to go back further than 30 days. And that is not something Azure does by default, but can be enabled:


Our options when exporting logs are limited to a Storage account, Log Analytics or an Event Hub. All these options offer multiple extraction methods to cover your transport needs to other systems. The default retention period is then forever, which is nice as we might need audit info going back a bit as hacks are usually discovered after about 206 days.

If you don’t have specific tools or requirements, I recommend setting up a Log Analytics workspace and connecting that to Azure AD:

Whichever method you choose, a P1 or P2 license is required. You only need a single license for the entire tenant when using the export audit / singin log functionality of AzureAD. Once configured, the Logs option directly bring you to the Log Analytics workspace search results:

I’ve briefly shown how to configure AzureAD to send audit and sign in logs to Log Analytics so you can go back further than 30 days. Stay tuned for the next post that will utilize these logs to dive deeper into Guest User activity.

Programmatically triggering a group licenses refresh for AzureAD

Azure AD allows us to assign licenses to groups, a nifty feature that has made a host of automation scripts dealing with bulk license assignment obsolete.

A problem I’ve encountered is that when you assign users to a group, license assignments are not processed right away, especially if you didn’t have enough licenses when you assigned the user to the group (and added licenses to the tenant later).

Azure AD has a button to trigger an update manually:

But of course, this can also be automated with PowerShell!

function Invoke-AzHAPIReprocessGroupLicenses{
    <#
        .SYNOPSIS
        reprocesses group license assignment

        .NOTES
        Author: Jos Lieben

        .PARAMETER AzureRMToken
        Use Get-azureRMToken to get a token for this parameter

        .PARAMETER groupGUID
        GUID of the group to reprocess licenses of
    
        Requires:
        - Global Administrator Credentials (non-CSP!)
        - AzureRM Module
        - supply result of get-azureRMToken function
    #>
    param(
        [Parameter(Mandatory = $true)]$AzureRMToken,
        [Parameter(Mandatory = $true)]$groupGUID
    )
    $header = @{
        'Authorization' = 'Bearer ' + $AzureRMToken
        'X-Requested-With'= 'XMLHttpRequest'
        'x-ms-client-request-id'= [guid]::NewGuid()
        'x-ms-correlation-id' = [guid]::NewGuid()
    }   

    $url = "https://main.iam.ad.ext.azure.com/api/AccountSkus/Group/$groupGUID/Reprocess"
    Invoke-RestMethod –Uri $url –Headers $header –Method POST -Body $Null -UseBasicParsing -ErrorAction Stop -ContentType "application/json"
}

Source on GIT: https://gitlab.com/Lieben/assortedFunctions/blob/master/invoke-AzHAPIReprocessGroupLicenses.ps1https://gitlab.com/Lieben/assortedFunctions/blob/master/invoke-AzHAPIReprocessGroupLicenses.ps1

Disclaimer: the ‘hidden azure api’ is not officially supported.

Requires output from the Get-AzureRMToken function