PowerShell: Deploying Outlook ribbon customisations


The Outlook ribbon can be customised by users, and any such changes are stored in an Xml file in the user’s AppData folder (by default, this will be %LocalAppData%\Microsoft\Office – though this can be changed to roaming AppData using a registry key).  Sometimes a company may want to have specific functionality (or custom functionality) available to users via the ribbon, which would require the Xml file to be pushed out to all Outlook clients.  Pushing out an Xml file is not very difficult, but if the user has made other customisations to the ribbon (specific to their Outlook), then overwriting the ribbon Xml file will lose the user’s customisations.

To ensure that any user’s customisations aren’t lost, the new ribbon Xml needs to be merged with any that already exists.  Fortunately, PowerShell (and .Net) make dealing with Xml easy, and it is quite easy to write a script to combine two ribbon files.  Below is a script that can do exactly this, and can also remove any ribbon controls that have a particular OnAction attribute (it would be quite easy to change this to check for Id or another attribute).

The easiest way to use the script would be to start with an unmodified Outlook (i.e. no ribbon customisations) and then make the changes you want to deploy.  Once done, close Outlook and then copy the olkexplorer.officeUI file to another location.  This file is then used by the script to merge changes into another customisation file.  It will also work where the user doesn’t have any customisations.  So, you could call the script like this:

.\Add-OutlookRibbonControls.ps1 -XmlToAdd \\networkshare\customisations.xml

The above will combine the customisations.xml ribbon file with the user’s olkexplorer.officeUI file.  You can update another file by specifying it using the -RibbonXmlFile parameter.

The script was based on the information found here: https://msdn.microsoft.com/en-us/library/office/ee704589(v=office.14).aspx

Download: Add-OutlookRibbonsControls.ps1

Code:

#
# Add-OutlookRibbonControls.ps1
#
# By David Barrett, Microsoft Ltd. 2017. Use at your own risk. No warranties are given.
#
# DISCLAIMER:
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
#
# Please see https://msdn.microsoft.com/en-us/library/office/ee704589(v=office.14).aspx for an overview of what we are doing
# Note that this script has only been tested against Outlook 2010


[CmdletBinding()]
param(
 [Parameter(Position=0,Mandatory=$False,HelpMessage="Specifies the Xml containing the ribbon controls to add. This should be the full Xml as exported")]
 [string]$XmlToAdd,

 [Parameter(Position=0,Mandatory=$False,HelpMessage="Deletes any buttons that have matching onAction payloads (use an array to delete multiple buttons)")]
 $DeleteOnAction = "",

 [Parameter(Mandatory=$False,HelpMessage="The ribbon Xml file to update")]
 [string]$RibbonXmlFile = "$env:userprofile\AppData\local\microsoft\office\olkexplorer.officeUI"
)

# We set the Xml to a complete but empty ribbon UI, in case we don't have an existing one to update (in which case we just want the new Xml)
$ribbonXml = '<mso:customUI xmlns:x1="http://schemas.microsoft.com/office/2009/07/customui/macro" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui"><mso:ribbon><mso:qat/></mso:ribbon></mso:customUI>'

if ([System.IO.File]::Exists($RibbonXmlFile))
{
 # We have existing ribbon customisations, so load those
 $ribbonXml = Get-Content $RibbonXmlFile
}

$ribbonXmlDoc = [Xml]$ribbonXml
$xmlToAddDoc = [Xml]$xmlToAdd

function MergeNodes($sourceNode, $targetNode)
{
 Write-Verbose "Merging $($sourceNode.Name) and $($targetNode.Name)"
 if ($sourceNode.HasChildNodes)
 {
 ForEach ($node in $sourceNode.ChildNodes)
 {
 # Do we already have this node in the target Xml node?
 $targetChildNode = $null
 if ($targetNode.HasChildNodes)
 {
 ForEach ($tNode in $targetNode.ChildNodes)
 {
 if ($tNode.Name -eq $node.Name)
 {
 $targetChildNode = $tNode 
 break
 }
 }
 }
 if ($targetChildNode -eq $null)
 {
 $targetChildNode = $targetNode.AppendChild($ribbonXmlDoc.ImportNode($node, $False))
 }
 MergeNodes $node $targetChildNode
 }
 }
}

# Merge the Xml
Write-Verbose "Merging Xml"
MergeNodes $xmlToAddDoc.DocumentElement $ribbonXmlDoc.DocumentElement


function DeleteButtonsOnAction($ParentNode, $OnAction)
{
 Write-Verbose "Processing $($ParentNode.Name)"

 if ($ParentNode.LocalName.ToLower().Equals("button"))
 {
 # This is a button, check the onAction attribute
 Write-Verbose "Button found $($ParentNode.Name)"
 if ($ParentNode.Attributes.GetNamedItem('onAction').Value.Equals($OnAction))
 {
 # This node is to be deleted
 Write-Verbose "$($ParentNode.Name) matches onAction, deleting"
 $ParentNode.ParentNode.RemoveChild($ParentNode) | Out-Null
 return
 }
 }
 else
 {
 if ($ParentNode.HasChildNodes)
 {
 Write-Verbose "$($ParentNode.Name) has child nodes"
 ForEach ($node in $ParentNode.ChildNodes)
 {
 DeleteButtonsOnAction $node $OnAction
 }
 }
 }
}

# Check for any deletions
if ($DeleteOnAction)
{
 # We have some deletions for onAction attributes (we delete any button that has a matching onAction attribute)
 ForEach ($onActionValue in $DeleteOnAction)
 {
 DeleteButtonsOnAction $ribbonXmlDoc.DocumentElement $onActionValue 
 }
}

# Now save the updated file
$ribbonXmlDoc.Save($RibbonXmlFile)
Comments (0)

Skip to main content