Connect Azure Functions To Office 365
,

Connect an Azure Function to Office 365

In the past couple of weeks I’ve uploaded a few scripts to help manage Office 365 customer environments in bulk via delegated administration. These scripts work well for us, though they only work when they’re initiated by a delegated administrator here. Sure, we could set them up on a server as a scheduled task, though in the interest of keeping things in the cloud, we’re moving them to Azure Functions.

If you’re interested, the scripts I’ve posted so far regarding Delegated Administration are here:

What are Azure Functions?

The Azure Functions service is Microsoft’s Function as a Service offering (FaaS). It’s similar to Hook.io, Google Cloud Functions or AWS Lambda if you’ve used any of those. Basically it lets you run standalone scripts or functions of a program in the cloud. One of Azure Functions’ benefits is that you don’t have to look after the underlying infrastructure, you can just add in your code and you’re pretty much done. You can start an Azure function using a HTTP or Azure Storage Queue trigger, or just set it to run on a timer. Azure Functions can run a variety of languages, though in this scenario, we’ll convert a simple Office 365 PowerShell script into a timer trigger function that runs each weekday.

Consumption Plan vs App Service Plan

Azure Functions Consumption Plan vs App Service PlanFor the number of functions we’ll be running, Azure functions are pretty much free with a Consumption Plan. This plan gives you a grant of 1 million executions and 400,000 GB-s of bandwidth, which we’ll be well under. However, Azure functions can also run on top of a paid Azure App Service Plan – which we’ll be taking advantage of.

Why pay for an Azure App Service Plan to run Azure Functions?

One of the limitations of the (almost) free version of Azure Functions is that it’s executions have a 5 minute limit, after which time they are terminated automatically. Apparently this is because the underlying virtual machines that run the functions are regularly recycled. Since some of our scripts have the potential to run longer than five minutes, we need to provision a small Azure App Service resource and then run our Azure functions on top of this. The VM that runs our App service runs continuously and will support long running functions

Here’s what we want to achieve:

  1. Set up an Azure Function App running on an App Service Plan
  2. Connect an Azure Function to Office 365
  3. Modify an existing PowerShell script to run on an Azure function

In another post we’ll look at connecting Azure Functions to Azure Storage to use in reporting via Power BI, and triggers for Microsoft Flow.

How to set up a new Azure Function App

  1. Log on to https://portal.azure.com using an account with an active Azure subscription.
  2. Click the Green + button on the left menu, search for Functions, then click Function AppSearch For Azure Functions And Click Create
  3. Click Create on the bottom right
  4. Complete the required fields for the Function AppComplete Fields To Create Azure Function App
  5. Choose to create a new Resource Group and Storage Account. For the Hosting Plan option, choose App Service Plan, then select an existing subscription or create a new one. In my case, I chose an S1 Plan, which is probably overkill. You’ll be able to get by with something much smaller.Create A New App Service Plan For Azure Functions
  6. Once you’ve completed the required fields, click Create and wait for it to complete deploymentWait For Azure Function App To Complete Deployment
  7. After it’s finished deploying, open your function app and click the + button to create a new function.Create A New Function Within Azure Functions
  8. Choose Custom function at the bottomChoose To Create A New Custom Function
  9. On the dropdown on the right, choose PowerShellSelect PowerShell From Azure Functions Drop Down
  10. Choose TimerTigger-PowerShell and enter a name for your Azure Function.Create Timer Trigger PowerShell Azure Function
  11. For the Schedule, enter a cron expression. There used to be documentation at the bottom of the page on how to format these, though at the time of writing it hasn’t appeared. For a function that runs Monday to Friday at 9:30 AM GMT time, enter the following:
    0 30 9 * * 1-5

    Define Schedule For Azure Function

  12. Click Create, you’ll be greeted with an almost blank screen where you can start to enter your PowerShell script. Before we do this, we’ll set up the Azure function to connect to Office 365, and secure your credentials within the function app.

Set up your Azure Function to connect to Office 365

In this step, we’ll be doing the following:

Define and retrieve your FTP Details

The FTP Details of the Azure Function are needed to upload resources that the Azure Function requires to connect to Office 365.

