Getting Around the Basics of Azure Automation for Office 365

One of the the things that we've learned about the cloud over the past few years is that you still need an environment (a utility server, an app server, a something) to run batch jobs and custom scripts against your environments.  You may have an on-premises script task for moving and rebalancing mailboxes, updating group memberships or permissions, or adding users to certain policies after their objects have been provisioned by the HR system.

But the cloud fixes that, right?

Eh. The cloud is just where we've moved your cheese.  While many cloud providers (us included) have made significant investments in developing automation technologies and off-the-shelf solutions, there comes a time when your organization requires customization that we can't do out of the box.

Enter, the utility server.

Or now, enter Azure Automation--your utility server in the sky.

[toc]

What is Azure Automation?

Well, I think I already answered that--it's your utility server in the sky.  More specifically, it's a framework that allows you to publish scripted procedures (called 'runbooks,' since we needed yet another new term to describe things you already do).  Runbooks come in several flavors:

  • GUI or Graphical: Much like they sound, it's a GUI interface for creating a runbook.  They only run in the Azure portal, so you can't really reuse the code anywhere else.  You can't directly see or modify the code.
  • PowerShell: <spoiler>It's PowerShell.</spoiler>  You can basically reuse existing code with little modification.
  • PowerShell Workflow: It looks like PowerShell, but has some quirks we call "features."  It's got some fantastic qualities (such as parallel processing and failure recovery), but converting your existing PowerShell scripts is going to require some work.  PowerShell Workflow runbooks probably have the most capability, but require the becoming familiar with deserialized objects (meaning an object's properties are available, but not its methods) and using named parameters (as opposed to positional parameters).
  • Python: <another spoiler>It's Python.</spoiler>  You are executing Python scripts.  For some stuff (such as web calls and web service automation), this is great.  For working with Office 365 cmdlets, not ideal.

Now What?

So, in order to use Azure Automation with Office 365, we need to do a few things.  If you've got an Office 365 tenant, you're halfway there.  Next, you need to make sure you have an Azure subscription that you can run this stuff in.  You can get a trial subscription for free, but if you intend to keep on using it, you'll probably incur some small monthly charge to run scripts.  Depending on your load, it will most likely be much less than the effort or cost of maintaining a VM to do the same things, plus no updating or patching--really getting to the point of utility pay-as-you-go service.

Prepare Credentials

You'll need credentials to do anything.  You'll need an account in Office 365 and an automation object in Azure to hold the user account credentials and runbooks.

Create an Office 365 Service Account

In order to run services, you're going to have to log into stuff.  So, it would probably behoove you to create an account with the privileges you need to do the stuff you want to do.  Maybe you want a single global admin account, maybe you want one per service, maybe you want one per function or script that you're going to execute.  In any case, it should be one with a secure (read:long and complex) password as it will probably change infrequently.

