250 Hello

Random Musings on Exchange and Virtualization

Exchange Scripting Agent - The Power Of Script

Exchange Scripting Agent - The Power Of Script

  • Comments 17
  • Likes

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:

  • Disable Outlook Anywhere for new users by default
  • Disable POP/IMAP for new users by default
  • Enable Single Item Recovery for new users by default
  • Enable External Calendar Processing  for new users by default

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?

 

Functionality Breakdown

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:

  1. ProvisionDefaultProperties   This API can be used to set values of properties on objects when they're created. When you set a value, that value is returned to the cmdlet, and the cmdlet sets the value on the property. You can fill in values on properties if the user didn't specify a value, or you can override the value specified by the user. This API respects the values set by higher priority agents. The Scripting Agent cmdlet extension agent won't overwrite the values set by higher priority agents.
  2. UpdateAffectedIConfigurable   This API can be used to set values of properties on objects after all other processing has been completed, but the Validate API hasn't yet been called. This API respects the values set by higher priority agents. The Scripting Agent cmdlet extension agent won't overwrite the values set by higher priority agents.
  3. Validate   This API can be used to validate the values on an object's properties that are about to be set by the cmdlet. This API is called just before a cmdlet writes any data. You can configure validation checks that allow a cmdlet to either succeed or fail. If a cmdlet passes the validation checks in this API, the cmdlet is allowed to write the data. If the cmdlet fails the validation checks, it returns any errors defined in this API.
  4. OnComplete   This API is used after all cmdlet processing is complete. It can be used to perform post-processing tasks, such as writing data to an external database.

 

Deployment Considerations

Here are some items to consider before going live with the feature:

  • The  Scripting cmdlet extension agent is disabled by default. You must manually enable it
  • The configuration file must be called ScriptingAgentConfig.xml
  • You are responsible for copying the ScriptingAgentConfig.xml to every server
  • You are responsible for ensuring that the correct version of ScriptingAgentConfig.xml is copied to every server.  Exchange does not replicate or manage this file at all.
  • Test changes to the file in a lab prior to deploying to production
  • Management workstations will also need a copy of the ScriptingAgentConfig.xml file.  Again you must manage the deployment of this file.
  • Exchange installer expects the scripting file to be present when installing a new Exchange server or installing management tools.  Do not disable the Scripting Agent just to install a server/admin workstation.  You can manually create the necessary directory path and copy the ScriptingAgentConfig.xml to the \Bin\CmdletExtensionAgents folder.
  • By default, the Scripting Agent cmdlet extension agent runs after every other agent, with the exception of the Admin Audit Log agent
  • You will likely experience object not found errors when multiple domain controllers are present in an AD site.  This is because one part of the Scripting Agent script will fire against DC-1 and then the next part against DC-2.  This will result in a failure on DC-2 since the object has not yet replicated to it.  To deal with this we will pin a domain controller.  Note that this is not hardcoding the DC.  Hardcoding is never  a great idea since if that one DC fails then we will run into issues.  See below for more details on this and how to deal with it.
  • Since the file is XML based, Notepad will be able to edit it but you probably find it easier to edit using an XML aware tool such as XML Notepad.  There are other editors out there – the weapon of choice is yours!
  • Be aware of the XML sensitivity of the XML file.  Please see Michel’s post here for more details.
  • Carefully consider the cmdlets that you will fire actions on.  For example do not include just  the New-Mailbox cmdlet to take your specific actions.  What about mailbox enabling existing accounts?  They will be missed as the Enable-Mailbox cmdlet was not also added.  This would lead to inconsistent behaviours.

Michel also has a great end to end solution for enabling archive mailboxes using the Scripting Agent – check that out too.

Enabling And Deploying Scripting Agent

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:

Check Status Of CmdletExtensionAgents

The sample ScriptingAgentConfig.xml.sample file is present and is dated the 21st July 2009.

Exchange Scripting Agent - Sample File

 

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.

Exchange Scripting Agent - Custom File Deployed

 

We can then enable the scripting agent using PowerShell Enable-CmdletExtensionAgent  and check that the Scripting Agent’s status is now enabled.

Enabling Exchange Scripting Agent

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!

 

Testing The Scripting Agent

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:

Creating Test Mailbox To Verity Scripting Agent - 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:

Creating Test Mailbox To Verity Scripting Agent - Exchange Management Console

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.

Noting Up Sleeves - Exchange Management Console Cmdlet Audit Log

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!

 Verifying That SingleItemRecovery Was Enabled For The Test 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.

Mailbox Created Prior To Enabling Scripting Agent - SingleItemRecovery Was NOT Enabled Automatically

 

Dealing With Multiple Domain Controllers

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;

  • Uninstall all domain controllers but one.  Yes – I’m joking but it would fix the Scripting Agent issue
  • Hardcode a single domain controller to all cmdlets.  This is bad on multiple levels.  If that one DC goes offline or us decommissioned all of the Scripting Agent tasks will fail.  The other issue is for larger enterprises, it would not be efficient for every machine to target a single DC thus ignoring AD site boundaries. 
  • Add a script section that determines the site, domain controllers in the site and then choose one.  This is not ideal as Exchange already does the DC selection process so the code is a tad superfluous.  Additionally how do you then deal with your chosen DC being unavailable?  That is yet more code to trap and handle the error.
  • Workout what DC Exchange used automatically, then persist that for the duration of the session.  This allows for local site awareness, and should the DC fail or be decommissioned other DCs will be automatically chosen. 

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

 

 

Sample Scripting Agent Files

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. 

 

Example 1

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>

 

Example 2

This example disables POP and IMAP access to newly created mailboxes

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-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>

 

Example 3

The following example Disables Outlook Anywhere


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-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

 

Can You Help Us?  -- Yes !

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.





Comments
  • 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

  • Is this possible in exchange 2007 ?

  • No - as mentioned in the first line it was Exchange 2010 that introduced the feature.

    Cheers,
    Rhoderick

  • Hi Rhoderick,

    Thanks for this article.

    I have question:

    I have Exchange Server 2013 and I created the actions (in XML-file) for New-Mailbox and Enable-Mailbox. It is works when I create user (or enable) through PowerShell. But it not working through ECP.

    Where to look a solutions to this problem?

    Thanks,
    Evgeny.

  • I believe Jeremy is correct - $readOnlyIConfigurable.originatingserver is invalid and in my testing also does not work. We have over 14 DC's company wide and I constantly run into issues unless I SPECIFICALLY identity a DC in the scripting. My suggestion is to specify the DC, one that won't go away - like your PDC enumerator.

  • This is odd. If I put this before the so it reads:




    That works.. If I pipe the $DC variable to a log file there's data in it.

    However, I can't select multiple "existing" accounts via the EMC and enable all of them. I get null value errors. However, if I select one user account at a time via the EMC GUI and enable them for mail, it works, each and every time. I've tested this dozens of times. More than one account selected for "enable-mailbox" = fails, single account = successful.

    At least I have $readOnlyIConfigurable.originatingserver working. It wasn't populating data until I punched that additional "validate" entry in the script.

    No idea why I can't run enable-mailbox on more than one account at a time.

  • This website is messed up. I can't even post the code I used to fix the $DC variable.


    I pulled the ending carrots off to see if the data will post.

  • Not sure what to tell you guys. I'm trying to provide this fix but this website is ridiculous.

    Does the author have an email address or another way to contact? This website needs corrections at many levels.

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
Post Comment Fixer