Download, then upload the MSOnline PowerShell Module via FTP

Azure Functions have a lot of PowerShell Modules installed by default, though they don’t have the MSOnline module that lets us connect to Office 365. We’ll need to download the module on our local computer, then upload it into the Azure function. This method was borrowed from this article by Alexandre Verkinderen.

Secure your Office 365 Credentials within the Function App

Right now, Azure Functions don’t integrate with the Azure Key Vault service. While we can store credentials within the function, these credentials are stored in plain text where anyone with access to the function can view them. This method was borrowed from this article by Tao Yang.

How to define and retrieve the FTP credentials for your Azure function app

  1. Click on the name of your function on the left menu.Click Azure Function Settings To Retrieve FTP Details
  2. Click Platform Features at the top, then click Deployment CredentialsOpen Platform Features
  3. Define a username and password for your FTP CredentialsSet Deployment Credentials For FTP Access
  4. Next under General Settings, click Properties.Open Properties Under General Settings
  5. Copy the FTP Host Name and make a note of it. You’ll need it to connect to the function’s storage via FTP and upload the MSOnline ModuleCopy FTP Host Name And User Details For FTP Deployment

Download, then upload the MSOnline PowerShell Module via FTP

  1. Open PowerShell on your computer, then run the following command. Make sure there’s a folder called ‘temp’ in your C:\ drive.
    Save-Module msonline -Repository PSGallery -Path "C:\temp"

    Save MSOnline Module For Office365 PowerShell On Local PC

  2. Wait for it to download, then make sure it exists within C:\tempWait For MSOnline Module To Download
  3. Open Windows Explorer, and connect to your function via FTP using the FTP Hostname and credentials we retrieved earlier.Connect To Your Azure App Service Via FTP Credentials
  4. Navigate to site/wwwroot/YourFunctionName then create a new folder called binCreate Bin Directory Under Azure Function
  5. Open the bin directory, and upload the MSOnline folder from your C:\Temp DirectoryUpload MSOnline PowerShell Module To Bin Directory In Azure Function

