Exchange 2010 introduced a very interesting feature – the Scripting Agent. The intent for this component is to provide extensibility to the base management tools and ensure consistency for the execution of cmdlets in the environment. The feature is not enabled by default and you must manually enable it if you want to leverage the Scripting Agent.
If you are looking for a way to set default options on mailboxes that do not inherit that specific configuration item from the database or server level, then this is for you!
As TechNet describes: when you enable the Scripting Agent cmdlet extension agent, the agent is called every time a cmdlet is run on a server running Exchange 2010. This includes not only cmdlets run directly by you in the Exchange Management Shell, but also cmdlets run by Exchange services, the Exchange Management Console (EMC), and the Exchange Control Panel (ECP). We strongly recommend that you test your scripts and any changes you make to the configuration file, before you copy your updated configuration file to your Exchange 2010 servers and enable the Scripting Agent cmdlet extension agent.
To summarise -- Every time an Exchange cmdlet is executed the list of cmdlets and actions contained within the Scripting Agent configuration is checked. If there are actions defined for the cmdlet in the Scripting Agent configuration then those actions are automagically added to the cmdlet being executed prior to the actual command doing anything.
This means that the Scripting Agent is a great tool to ensure that certain options are set in the environment. For example this can be used to:
The last one will be of interest to Blackberry users who run into the issue where they need to allow Exchange 2010 to process external meeting messages. You can run the below to enable for one mailbox:
Set-CalendarProcessing -ProcessExternalMeetingMessages $True
Or the below to change all the mailboxes on a given server.
Get-Mailbox -Server "servername” -ResultSize Unlimited | Set-CalendarProcessing -ProcessExternalMeetingMessages $True
But this is all after the fact. Some customers have implemented scheduled scripts to go back and re-set such configuration items but that still leaves a period of time when the configuration is not what it should be. The Scripting Agent can fix you up here! Additional filtering examples for PowerShell are in this post.
How does this good stuff all work then?
The purpose of the scripting agent is to insert your own custom values and logic into the Exchange workflow. This applies to both Exchange Management Console, Exchange Management Shell actions and Exchange Control Panel. The cmdlets underpin actions taken in the GUI, and every time an Exchange cmdlet is called the Scripting Agent cmdlet extension agent is called. This agent check to see if there are any additional actions to be added to the cmdlet.
Note that the Scripting Agent is only for Exchange cmdlets, it will not fire on Get cmdlets and does not exist on the Exchange Edge role.
There is a sample Scripting Agent file on a default Exchange 2010 installation. This file can be found in the Exchange Installation Folder\Bin\CmdletExtensionAgents folder. By default this is:
C:\Program Files\Microsoft\Exchange Server\V14\Bin\CmdletExtensionAgents
The file is called ScriptingAgentConfig.xml.sample and to allow Exchange to use it, the file must be renamed to remove the .sample suffix. For those who had to endure it, it is the same concept as “LMHosts.sam” – but let’s not go down the #PRE and #DOM silly road again….
There are four APIs that are available and are called in the following order:
Validate
Here are some items to consider before going live with the feature:
Michel also has a great end to end solution for enabling archive mailboxes using the Scripting Agent – check that out too.
Let’s look at an example of enabling the Scripting Agent and a sample configuration file that overcomes some of the common issues with writing to multiple domain controllers.
In the below screen shot the Scripting Agent is still in its default configuration and is disabled:
The sample ScriptingAgentConfig.xml.sample file is present and is dated the 21st July 2009.
Copy the ScriptingAgentConfig.xml to all Exchange servers, and administrator workstations. Ensure that you have a process to keep the files in lock step else you will get varying results.
We can then enable the scripting agent using PowerShell Enable-CmdletExtensionAgent and check that the Scripting Agent’s status is now enabled.
For reference, the above command is:
Enable-CmdletExtensionAgent "Scripting Agent"
Now that the Scripting Agent is enabled and the same ScriptingAgentConfig.xml copied to all machines, we can start to test it out!
Let’s test out the Scripting Agent. To do this we will make a mailbox using the Exchange Management Shell and then using the Exchange Management Console. The custom configuration file that was deployed will enable Single Item Recovery for all newly created mailboxes. Please see the end of this post of the contents of the XML.
First up, creating a new mailbox (SA-Test-1) using Exchange Management Shell:
Secondly using the Exchange 2010 Management Console to create mailbox SA-Test-1. For reference only the completion screen is shown here, so that we can see the cmdlet properties that were specified:
If you look at the details of the cmdlet Executed in the above screenshot there is no mention of SingleItemRecovery. And the same is also true when examining the contents of the Exchange Management Console log file.
As you can see, when creating these mailboxes, there has been absolutely no reference to Single Item Recovery. But let’s go and check the properties of these newly created mailboxes!
You can see that both accounts have SingleItemRecoveryEnabled set to $True, which means the feature is enabled despite not specifying this in the New-Mailbox cmdlet.
Round of applause here!
For comparison, user account (User-1) that was created months ago does not have this feature enabled.
When multiple domain controllers are present in the same AD site, then some of the commands will fire against DC-1, some against DC-2 and so on. You will get errors along the lines of:
The cmdlet extensionagent with the index 5 has thrown an exception in OnComplete. The Exception is: Microsoft.exchange.provisioning.provisioningexception. Scriptingagent exception thrown while invoking scriptlet for OnComplete API. The operation couldn't be performed because object 'objectname' couldn't be found
As an added bonus you will also get errors from ms.exchange.provisionin.provisioninglayer.oncomplete.
There are a few ways around this;
I typically use option three, and store the DC that was automatically selected in a variable that can then be used consistently throughout the script. This would look like:
$DC = [string]($readOnlyIConfigurable.originatingserver)
Thus when running the Set-Mailbox cmdlet we will then specify that domain controller using the DomainController parameter:
Set-Mailbox -Identity $Newmailbox -SingleItemRecoveryEnabled $True -DomainController $DC.domain.com
Since seeing sample files makes it easier to understand this feature, and some folks will be able to just use the examples below directly there are a few included. As always note that any and all sample code follows the terms of use as described here.
My lab servers are a tad slow, so the Start-Sleep is in there for my purposes, you can remove or decrease the timeout.
This example enables SingleItemRecovery and also sets custom default calendar permissions for the Enable-Mailbox and New-Mailbox cmdlets.
Be sure to change domain.com to match your domain suffix.
<?xml version="1.0" encoding="utf-8" ?><Configuration version="1.0"> <Feature Name="NewMailbox" Cmdlets="new-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $newmailbox = $provisioningHandler.UserSpecifiedParameters["Name"] Set-mailbox -Identity $Newmailbox -SingleItemRecoveryEnabled $True -DomainController $DC.domain.com
$AccessRights = "Reviewer" $mailbox = Get-Mailbox $newmailbox $calendar = (($mailbox.SamAccountName)+ ":\" + (Get-MailboxFolderStatistics -Identity $mailbox.SamAccountName -FolderScope Calendar | Select-Object -First 1).Name) Set-MailboxFolderPermission -User "Default" -AccessRights $AccessRights -Identity $calendar -DomainController $DC.domain.com } </ApiCall> </Feature> <Feature Name="EnableMailbox" Cmdlets="enable-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $newmailbox = $provisioningHandler.UserSpecifiedParameters["Identity"] set-mailbox -Identity "$newmailbox" -SingleItemRecoveryEnabled $True -DomainController $DC.domain.com
$AccessRights = "Reviewer" $mailbox = Get-Mailbox -identity "$newmailbox" $calendar = (($mailbox.SamAccountName)+ ":\" + (Get-MailboxFolderStatistics -Identity $mailbox.SamAccountName -FolderScope Calendar | Select-Object -First 1).Name) Set-MailboxFolderPermission -User "Default" -AccessRights $AccessRights -Identity $calendar -DomainController $DC.domain.com } </ApiCall> </Feature></Configuration>
This example disables POP and IMAP access to newly created mailboxes
<?xml version="1.0" encoding="utf-8" ?><Configuration version="1.0"> <Feature Name="NewMailbox" Cmdlets="New-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $NewMailbox = $provisioningHandler.UserSpecifiedParameters["Name"] Set-CASMailbox -Identity $NewMailbox -ImapEnabled $false -POPEnabled $false -DomainController $DC.domain.com } </ApiCall> </Feature> <Feature Name="EnableMailbox" Cmdlets="Enable-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $NewMailbox = $provisioningHandler.UserSpecifiedParameters["Name"] Set-CASMailbox -Identity $NewMailbox -ImapEnabled $false -POPEnabled $false -DomainController $DC.domain.com } </ApiCall> </Feature></Configuration>
The following example Disables Outlook Anywhere
<?xml version="1.0" encoding="utf-8" ?><Configuration version="1.0"> <Feature Name="NewMailbox" Cmdlets="New-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $NewMailbox = $provisioningHandler.UserSpecifiedParameters["Name"] Set-CASMailbox -Identity $NewMailbox -MAPIBlockOutlookRpcHttp $True -DomainController $DC.domain.com } </ApiCall> </Feature> <Feature Name="EnableMailbox" Cmdlets="Enable-Mailbox"> <ApiCall Name="OnComplete"> if($succeeded) { Start-sleep 20 $DC = [string]($readOnlyIConfigurable.originatingserver) $NewMailbox = $provisioningHandler.UserSpecifiedParameters["Name"] Set-CASMailbox -Identity $NewMailbox -MAPIBlockOutlookRpcHttp $True -DomainController $DC.domain.com } </ApiCall> </Feature></Configuration>
Please feel free to leave suggestions in the comments for other great use cases for this feature.
Cheers,
Rhoderick
If you would like to have Microsoft Premier Field Engineering (PFE) visit your company and assist with the topic(s) presented in this blog post, then please contact your Microsoft Premier Technical Account Manager (TAM) for more information on scheduling and our varied offerings!
If you are not currently benefiting from Microsoft Premier support and you’d like more information about Premier, please email the appropriate contact below, and tell them you how you got introduced!
US
Canada
For all other areas please use the US contact point.
Thanks
Hi Rhoderick, thank you for this great article. Could you please provide some examples for the ProvisionDefaultProperties API? I want to achieve, that new contacts will be created in a predifined Organizational Unit. This doesn´t work: if($provisioningHandler.UserSpecifiedParameters["OrganizationalUnit"] -eq "Test.lab/Users") { $user = new-object -type Microsoft.Exchange.Data.Directory.Management.MailContact; $user.OrganizationalUnit = " OU=UsersandGroups,OU=Test,DC=Test,DC=lab " new-object -type Microsoft.Exchange.Data.Directory.Management.MailContact -argumentlist $user } I´m getting the error 'OrganizationalUnit' is a ReadOnly property. I would be really appreciated for any help. Bye, Felix
That should be dooable Felix. I'll need a bit time, since all of this is something I do on the side and it's a bit hectic with the new FY start and all that :) Cheers, Rhoderick
I played with this over the weekend Felix. Same result. Will run down the PM for this feature to see what I can get for you. We might be looking at a cmdlet that is not wired up for the scripting agent, but that is something he/she will have to confirm. Cheers, Rhoderick
Hi Rhoderick, thank you so much. Regards, Felix
If I create user and mailbox through exchange, everything works ok. If I create user from AD then create MB after the user account, all the commandlets fail : "Cmdlet failed. Cmdlet Set-CASMailbox, parameters " . Any reasons as to why
Hi Rhoderick. I did some testing and identified that $readOnlyIConfigurable isn't actually even an available variable to the scripting agent. That means that this method for identifying the domain controller will never work. On that note there are quite a few threads in other forums where people use the OriginatingServer attribute from a get-mailbox loop until it finds the mailbox. Even that doesn't seem to work. On another note, it dawned on me - this thing is very dangerous in that somebody could adversely set settings on one or more accounts that are not the newly created one at all if they manage to get the identity wrong in their set cmdlet. Microsoft really needs to rethink this feature. Thanks, Jeremy
Hi Jeremy, Can I ask which API call you were using? In terms of updating the settings on other accounts, don't add set-mailbox to the XML. if it is not to be net new mailboxes then new-mailbox or enable-mailbox should be in there for example. Cheers, Rhoderick