Thursday, September 19, 2013

Assign a Move-To-Archive tag to a specific mailbox folder in Exchange Online/Office 365

You have an Exchange Online environment, and want to apply a move-to-archive tag to a specific folder in some user mailboxes.

GIVENS:
- Retention Policy tags do not allow a Move To Archive action.
- Default Policy tags apply to an entire mailbox.
- Personal tags are not administratively set or enforced.

SCENARIO:
- A client of mine required that all items in a specific folder be moved to the archive after a specifc time period, and there should be no other automatic archive or retention actions for any other folder.
- The users at this client are unlikely to ever reconfigure or explore their archive settings and cannot be asked to apply any personal tags themselves.
- There are too many users to expect that an IT person would manually apply the personal tag for each user.
- It is OK that the users have the option to change the archive settings on their mailbox; given the above there is no worry that they will do so.

I found and reviewed two very helpful pages that almost solved this for me, but didn't quite bring the runner all the way home. (I can't emphasize enough though, there is no way I could have done this without them.)

My script, with the required changes -
#Connect to Exchange Online for Remote PowerShell
$cred = Get-Credential
 
$Office365 = @{
configurationName = "Microsoft.Exchange"
connectionUri = "
https://ps.outlook.com/powershell"
credential = $cred
Authentication = "Basic"
AllowRedirection = $true
}
 
$s = new-pssession @Office365
Import-PSSession $s


##########################################################################################################################################################################################################################################
# The script requires the EWS managed API, which can be downloaded here:
#
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1
# This also requires PowerShell 2.0
# Make sure the Import-Module command below matches the DLL location of the API.
# This path must match the install location of the EWS managed API. Change it if needed.
[string]$info = "White" # Color for informational messages
[string]$warning = "Yellow" # Color for warning messages
[string]$error = "Red" # Color for error messages
[string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File
function StampPolicyOnFolder($MailboxName)
{
Write-host "Stamping Policy on folder for Mailbox Name:" $MailboxName -foregroundcolor $info
Add-Content $LogFile ("Stamping Policy on folder for Mailbox Name:" + $MailboxName)



#Change the user to Impersonate
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName);



#Search for the folder you want to stamp the property on
$oFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName)

$oFindFolderResults = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$oSearchFilter,$oFolderView)

if ($oFindFolderResults.TotalCount -eq 0)
{
Write-host "Folder does not exist in Mailbox:" $MailboxName -foregroundcolor $warning
Add-Content $LogFile ("Folder does not exist in Mailbox:" + $MailboxName)
}
else
{
Write-host "Folder found in Mailbox:" $MailboxName -foregroundcolor $info
Add-Content $LogFile ("Folder found in Mailbox: " + $MailboxName)



#PR_ARCHIVE_TAG 0x3018 - We use the PR_ARCHIVE_TAG instead of the PR_POLICY_TAG
$ArchiveTag = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3018, Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);



#PR_RETENTION_FLAGS 0x301D
$RetentionFlags = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);



#PR_ARCHIVE_PERIOD 0x301E - We use the PR_ARCHIVE_PERIOD instead of the PR_RETENTION_PERIOD
$ArchivePeriod = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301E,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);



#Change the GUID based on your policy tag
# The Guid of our 9-month Move To Archive retention tag (in our O365 tenant) is xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ArchiveTagGUID = new-Object Guid("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}");



#Bind to the folder found
$oFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$oFindFolderResults.Folders[0].Id)
$oFolder.SetExtendedProperty($ArchiveTag, $ArchiveTagGUID.ToByteArray())



#Same as that on the policy - 16 specifies that this is a ExplictArchiveTag
$oFolder.SetExtendedProperty($RetentionFlags, 16)



#Specify the days until the tag's action occurs (i.e., the retention period before the items are moved to the archive)
$oFolder.SetExtendedProperty($ArchivePeriod, 275)
$oFolder.Update()



Write-host "Retention policy stamped!" -foregroundcolor $info
Add-Content $LogFile ("Retention policy stamped!")
}



Start-ManagedFolderAssistant -Identity $WindowsEmailAddress

$service.ImpersonatedUserId = $null
}



#Change the name of the folder. This is the folder the properties will be stamped on.
$FolderName = "Deleted Items"

Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"

$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013)


# Set the Credentials
#$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("UserName","Password","Domain")
$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials($cred)



# Change the URL to point to your cas server
#$service.Url= new-object Uri(
http://YOUR-CAS-SERVER/EWS/Exchange.asmx) -- presumably this line works for On-Prem Exchange, but we're using Exchange Online/O365
$service.Url= new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")


# Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above
$UseAutoDiscover = $false



#Read data from the UserAccounts.txt.
#This file must exist in the same location as the script.
import-csv UserAccounts.txt | foreach-object {
$WindowsEmailAddress = $_.WindowsEmailAddress.ToString()
if ($UseAutoDiscover -eq $true) {



Write-host "Autodiscovering.." -foregroundcolor $info
$UseAutoDiscover = $false
$service.AutodiscoverUrl($WindowsEmailAddress)

Write-host "Autodiscovering Done!" -foregroundcolor $info
Write-host "EWS URL set to :" $service.Url -foregroundcolor $info
}



#To catch the Exceptions generated
trap [System.Exception]
{
Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $error;
Add-Content $LogFile ("Error: " + $_.Exception.Message);
continue;
}



StampPolicyOnFolder($WindowsEmailAddress)
}



Remove-Pssession $s

NOTE: The 'Start-ManagedFolderAssistant' function will force the MFA service on the server to run immediately against the specified mailbox, in order to apply the changes immediately, instead of whenever the service would get around to that mailbox through the course of its routine operation.

When I tested my code that line threw an error that it was not recognized as a valid commandlet. I believe that was due to the context I was running the script in, and I either need to run the script from my MSOL PS prompt, or relocate the command elsewhere in the script above.

When I ran the script above though, and then connected to Exchange Online in an MSOL PS cmd window and ran the Start-ManagedFolderAssistant command there, afterward I closed & restarted Outlook and the changes to my mailbox WERE evident.


PS: Sorry for the poor formatting, I'm having ridiculous trouble trying to clean the results up from pasting the code.

PPS: Holy Cow - I knew it had been a long time, but 2 YEARS since I updated this blog? Wow, Facebook really is wiping out the rest of the web, starting with me....