Category Archives: Azure

Getting remoteapps through vm custom extension on Azure session brokers

So I wanted to retrieve the remoteapps present on VM’s in a uniform way, without logging in to either VM’s or database.

Using a custom extension, I tried to execute the Get-RDRemoteApp command and got the following:

Get-RDRemoteApp : A Remote Desktop Services deployment does not exist on server. This operation can be perfor
med after creating a deployment. For information about creating a deployment

Apparently, all the powershell commands for RDS require that you DON’T run them under SYSTEM. Of course VMExtensions run under SYSTEM. So, to get all remoteapps in a RDS deployment, execute the following Powershell script as VMExtension on a connection broker VM:

 

$farms = get-childitem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\CentralPublishedResources\PublishedFarms"
foreach($farm in $farms){
    (get-childItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\CentralPublishedResources\PublishedFarms\$($farm.PSChildName)\Applications").PSChildName
}

To register this Powershell script as a VM extension and retrieve the results

  1. Save the above PS code to a file
  2. Upload the file somewhere (e.g. public blob storage)
  3. Get the URL of the File
  4. Use Login-AzureRMAccount
  5. Execute Set-AzureRmVMCustomScriptExtension -FileUri URL TO SCRIPT -Run FILENAME OF SCRIPT -VMName VMNAME -Name “RetrieveRemoteApps” -ResourceGroupName RESOURCEGROUP NAME -location “westeurope” -ForceRerun $(New-Guid).Guid
  6. To retrieve the list (after execution): [regex]::Replace(((Get-AzureRmVMDiagnosticsExtension -ResourceGroupName RESOURCEGROUP NAME -VMName VM NAME -Name “RetrieveRemoteApps” -Status).SubStatuses[0].Message), “\\n”, “`n”)

Running an Azure runbook on a System hybrid worker

Azure Runbooks are usually run in the cloud (on an automatically assigned ‘Microsoft’ host) or on a Hybrid Worker Group.

Hybrid Worker Groups consist of 1 or more machines, but there are also ‘System hybrid workers’, which are machines monitored by OMS. If you want to execute a Powershell script directly on a specific System hybrid worker, or on a specific group member of a worker group, you can use Powershell and specify the host instead of the group:

Start-AzureRmAutomationRunbook -Name “RunbookName” -RunOn hybridWorkerName -AutomationAccountName “automationaccount” -ResourceGroupName “resourcegroup”

If you try this on a System Hybrid Worker, you’ll get an error on the device itself and in the runbook results:

“Invalid Runbook xxx Authenticode signature status – NotSigned”.

This can be ‘fixed’ by setting the following registry key to ‘False’:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HybridRunbookWorker\GuidOfYourWorker\EnableSignatureValidation

Et voila, the runbook runs nicely. I do not recommend disabling this key in production, this article is purely to share knowledge, and if someone knows how to do this without disabling this key, I’d love to hear it!

Remove-StaleIntuneDevices using a scheduled Azure Runbook

I recently came upon a really cool post by Josh and Sarah that explains how to clean up stale devices in Intune using the Graph API.

As I want to run this from an Azure runbook, silently, I had to modify it a little so it automatically consents to azure app permissions and logs in silently. If you’d like to use it, feel free to add it from the Azure gallery (search for Lieben) or download it yourself.

Make sure you’ve also imported the AzureAD and AzureRM modules into your automation account, and configured a credential object for the script to use.

GitLab: Remove-StaleIntuneDevicesForAzureAutomation.ps1

Technet: Remove-Stale-Intune-4b07488a

How to grant OAuth2 permissions to an Azure AD Application using PowerShell unattended / silently

You may know this button:There is no native Powershell command to grant OAuth permissions to an Azure AD Application, so I wrote a function for that. Note that this is NOT a supported way to grant permissions to an application because it does not follow the proper admin consent flow that applications normally use.

The great advantage of my method is that it can be used to grant permissions silently, AND to ‘hidden’ and/or multi-tenant applications that companies like Microsoft use for backend stuff like the Intune API. (e.g. the ‘Microsoft Intune Powershell’ multi-tenant application).

The function requires AzureAD and AzureRM modules installed!


Function Grant-OAuth2PermissionsToApp{
Param(
[Parameter(Mandatory=$true)]$Username, #global administrator username
[Parameter(Mandatory=$true)]$Password, #global administrator password
[Parameter(Mandatory=$true)]$azureAppId #application ID of the azure application you wish to admin-consent to
)

Function Grant-OAuth2PermissionsToApp{
    Param(
        [Parameter(Mandatory=$true)]$Username, #global administrator username
        [Parameter(Mandatory=$true)]$Password, #global administrator password
        [Parameter(Mandatory=$true)]$azureAppId #application ID of the azure application you wish to admin-consent to
    )

    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd)
    $res = login-azurermaccount -Credential $mycreds
    $context = Get-AzureRmContext
    $tenantId = $context.Tenant.Id
    $refreshToken = $context.TokenCache.ReadItems().RefreshToken
    $body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
    $apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'
    $header = @{
    'Authorization' = 'Bearer ' + $apiToken.access_token
    '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/RegisteredApplications/$azureAppId/Consent?onBehalfOfAll=true"
    Invoke-RestMethod –Uri $url –Headers $header –Method POST -ErrorAction Stop
}

GITLAB: Grant-OAuth2PermissionsToApp.ps1

Connect-AzureRMAccount requires CASE SENSITIVE input for the tenantId

Just for those poor souls googling this error:

get-azurermvm : Your Azure credentials have not been set up or have expired, please run Connect-AzureRmAccount to set
up your Azure credentials.

Or any other command after Connect-AzureRMAccount with the -tenantId switch specified.

Took me over an hour to figure out that the tenant ID is actually case sensitive as the error is confusing, the log in works fine but subsequent commands fail.

set Intune MDM user scope to ALL using Powershell and hidden API

If you want to change the settings on this page (or most Azure Portal pages) programmatically:

Microsoft’ll tell you to use your browser, there is no API/PS for this yet. As I really hate the answer “no”, I used Fiddler and baked some Powershell:


login-azurermaccount
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$refreshToken = $context.TokenCache.ReadItems().RefreshToken
$body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
$apiToken = Invoke-RestMethod "https://login.windows.net/$tenantId/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'

$header = @{
'Authorization' = 'Bearer ' + $apiToken.access_token
'Content-Type' = 'application/json'}
$url = "https://main.iam.ad.ext.azure.com/api/MdmApplications/eab0bcaf-9b2e-4e62-b9be-2eea708422f8?mdmAppliesToChanged=true&mamAppliesToChanged=true"

$content = '{"objectId":"eab0bcaf-9b2e-4e62-b9be-2eea708422f8","appId":"0000000a-0000-0000-c000-000000000000","appDisplayName":"Microsoft Intune","appCategory":null,"logoUrl":null,"isOnPrem":false,"appData":{"mamEnrollmentUrl":null,"mamComplianceUrl":null,"mamTermsOfUseUrl":null,"enrollmentUrl":"https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc","complianceUrl":"https://portal.manage.microsoft.com/?portalAction=Compliance","termsOfUseUrl":"https://portal.manage.microsoft.com/TermsofUse.aspx"},"originalAppData":{"mamEnrollmentUrl":"https://wip.mam.manage.microsoft.com/Enroll","mamComplianceUrl":"","mamTermsOfUseUrl":"","enrollmentUrl":"https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc","complianceUrl":"https://portal.manage.microsoft.com/?portalAction=Compliance","termsOfUseUrl":"https://portal.manage.microsoft.com/TermsofUse.aspx"},"mdmAppliesTo":2,"mamAppliesTo":2,"mdmAppliesToGroups":[],"mamAppliesToGroups":[]}'
Invoke-RestMethod –Uri $url –Headers $header –Method PUT -Body $content -ErrorAction Stop

You can do almost anything using the above snippet and changing the endpoint URL and POST contents. Use Fiddler to capture, then replicate in code 🙂

Be warned and use at your own risk, obviously this method is unsupported.

CSP delegation on non CSP azure subscriptions

If you’re a Cloud Solution Provider and you supply a CSP azure subscription to that tenant, your AdminAgents will have Owner access to that subscription by default. Lets say the customer also has an existing subscription (maybe a non-profit donation?).

When you add your accounts as Owner to the existing tenant’s (non-csp) subscription, your users are added as Guest accounts in the customer’s Azure AD. This removes the delegated CSP rights on the CSP subscription because the references to foreign accounts break due to the new guest accounts having the same UPN.

So, alternatively, use

Get-AzureRmRoleAssignment -Scope "/subscriptions/<CSP SUBSCRIPTION ID>

on the CSP subscription to get the Foreign Principal ID for your own tenant. Then use

New-AzureRMRoleAssignment -ObjectId <FOREIGN PRINCIPAL ID> -Scope "/subscriptions/ 
<EXISTING SUBSCRIPTION ID>" -RoleDefinitionName Owner

to add the foreign principal ID to the existing customer subscription to get delegated access 🙂

 

Setting up Okta User -> Office 365 contact synchronisation

Okta natively does not allow you to sync users to Office 365 contacts; they either exist as users in Office 365, or they don’t exist at all.

In hybrid scenarios where you are doing a staged migration to Office 365, or where you simply manage your contacts in Okta, you may want to populate the Global Address List in Office 365 with your Okta users.

I’ve written a simple solution for this, you will require:

  1. Okta Admin Access (to obtain a token)
  2. Office 365 credentials (to write / modify Contacts)
  3. An Azure Subscription (for automation)

The solution will sync your users in Okta to Office 365, take note of the following: Continue reading Setting up Okta User -> Office 365 contact synchronisation