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.
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.
3. Create a runbook in the automation account you created earlier, in my case I called it ‘Sync-EXOSharedMailboxPermissions’.
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.
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. 🙂
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!)
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
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
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
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
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?
Hi Colin,
Sorry for the late reply.
That sounds very strange, is it working properly if you run it from outside Azure Automation?
/Johan
Pingback: License your Office 365/Azure AD users with Azure Automation | Tailspintoys – 365lab.net
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
}
}
—————————————————————————————————————————-