Devices that lack a bitlocker recovery key in AzureAD

With Intune’s new Bitlocker Encryption Report administrators have an effective way of seeing which of their devices have been encrypted.

But if we want to know if we can actually recover the bitlocker key of a device, we need to know if it was ever uploaded to AzureAD.

Network or local device issues can sometimes prevent the recovery key from reaching AzureAD, resulting in lost data if the device’s disk needs to be recovered for any reason. To hunt down devices that have not escrowed their recovery key to AzureAD, you can use my report function (in PowerShell as always):

GitLab source download link

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{
        reprocesses group license assignment

        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
        - Global Administrator Credentials (non-CSP!)
        - AzureRM Module
        - supply result of get-azureRMToken function
        [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 = "$groupGUID/Reprocess"
    Invoke-RestMethod –Uri $url –Headers $header –Method POST -Body $Null -UseBasicParsing -ErrorAction Stop -ContentType "application/json"

Source on GIT:

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

Requires output from the Get-AzureRMToken function

Get an Office365 / Azure AD tenant ID from a user’s login name or domain

I often need a tenant ID for a given customer, the usual method to get it is to log in to the Azure portal and find it there. But what if you want to get the tenant ID programmatically? Without actually logging in? And you only know the log in name of a user? Or just one of the customer’s domain names?

Then this’ll help you out!

function get-tenantIdFromLogin(){
      Retrieves an Office 365 / Azure AD tenant ID for a given user login name (email address)
      $tenantId = get-tenantIdFromLogin -Username
      .PARAMETER Username
      the UPN of a user
      filename: get-tenantIdFromLogin.ps1
      author: Jos Lieben
      created: 8/3/2019
    $openIdInfo = Invoke-RestMethod "$($Username.Split("@")[1])/.well-known/openid-configuration" -Method GET
    return $openIdInfo.userinfo_endpoint.Split("/")[3]

Obviously, you can also get the tenant ID by just filling out bogus info in front of the user’s login (e.g., it’ll still work as only the domain part of the login is really used.

Hope this helps someone 🙂

Git link:

Deploying an embedded file (FONT) in a Powershell script through Intune MDM

Most solutions that describe how to deploy a font through Intune use an external source to host fonts such as Azure Blob storage.

If you want to KISS (keep it simple, stupid), you don’t want to maintain two different things (your script and externally accessible storage).

The following example shows how to embed a file in a PowerShell script, and then install it into the Fonts folder. This could, of course, be used for other purposes, but don’t forget that the script size limit in Intune is only 200kb.

First, execute the following in a PS window:

$inputFontPath = "C:\fonts\YourFont.ttf"
Compress-Archive $inputFontPath -CompressionLevel Optimal -DestinationPath (Join-Path $Env:TEMP -ChildPath "") -Force
[Array]$bytes = [io.file]::ReadAllBytes((Join-Path $Env:TEMP -ChildPath ""))
$b64 = [System.Convert]::ToBase64String($bytes)
$b64 | Set-Clipboard

You have now compressed and base64 encoded YourFont.ttf, and this is loaded in memory (clipboard).

Create a new file, e.g. YourFont.ps1 and add the following:

$byteContent = [System.Convert]::FromBase64String($b64)
$byteContent | Set-Content (Join-Path $Env:TEMP -ChildPath "") -Encoding Byte -Force
Expand-Archive -Path (Join-Path $Env:TEMP -ChildPath "") -DestinationPath (Join-Path $Env:TEMP -ChildPath "tempfontsfolder") -Force

$sa =  new-object -comobject shell.application
$Fonts =  $sa.NameSpace(20)
gci (Join-Path $Env:TEMP -ChildPath "tempfontsfolder") | % {$Fonts.MoveHere($_.FullName)}

Remove-Item (Join-Path $Env:TEMP -ChildPath "") -Force -ErrorAction SilentlyContinue
Remove-Item (Join-Path $Env:TEMP -ChildPath "tempfontsfolder") -Force -Recurse -ErrorAction SilentlyContinue

On the first line, follow the instructions (e.g. paste your b64-encoded font on that line). Now save and check if the size is below 200kb, then distribute the script to your users. For Fonts, don’t forget to let the script run in system context as administrative permissions are required when installing Fonts.

This method can be used to deploy other payloads as well, happy scripting!

Office 365, Azure, Enterprise Mobility and DevOps