Secure your Office 365 Credentials within the Azure Function App

  1. On your computer, open PowerShell again and run the following commands. When you’re asked for your password, enter the password for the delegated admin account that you’ll use to manage your customers Office 365 environments. Make sure you press Enter again to run the final command to output the EncryptedPassword.txt file.
    $AESKey = New-Object Byte[] 32
     $Path = "C:\Temp\PassEncryptKey.key"
     $EncryptedPasswordPath = "C:\Temp\EncryptedPassword.txt"
     [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
     Set-Content $Path $AESKey
     $Password = Read-Host "Please enter the password"
     $secPw = ConvertTo-SecureString -AsPlainText $Password -Force
     $AESKey = Get-content $Path
     $Encryptedpassword = $secPw | ConvertFrom-SecureString -Key $AESKey
     $Encryptedpassword | Out-File -filepath $EncryptedPasswordPath

    Run PowerShell Script To Secure Password
    This will create two files on in your C:\temp folder. An EncryptedPassword text file and a PassEncryptKey file. Be sure to delete the EncryptedPassword file once we’re done.Locate Secure Password And Key In Temp Folder

  2. Return to the FTP connection and create a directory called keys under the bin directory
  3. Upload the PassEncryptKey file into the keys directory.Upload PassEncryptKey To Azure Function Via FTP
  4. Return to your Azure Function Platform Settings, then open Application Settings.
  5. Under Application Settings, create two new Key-Value pairs. One called user, which contains the username of your delegated admin account, and another called password, which contains the contents of your EncryptedPassword.txt file. Once you’ve added this, be sure to delete the EncryptedPassword.txt file from your computer.
  6. Before you leave Application settings, update the Platform from 32 bit to 64 bit.Update Azure Function Platform To 64 Bit
  7. Wait for the settings to apply, then return to the Develop Section of your Azure FunctionWait For Azure Function Web App Settings To Apply

Modify your Office 365 PowerShell script for Azure Functions

  1. Update the variables at the top of the script to ensure they match the function name, Module Name and Module Version.For your existing scripts, you may need to update your Write-Host references to Write-Output.This sample script is a modified version of this one. It will set the default password expiration policy for all of your customers’ domains to never expire.You can use this one or create your own script under the # Start Script comment
    Write-Output "PowerShell Timer trigger function executed at:$(get-date)";
    
    $FunctionName = 'SetPasswordExpirationPolicy'
    $ModuleName = 'MSOnline'
    $ModuleVersion = '1.1.166.0'
    $username = $Env:user
    $pw = $Env:password
    #import PS module
    $PSModulePath = "D:\home\site\wwwroot\$FunctionName\bin\$ModuleName\$ModuleVersion\$ModuleName.psd1"
    $res = "D:\home\site\wwwroot\$FunctionName\bin"
    
    Import-module $PSModulePath
    
    # Build Credentials
    $keypath = "D:\home\site\wwwroot\$FunctionName\bin\keys\PassEncryptKey.key"
    $secpassword = $pw | ConvertTo-SecureString -Key (Get-Content $keypath)
    $credential = New-Object System.Management.Automation.PSCredential ($username, $secpassword)
    
    # Connect to MSOnline
    
    Connect-MsolService -Credential $credential
    
    # Start Script
    
    $Customers = Get-MsolPartnerContract -All
    $PartnerInfo = Get-MsolCompanyInformation
    
    Write-Output "Found $($Customers.Count) customers for $($PartnerInfo.DisplayName)"
    
    
    foreach ($Customer in $Customers) { 
    
    	Write-Output "-----------------------------------------------"
    	Write-Output " "
    	Write-Output "Checking the Password Expiration Policy on each domain for $($Customer.Name)"
    	Write-Output " "
    
    	$domains = Get-MsolDomain -TenantId $Customer.TenantId | Where-Object {$_.Status -eq "Verified"}
    
    	foreach($domain in $domains){
     
    		$domainStatus = Get-MsolPasswordPolicy -TenantId $Customer.TenantId -DomainName $domain.Name
    
    		if($domainStatus.ValidityPeriod -eq 2147483647){
    
    			Write-Output "Password Expiration Policy is set for $($domain.name) already"
    
    			$PasswordsWillExpire = $false
    
    			$MsolPasswordPolicyInfo = @{
    
    				TenantId = $Customer.TenantId
    				CompanyName = $Customer.Name
    				DomainName = $domain.Name
    				ValidityPeriod = $domainStatus.ValidityPeriod
    				NotificationDays = $domainStatus.NotificationDays
    				PasswordsWillExpire = $PasswordsWillExpire
    			}
    
    		}
    
    
    
    		if($domainStatus.ValidityPeriod -ne 2147483647){
    
    			Write-Output "Setting the Password Expiration Policy on $($domain.Name) for $($Customer.Name):"
    			Write-Output " "
    
    			Set-MsolPasswordPolicy -TenantId $Customer.TenantId -DomainName $domain.Name -ValidityPeriod 2147483647 -NotificationDays 30
    
    			$PasswordPolicyResult = Get-MsolPasswordPolicy -TenantId $Customer.TenantId -DomainName $domain.Name
    
    			if($PasswordPolicyResult.ValidityPeriod -eq 2147483647){
    
    				$PasswordsWillExpire = $false
    				Write-Output "Password policy change confirmed working"
    			}
    
    			if($PasswordPolicyResult.ValidityPeriod -ne 2147483647){
    
    				$PasswordsWillExpire = $true
    				Write-Output "Password policy change not confirmed yet, you may need to run this again."
    			}
    
    			$MsolPasswordPolicyInfo = @{
    
    				TenantId = $Customer.TenantId
    				CompanyName = $Customer.Name
    				DomainName = $domain.Name
    				ValidityPeriod = $PasswordPolicyResult.ValidityPeriod
    				NotificationDays = $PasswordPolicyResult.NotificationDays
    				PasswordsWillExpire = $PasswordsWillExpire
    
    			}
    
    		}
    	}
    }
    
  2. Click Run to manually start the script. You should see following output under LogsAzure Functions Output Log