Category Archives: Azure

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

Parsing a GET request in PHP with an Azure Function

While playing around with PHP (experimental support) in Azure Functions, I noticed that there is no documentation yet and very few examples, so here’s my first simple example on how to build an Azure Function using PHP to parse a very simple GET request.

I’m assuming you’ve set up your function, go into Files and edit the function.json file:

 

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get"
      ],
      "authLevel": "function"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "disabled": false
}

This sets the function to listen to get requests and ignore the default Azure Table storage stuff.

Then open the run.php file and Continue reading Parsing a GET request in PHP with an Azure Function

On-Demand MSI customization using Azure Functions

This post describes how you can use the WIX Toolkit or any DLL file in an Azure Function, in this case to edit an MSI file on the fly. The WIX Toolkit is free, but only runs on Windows. Azure Functions run on Windows too, isn’t that nice 🙂

So, an example use case could be my OnedriveMapper MSI file, which is installed with a configuration GUID property by an admin to customize OnedriveMapper. If that GUID was already in the MSI, no such parameter would be necessary.

Using an Azure function in a download link or http request, we could insert a GUID on the fly and create personalized MSI files on demand.

I’ll leave other applications to your imagination, let’s get started!

  1. Download the WIX toolkit (binaries)
  2. Extra Microsoft.Deployment.WindowsInstaller.dll
  3. Add it to the function files or host it at an URL somewhere. In my example, I’m hosting it at http://www.lieben.nu/wix/wix.dll
  4. Add your MSI file to your function files or host it at an URL somewhere. In my example, I’m hosting it at http://www.lieben.nu/wix/OnedriveMapper.msi
  5. Add the following code to the Azure Function:

Continue reading On-Demand MSI customization using Azure Functions

Creating a Dynamic Group using the Graph API

