Get rid of your Office 365 Scheduled tasks with Azure Automation!

Implementing Office 365 in a production environment you most often end up with quite a few scheduled scripts for licensing, shared mailbox maintenance and other similiar tasks. When moving more and more services to the cloud, moving from infrastructure services to platform services is the way of nature.

One service that can help you to get more efficient when it comes to scheduling scripts is Azure Automation. Instead of having a server where you schedule your scripts, you simply schedule them within the automation service that takes care of the rest. As of now, Azure Automation does not support use of the Azure Active Directory Powershell module, which means we cannot use the service for our licensing scripts. Make sure to vote for the suggestion to fix that here.

Exchange Online, Lync Online and SharePoint Online do however work well, so that’s what my example is going to be about. To make it easy for me I’m using a script from one of my older posts about Dynamically adding mailbox permissions through groups, with some adjustments to fit Azure Automation.

1. First of all, if you don’t already have one, you need an Azure Automation account. I’m choosing to create mine in the West Europe region. By default, it will use a free automation plan that gives you 500 minutes of job runtime per month.
2015-04-05_16-56-21
2. Now create an asset/setting that will store your Exchange Online admin credentials in a secure way. We will call the asset ‘EXOCreds’ so we easliy can pick them up later in the script.2015-04-05_17-03-15
2015-04-05_17-04-13
2015-04-05_17-05-11
3. Create a runbook in the automation account you created earlier, in my case I called it ‘Sync-EXOSharedMailboxPermissions’.
2015-04-05_17-15-35
4. Now it’s time to author the runbook, in my example I’ve just changed some minor things with parameters and credentials. If you’re a PowerShell WF Pro, there might be a lot of things you can do to improve the script.
2015-04-05_19-11-27
Below you’ll find the code I’ve used:

Sync-EXOSharedMailboxPermissions

workflow Sync-EXOSharedMailboxPermissions {
<#
    .SYNOPSIS
    The script will automatically assign mailbox and recipient permissions on shared mailboxes based on groups.
    Modified 2015-04-05 to support Azure automation and PowerShell workflows
    .NOTES
    File Name: SharedMailboxViaGroups.ps1
    Author   : Johan Dahlbom, johan[at]dahlbom.eu
    Blog     : 365lab.net
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
    Requires PowerShell Version 3.0!
#>
    #Credentials to connect to Exchange Online
    $Credentials = Get-AutomationPSCredential -Name 'EXOCreds'
    #Prefix to search for
    $Prefix = 'SM-'
    function Connect-ExchangeOnline {
    param (
        $Creds
    )
        #Clean up existing PowerShell Sessions
        Get-PSSession | Remove-PSSession
        #Connect to Exchange Online
        $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Creds -Authentication Basic -AllowRedirection
        $Commands = @("Add-MailboxPermission","Add-RecipientPermission","Remove-RecipientPermission","Remove-MailboxPermission","Get-MailboxPermission","Get-User","Get-DistributionGroupMember","Get-DistributionGroup","Get-Mailbox")
        Import-PSSession -Session $Session -Prefix "Cloud" -DisableNameChecking:$true -AllowClobber:$true -CommandName $Commands | Out-Null
    }
    Connect-ExchangeOnline -Creds $Credentials
    inlineScript {
        function Add-JDMailboxPermission {
            param(
                [string]$Identity,
                [string]$SharedMailboxName
            )
            try {
                Add-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -ErrorAction stop | Out-Null
                Add-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop | Out-Null
                Write-Output "INFO: Successfully added $Identity to $SharedMailboxName"
            } catch {
                Write-Warning "Cannot add $Identity to $SharedMailboxName`r`n$_"
            }
        }
        function Remove-JDMailboxPermission {
            param(
                [string]$Identity,
                [string]$SharedMailboxName
            )
            try {
                Remove-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -Confirm:$False -ErrorAction stop -WarningAction ignore | Out-Null
                Remove-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop -WarningAction ignore  | Out-Null
                Write-Output "INFO: Successfully removed $Identity from $SharedMailboxName"
            } catch {
                Write-Warning "Cannot remove $Identity from $SharedMailboxName`r`n$_"
            }
        }
        function Sync-EXOResourceGroup {
            [CmdletBinding(SupportsShouldProcess=$true)]
            param(
                [string]$Prefix = 'SM-'
            )
            #Get All groups to process mailboxes for
            $MasterGroups = Get-CloudDistributionGroup -ResultSize Unlimited -Identity "$Prefix*"
            foreach ($Group in $MasterGroups) {
                #Remove prefix to get the mailbox name
                $MbxName = $Group.Name.Replace("$Prefix",'')
                $SharedMailboxName =  (Get-CloudMailbox -Identity $MbxName -ErrorAction ignore -WarningAction ignore).WindowsLiveID
                if ($SharedMailboxName) {
                    Write-Verbose -Message "Processing group $($Group.Name) and mailbox $SharedMailboxName"
                    #Get all users with explicit permissions on the mailbox
                    $SharedMailboxDelegates = Get-CloudMailboxPermission -Identity $SharedMailboxName -ErrorAction Stop -ResultSize Unlimited | Where-Object {$_.IsInherited -eq $false -and $_.User -ne "NT AUTHORITY\SELF" -and $_.User -notmatch 'S-\d-\d-\d+-\d+-\d+-\d+-\w+' -and $_.User -notlike "$Prefix*"} |  Select-Object @{Name="User";Expression={(Get-CloudUser -identity $_.User).WindowsLiveID }}
                    #Get all group members
                    $SharedMailboxMembers = Get-CloudDistributionGroupMember -Identity $Group.Identity -ResultSize Unlimited
                    #Remove users if group is empty
                    if (-not($SharedMailboxMembers) -and $SharedMailboxDelegates) {
                        Write-Warning "The group $Group is empty, will remove explicit permissions from $SharedMailboxName"
                        foreach ($user in $SharedMailboxDelegates.User) {
                            Remove-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
                        }
                        #Add users if no permissions are present
                    } elseif (-not($SharedMailboxDelegates)) {
                        foreach ($user in $SharedMailboxMembers.WindowsLiveID) {
                            Add-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
                        }
                        #Process removals and adds
                    } else {
                        #Compare the group with the users that have actual access
                        $Users = Compare-Object -ReferenceObject $SharedMailboxDelegates.User -DifferenceObject $SharedMailboxMembers.WindowsLiveID 

                        #Add users that are members of the group but do not have access to the shared mailbox
                        foreach ($user in ($users | Where-Object {$_.SideIndicator -eq "=>"})) {
                            Add-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
                        }
                        #Remove users that have access to the shared mailbox but are not members of the group
                        foreach ($user in ($users | Where-Object {$_.SideIndicator -eq "<="})) {
                            Remove-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
                        }
                    }
                } else {
                    Write-Warning "Could not find the mailbox $MbxName"
                }
            }
        }
        #Start Processing groups and mailboxes
        Sync-EXOResourceGroup -Prefix $Using:Prefix -Verbose
    }
}