Let's say we want to just create a single Office 365 Global Admin account.  We could do it through the portal ... OR ... since I already have my PowerShell window open and connected to my tenant:

 New-MsolUser -UserPrincipalName Office365_ServiceAutomation@undocumentedfeatures.onmicrosoft.com -DisplayName "Office 365 Service Automation" -PasswordNeverExpires $true -Password (([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | sort {Get-Random})[0..15] -join '')
Add-MsolRoleMember -RoleObjectId (Get-MsolRole | ? {$_.Name -eq "Company Administrator"}).ObjectId -RoleMemberObjectId (Get-MsolUser -UserPrincipalName office365_serviceautomation@undocumentedfeatures.onmicrosoft.com).objectid

Be sure to copy that password down. You won't remember it. ;-)

Create an Azure Automation Account

Now that I have an Office 365 account with an uber-complex password that I generated, I can create an Azure Automation Account using that saved credential.

Azure Automation requires an Azure subscription.  If you don't have one, you can select a "Free Trial" to get an idea of how they work, and then how much your runbooks cost to actually run.  In my demo Office 365 tenant, I'm going to set up a trial one.

Adding a Trial Subscription (if you don't have a subscription already associated with your account)

If you don't have a subscription, follow these quick steps to set one up.  If you do, just skip to the next section.

  1. Log into https://portal.azure.com using an Office 365 Global Admin account.
  2. In the search bar at the top, type Subscriptions and select it to bring up the subscriptions blade (or go to https://portal.azure.com/#blade/Microsoft_Azure_Billing/SubscriptionsBlade).
  3. Click the +Add button.  Yes, I know. This is an Opera browser screenshot. When 5 browsers for your job you require, be as picky you will not.
  4. Under Select An Offer, choose Free Trial.
  5. Fill out the sign-up form.  If you need help for this part, you might want to reconsider your field.
  6. After filling out the signup form, go back to the subscriptions tab in your browser and hit refresh.  You should see your new subscription present.  Once it's there, then we can continue.

Add the Automation Account

  1. In the Azure portal, type automation account in the search bar and select the option appearing under services or navigate to https://portal.azure.com/#blade/HubsExtension/Resources/resourceType/Microsoft.Automation%2FAutomationAccounts
  2. Click +Add to start the wizard.
  3. Select a name for your automation account. This name cannot be changed later, and must be unique in your tenant.  You'll need to select a resource group (if this is a trial subscription, you probably don't have one yet, so you'll need to create one).  You'll want to select a region that reflects where your Office 365 tenant lives. If you want to run this against Office 365, select No under Create Azure Run As account.  When you've got your settings selected, click Create.
  4. After it's done, you should be able to refresh the Automation Accounts blade and see the new account.

Add Modules

If you're connecting to Office 365 to Azure principals or MSOL objects (read:using the *AzureAD or *Msol cmdlets), you'll need to add the appropriate modules.  I like to add all the necessary modules here, that way you're not limited in the scripts you can import and call.

  1. Click on the Automation Account Name.
  2. Scroll down to modules, and then, if it's not displayed, select the ellipsis and click Browse to open the module gallery.
  3. Search for msonline and select it.
  4. Click Import.
  5. Click OK to acknowledge.

Add a Credential

  1. Scroll down to Credentials, select it, and then select +Add a credential.
  2. Enter a name for a credential (which we'll reference later), and then the username and password that you created earlier.  Click Create when finished.

Creating a PowerShell RunBook from an existing script

In this example, I'm going to take an existing script (say, maybe one that I created a few days ago here) and modify it to run on a daily basis.  In the script example I selected, I'm looking for litigation holds that end in less than a week and emailing an administrator.

Prepare or update script

Here's the current script as it appears on the aforementioned blog post:

 $SmtpServer = "smtpserver.domain.com"; 
$To = @("recipient1@domain.com", "recipient2@domain.com");
$From = "noreply@domain.com"; 
$NotificationWarningWindow = 7; 
[System.Collections.ArrayList]$obj = @();
$Today = Get-Date; 
[array]$mailboxes = Get-Mailbox -ResultSize Unlimited -Filter { LitigationHoldEnabled -eq $true } | Select DisplayName, PrimarySmtpAddress, Litigation* 
foreach ($mailbox in $mailboxes)
{
     $ExpirationDate = $mailbox.LitigationHoldDate + $mailbox.LitigationHoldDuration;
     If (($ExpirationDate - $Today).Days -lt $NotificationWarningWindow)
     {
          Write-Host "Warning: Litigation Hold for $($mailbox.PrimarySmtpAddress.ToString()) expires in less than $($NotificationWarningWindow) days" 
          $hashtemp = @{
          DisplayName = $mailbox.DisplayName;
          PrimarySmtpAddress = $mailbox.PrimarySmtpAddress;
          ExpirationDate = $ExpirationDate
          };
     $objtemp = New-Object PSObject -Property $hashtemp; 
     $obj += $objtemp; 
     Remove-Variable hashtemp,objtemp
     }
}

[String]$Body = $obj.GetEnumerator() | %{ "`n$($_.ExpirationDate)," + "$($_.PrimarySmtpAddress)," + "$($_.DisplayName)" }
Send-MailMessage -SmtpServer $SmtpServer -To $To -From $From -Subject "Litigation Hold expiring in the next 7 days" -Body $Body

As you can see, it's going to need some modification to run in this particular environment (such as connecting to Exchange Online).  Here's the how we're going to do that (updates/additions in bold):

  $Credential = Get-AutomationPSCredential -Name "Office365ServiceAccount-Credential" 

 $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection
Import-PSSession $Session -DisableNameChecking -Name ExSession

$SmtpServer = "outlook.office365.com"; 
$To = @("admin@undocumentedfeatures.onmicrosoft.com");
$From = "admin@undocumentedfeatures.onmicrosoft.com"; 
$NotificationWarningWindow = 7; 
[System.Collections.ArrayList]$obj = @();
$Today = Get-Date; 
[array]$mailboxes = Get-Mailbox -ResultSize Unlimited -Filter { LitigationHoldEnabled -eq $true } | Select DisplayName, PrimarySmtpAddress, Litigation* 
foreach ($mailbox in $mailboxes)
{
     $ExpirationDate = $mailbox.LitigationHoldDate + $mailbox.LitigationHoldDuration;
     If (($ExpirationDate - $Today).Days -lt $NotificationWarningWindow)
     {
           #  Write-Host "Warning: Litigation Hold for $($mailbox.PrimarySmtpAddress.ToString()) expires in less than $($NotificationWarningWindow) days" 
          $hashtemp = @{
          DisplayName = $mailbox.DisplayName;
          PrimarySmtpAddress = $mailbox.PrimarySmtpAddress;
          ExpirationDate = $ExpirationDate
          };
     $objtemp = New-Object PSObject -Property $hashtemp; 
     $obj += $objtemp; 
     Remove-Variable hashtemp,objtemp
     }
}

[String]$Body = $obj.GetEnumerator() | %{ "`n$($_.ExpirationDate)," + "$($_.PrimarySmtpAddress)," + "$($_.DisplayName)" }
If ($Body) {  Send-MailMessage  -UseSSL -Port 587  -Credential $Credential -SmtpServer $SmtpServer -To $To -From $From -Subject "Litigation Hold expiring in the next 7 days" -Body $Body  }<br>Remove-PSSession -Name ExSession

There.  Now we're ready to go.

Create a PowerShell Runbook

  1. Under Process Automation, select +Create a runbook.
  2. Add the basic details for the runbook, including a name and description (the name can only be alpha numeric with either dashes or underscores--no spaces or periods, start with a letter, and be no longer than 64 characters long).  Since we're just going to paste in our slightly modified PowerShell script, we're going to select the PowerShell runbook type.  Click Create when finished.
  3. The UI should automatically navigate you to editing the runbook.  If it doesn't, you can expand the Runbooks node, locate the one you're creating, and then paste the code in.
  4. Expand Assets and verify that the Office 365 credential is there.
  5. Click Save to save the runbook.
  6. Click the Test pane button.
  7. Click Start.
  8. Wait.
  9. Waiting is the hardest part, indeed.
  10. Once the Refresh job streams button appears, you can click it to refresh the screen output (if you're impatient like me).
  11. If everything goes well, you should end up with an email in your mailbox. Woot!

Set It and Forget It

Once your script works in the test pane, it's go-time.

  1. Close the test blade and locate the Publish button. Push it.  I know you want to.
  2. Yep.  This is what you want.  Don't chicken out now!  Click Yes.
  3. Scroll down to Schedules and then click +Add a schedule.
  4. Select Link a schedule to your runbook.
  5. Click +Create a new schedule.
  6. Enter a name, description, and other parameters.  Even if you've never scheduled a meeting in Outlook before, it should be pretty easy to figure out.  Click Create when you've conquered this task.
  7. Mash that OK button to bring some finality to this.

Now that we've got this problem licked, we'll be moving on to using Azure Automation for some more complex scripting tasks. Stay tuned!