Azure has a very nice feature called ‘Dynamic Groups‘. We use these in our customer tenants to dynamically generate a group with actual users, excluding Guest accounts (marked with #EXT#).

As I couldn’t find any articles detailing how to create a Dynamic Group through the Graph API, I’m posting this for whoever it helps 🙂

$dynamicGroupProperties = @{
    "description" = "Dynamic Group Created through the Graph API";
    "displayName" = "Dynamic Group Created through the Graph API";
    "groupTypes" = @("DynamicMembership");
    "mailEnabled" = $False;
    "mailNickname" = "testnickname";
    "membershipRule" = "(user.userPrincipalName -notContains `"#EXT#@`") -and (user.userType -ne `"Guest`")";
    "membershipRuleProcessingState" = "On";
    "securityEnabled" = $True
}

invoke-webrequest -Headers $headerParams -uri "https://graph.microsoft.com/beta/groups" -Body (ConvertTo-Json $dynamicGroupProperties) -method POST -Verbose

If you’re not yet used to working with the Graph API, read up on how to connect to the Graph API using Powershell.

Powershell Lock Function

A handy Powershell function to lock / unlock using .NET, to prevent concurrent read/writes to files or anything else you like.

function handleThreadLock{
    Param(
        [switch]$setLock,
        [switch]$releaseLock,
        [string]$lockName="defaultLockName",
        [int]$timeOut=600
    )
    if($setLock){
        #register a thread lock
        $script:threadLock = New-Object System.Threading.Mutex($false, $lockName)
        $waited = 0
        while($true){
            try{$lockState = $script:threadLock.WaitOne(1000)}catch{$lockState=$False}
            if($lockState){
                break
            }else{
                $waited+=1
                if($waited -gt $timeOut){
                    Throw "failed to get a thread within $timeOut seconds!"
                }
            }
        }
    }  
    if($releaseLock){
        #release a thread lock
        [void]$script:threadLock.ReleaseMutex()
    }  
}

In your script, call it like this:

try{
    handleThreadLock -setLock
}catch{Throw "Failed to set lock!"}

try{
    add-content -Path "c:\yourfile.txt" -Value "log entry" -ErrorAction Stop
}finally{
    handleThreadLock -releaseLock
}

The process cannot access the file ‘C:\Windows\system32\config\systemprofile\AppData\Roaming\Windows Azure Powershell\TokenCache.dat’ because it is being used by another process.

While building some multithreading Azure Runbooks that log into multiple subscriptions simultaneously, I noticed that these multiple concurrent runs often end up on the same Azure Automation Host.

Apparently, these runbooks then don’t run in full isolation, and the following error may occur:

The running command stopped because the preference variable “ErrorActionPreference” or common parameter is set to Stop: The process cannot access the file ‘C:\Windows\system32\config\systemprofile\AppData\Roaming\Windows Azure Powershell\TokenCache.dat’ because it is being used by another process.

AzureProfile.json may also get locked. I resolved this by doing a retry on the Save-AzureRMContext, and using a randomized file name for the azure json profile:


$randomProfileName = [System.IO.Path]::GetRandomFileName()

Save-AzureRmContext -Path .\$randomProfileName -Force -Confirm:$False

And for my full Azure Login code snippet:

$randomProfileName = [System.IO.Path]::GetRandomFileName()
 $tries=0
    while($true){
        $tries++
        try{
            Write-Output "Logging in to Azure $azureSubscription"
            $res = Login-AzureRmAccount -Credential $azureCreds -SubscriptionId $azureSubscription -TenantId $tenantId -ErrorAction Stop
            Select-AzureRmSubscription -SubscriptionId $azureSubscription -ErrorAction Stop -TenantId $tenantId
            if($res.Context.Subscription.Id -eq $azureSubscription){
                Write-Output "Logged in to Azure subscription $($res.Context.Subscription.Id)"
            }else{
                Throw "Failed, we were logged in to $($res.Context.Subscription.Id) while trying to log in to $azureSubscription"
            }
            Save-AzureRmContext -Path .\$randomProfileName -Force -Confirm:$False
            break
        }catch{
            if($tries -ge 30){
                Throw
                Exit
            }
            #sleep on failed attempts, as the azure token cache gets locked by concurrent jobs
            sleep -s (Get-Random -minimum 1 -maximum 6)
        }
    }

Transferring a domain to Azure (dns and billing)

Lately I’ve been playing with custom domains in Azure, Microsoft has been allowing us to directly purchase domain in Azure for a while now. This leverages GoDaddy’s API, but Microsoft bills you for the domain, consolidating your domains, management and usage nicely in Azure.

The portal only allows you to purchase new domains, so how do you transfer existing domains to Azure DNS?

First you’ll need a transfer code, which you can get from your current DNS provider. Then, execute the following script:


Register-AzureRmResourceProvider -ProviderNamespace Microsoft.DomainRegistration
$rgName = "NAME OF YOUR RESOURCE GROUP"
$ResourceLocation = "Global"
$ResourceName = "MYDOMAINNAME.NL"
$PropertiesObject = @{
'Consent' = @{
'AgreementKeys' = @("DNPA","DNTA");
'AgreedBy' = '122.13.11.20'; #ip address you're running this script from
'AgreedAt' = '2017-17-07T08:37:40'; #roughly the current time
};
'authCode' = 'DOMAIN TRANSFER CODE'; #code by current domain provider
'Privacy' = 'true';
'autoRenew' = 'true';
}

New-AzureRmResource -ResourceName $ResourceName -Location $ResourceLocation -PropertyObject $PropertiesObject -ResourceGroupName $rgName -ResourceType Microsoft.DomainRegistration/domains -ApiVersion 2015-02-01 -Verbose

It will take a long time to run, but you’ll have a custom domain in Azure that you can now connect to websites and/or manage through AzureDNS.

Note: this only works for domains OLDER than 60 days and can take 5-7 days until the domain is usable in Azure, all domain records will be copied to an Azure DNS zone.

Azure Runbooks and Write-Progress

If you use Azure Runbooks, but develop scripts locally first…you may like to display progress indicators to yourself when handling large amounts of records / data with Write-Progress.

Be sure to parameterise this, because if you use Write-Progress in an Azure Runbook, it will seriously slow your runbook down, increasing the cost, and if there are over 4000 write-progress calls the runbook will hang and crash.

Azure Powershell MFA and Device Commands

As a guy who really likes to script things, I was very happy to read this today:

http://blogs.technet.com/b/ad/archive/2015/10/20/azure-ad-powershell-public-preview-of-support-for-azure-mfa-new-device-management-commands.aspx

Firstly, my Powershell scripts / sessions can now be secured using MFA, without having to rewrite my scripts, except those where I built the Credential object with preset credentials….but that’s old anyway.

Secondly, I can now finally manage Registered Devices with the same Module, allowing me to use these commands:

  • Get-MsolDevice
  • Enable-MsolDevice
  • Disable-MsolDevice
  • Remove-MsolDevice

You can use either the owner’s UPN or the device ID with these commands.