5. Test the runbook by clicking the test button. You will be asked to save the runbook before you test. Hopefully your output will be as nice looking as mine. 🙂
2015-04-05_20-12-47
6. If all went good, you’re now ready to publish and schedule the runbook. I’m choosing to schedule mine to run every three hours. Depending on your script runtime, you might want to change this due to cost or other factors. (Remember your free 500 minutes!)
2015-04-05_20-12-472015-04-06_13-40-14
2015-04-06_13-45-53
2015-04-06_13-46-56
2015-04-06_13-47-44
2015-04-06_13-48-13
2015-04-06_13-48-39

My runbook is now published and scheduled, now it’s just to wait for the magic to happen. Hope this gives you an idea what Azure Automation can do for you!

Additional resources to get started can be found below:
Get Started with Azure Automation
Automating the Cloud with Azure Automation (MVA)

Until next time! 🙂

/Johan

7 thoughts on “Get rid of your Office 365 Scheduled tasks with Azure Automation!

  1. Colin Pazdzior

    Hello Johan,
    Thank You for this extremely useful code and walkthrough. I’ve just attempted to implement this in our environment, and the code runs and users are added and removed.

    Unfortunately, nested groups don’t seem to apply – for example, if I add an AD group containing users I’d like added to a shared mailbox to my SM-mailboxname group, all those users are removed from the shared mailbox if they were previously assigned, and they are not re-added.

    Any thoughts on this?

    Cheers,
    Colin

    Reply
    1. Johan Dahlbom Post author

      Hi Colin,

      Get-DistributionGroup does not support recursive group enumeration by default. If this is an important feature for you, I can make an example on how this could look like during the coming days.

      /Johan

      Reply
      1. Colin Pazdzior

        Hi Johan,
        That would be very helpful, both for us, and I expect for others! With that core bit of functionality added, I’ll bet that many would appreciate seeing it in the Azure runbook library, too.

        Cheers,
        Colin

      2. Colin Pazdzior

        Also, when running the task, about half the time it seems to error out with this: “The job action ‘Activate’ cannot be run, because the process stopped unexpectedly. The job action was attempted 3 times.” No other detail posted. Script is unmodified, and I’m using your suggested naming convention for groups – “SM-“. These are being matched to the alias of the shared mailboxes.

        Thoughts?

      3. Johan Dahlbom Post author

        Hi Colin,

        Sorry for the late reply.
        That sounds very strange, is it working properly if you run it from outside Azure Automation?

        /Johan

  2. Pingback: License your Office 365/Azure AD users with Azure Automation | Tailspintoys – 365lab.net

  3. Colin Pazdzior

    Modified script to work with nested groups. No longer requires flat group at top level.
    —————————————————————————————————————————-
    workflow Assign_Shared_Mailbox_Based_on_AD_Group {

    #Credentials to connect to Exchange Online
    $Credentials = Get-AutomationPSCredential -Name ‘EXOCreds’
    #Prefix to search for
    $Prefix = ‘SM-‘
    function Connect-ExchangeOnline {
    param (
    $Creds
    )
    #Clean up existing PowerShell Sessions
    Get-PSSession | Remove-PSSession
    #Connect to Exchange Online
    $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Creds -Authentication Basic -AllowRedirection
    $Commands = @(“Add-MailboxPermission”,”Add-RecipientPermission”,”Remove-RecipientPermission”,”Remove-MailboxPermission”,”Get-MailboxPermission”,”Get-User”,”Get-DistributionGroupMember”,”Get-DistributionGroup”,”Get-Mailbox”)
    Import-PSSession -Session $Session -Prefix “Cloud” -DisableNameChecking:$true -AllowClobber:$true -CommandName $Commands | Out-Null
    }
    Connect-ExchangeOnline -Creds $Credentials
    inlineScript {
    function Get-DistributionGroupMemberRecursive ($Identity) {
    $member_list = Get-CloudDistributionGroupMember -Identity $Identity
    foreach ($member in $member_list) {
    if ($member.RecipientType -like ‘*Group*’) {
    Get-DistributionGroupMemberRecursive -Identity $member.Identity
    } else {
    $member
    }
    }
    }
    function Add-JDMailboxPermission {
    param(
    [string]$Identity,
    [string]$SharedMailboxName
    )
    try {
    Add-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -ErrorAction stop | Out-Null
    Add-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop | Out-Null
    Write-Output “INFO: Successfully added $Identity to $SharedMailboxName”
    } catch {
    Write-Warning “Cannot add $Identity to $SharedMailboxName`r`n$_”
    }
    }
    function Remove-JDMailboxPermission {
    param(
    [string]$Identity,
    [string]$SharedMailboxName
    )
    try {
    Remove-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -Confirm:$False -ErrorAction stop -WarningAction ignore | Out-Null
    Remove-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop -WarningAction ignore | Out-Null
    Write-Output “INFO: Successfully removed $Identity from $SharedMailboxName”
    } catch {
    Write-Warning “Cannot remove $Identity from $SharedMailboxName`r`n$_”
    }
    }
    function Sync-EXOResourceGroup {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
    [string]$Prefix = ‘SM-‘
    )
    #Get All groups to process mailboxes for
    $MasterGroups = Get-CloudDistributionGroup -ResultSize Unlimited -Identity “$Prefix*”
    foreach ($Group in $MasterGroups) {
    #Remove prefix to get the mailbox name
    $MbxName = $Group.Name.Replace(“$Prefix”,”)
    $SharedMailboxName = (Get-CloudMailbox -Identity $MbxName -ErrorAction ignore -WarningAction ignore).WindowsLiveID
    if ($SharedMailboxName) {
    Write-Verbose -Message “Processing group $($Group.Name) and mailbox $SharedMailboxName”
    #Get all users with explicit permissions on the mailbox
    $SharedMailboxDelegates = Get-CloudMailboxPermission -Identity $SharedMailboxName -ErrorAction Stop -ResultSize Unlimited | Where-Object {$_.IsInherited -eq $false -and $_.User -ne “NT AUTHORITY\SELF” -and $_.User -notmatch ‘S-\d-\d-\d+-\d+-\d+-\d+-\w+’ -and $_.User -notlike “$Prefix*”} | Select-Object @{Name=”User”;Expression={(Get-CloudUser -identity $_.User).WindowsLiveID }}
    #Get all group members
    $SharedMailboxMembers = Get-DistributionGroupMemberRecursive -Identity $Group.Identity -ResultSize Unlimited | Sort-Object PrimarySmtpAddress -Unique
    #Remove users if group is empty
    if (-not($SharedMailboxMembers) -and $SharedMailboxDelegates) {
    Write-Warning “The group $Group is empty, will remove explicit permissions from $SharedMailboxName”
    foreach ($user in $SharedMailboxDelegates.User) {
    Remove-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
    }
    #Add users if no permissions are present
    } elseif (-not($SharedMailboxDelegates)) {
    foreach ($user in $SharedMailboxMembers.WindowsLiveID) {
    Add-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
    }
    #Process removals and adds
    } else {
    #Compare the group with the users that have actual access
    $Users = Compare-Object -ReferenceObject $SharedMailboxDelegates.User -DifferenceObject $SharedMailboxMembers.WindowsLiveID

    #Add users that are members of the group but do not have access to the shared mailbox
    foreach ($user in ($users | Where-Object {$_.SideIndicator -eq “=>”})) {
    Add-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
    }
    #Remove users that have access to the shared mailbox but are not members of the group
    foreach ($user in ($users | Where-Object {$_.SideIndicator -eq “<="})) {
    Remove-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
    }
    }
    } else {
    Write-Warning "Could not find the mailbox $MbxName"
    }
    }
    }
    #Start Processing groups and mailboxes
    Sync-EXOResourceGroup -Prefix $Using:Prefix -Verbose
    }
    }

    —————————————————————————————————————————-

    Reply

Leave a comment