Tuesday, April 30, 2019

Add Ribbon Buttons in Outlook


The goal was to place buttons to certain positions in users' Outlook 2016 ribbons.

The code below is a modified version of this blog post.
https://blogs.msdn.microsoft.com/emeamsgdev/2017/02/21/powershell-deploying-outlook-ribbon-customisations/

Outlook ribbon buttons are controlled in the users' roaming or local profile folder locations e.g. %APPDATA%\Microsoft\Office   (depending on the environment configuration)

An example file name is olkexplorer.officeUI.  When a user customises their ribbon, it is written to XML in this file.

The script below enables XML code segments to be either, injected into existing files, or written into new ones where they do not already exist.

In this example an Report Spam button that exists on another ribbon tab is created on the home and message ribbons for easier access.

#

# Add-OutlookReportSpamButton.ps1
#  
# Original concept obtained from https://blogs.msdn.microsoft.com/emeamsgdev/2017/02/21/powershell-deploying-outlook-ribbon-customisations/

#Set Values - the files will receive the XML from the InjectionStings array. i.e. file1 gets the first injection string and so on.
$TargetFiles = @("olkexplorer.officeUI", "olkmailread.officeUI")

$InjectionStrings = New-Object System.Collections.Generic.List[System.Object]
$InjectionStrings.Add("<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/><mso:tabs><mso:tab idQ=""mso:TabMail""><mso:group id=""mso_c1.41340A1"" label=""Spam"" insertBeforeQ=""mso:GroupMailDelete"" autoScale=""true""><mso:control idQ=""x1:ui_main_ribbon_gateway_report_spam_phishing_split_button"" imageMso=""GreenBall"" visible=""true""/></mso:group></mso:tab><mso:tab idQ=""x1:adxRibbonTab_c4dc3496375345748858382774b12030""/></mso:tabs></mso:ribbon></mso:customUI>")
$InjectionStrings.Add("<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/><mso:tabs><mso:tab idQ=""mso:TabReadMessage""><mso:group id=""mso_c2.13FA7E9B"" label=""Spam"" insertBeforeQ=""mso:GroupRespond"" autoScale=""true""><mso:control idQ=""x1:adxRibbonMenu_eee1842485324bc18a9a64adbd2147fd"" visible=""true""/></mso:group></mso:tab></mso:tabs></mso:ribbon></mso:customUI>")

# xmlns:x1="MimecastServicesForOutlook.AddinModule" had to be added to the $ribbonXml to create the correct blank file.

# 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="MimecastServicesForOutlook.AddinModule" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui"><mso:ribbon><mso:qat/></mso:ribbon></mso:customUI>'
$ribbonXml = "<mso:customUI xmlns:x1=""MimecastServicesForOutlook.AddinModule"" xmlns:mso=""http://schemas.microsoft.com/office/2009/07/customui""><mso:ribbon><mso:qat/></mso:ribbon></mso:customUI>"



# Set Functions
function MergeNodes($sourceNode, $targetNode)
{
Write-Verbose "Merging $($sourceNode.Name) and $($targetNode.Name)"

    if ($sourceNode.Name -eq "mso:customUI")
    {
    # Need to copy namespaces from this element
    ForEach ($attribute in $sourceNode.Attributes)
        {
        if ( $attribute.Name.StartsWith("xmlns:") )
            {
            # Check whether this namespace already exists in target node
            $namespaceExists = $false
            ForEach ($targetAttribute in $targetNode.Attributes)
                {
                if ($targetAttribute.Name -eq $attribute.Name)
                {
                $namespaceExists = $true
                Write-Verbose "Namespace $($attribute.Name) already declared in target node"
                }
                }
            if (!$namespaceExists)
                {
                Write-Verbose "Adding namespace attribute $($attribute.Name): $($attribute.Value)"
                $namespaceAttribute = $ribbonXmlDoc.CreateAttribute($attribute.Name)
                $namespaceAttribute.Value = $attribute.Value
                [void]$targetNode.Attributes.Append($namespaceAttribute)
                }
            }
        }
     }

    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) -and ($tNode.NamespaceURI -eq $node.NamespaceURI) )
                        {
                        # Check idQ (or id) value
                        $idAttribute = "idQ"
                        if ($tNode.Name.Equals("mso:group")) { $idAttribute = "id" }
                        Write-Verbose "$($tNode.Name): Target $idAttribute = $($tNode.Attributes.ItemOf($idAttribute).Value), Source $idAttribute = $($Node.Attributes.ItemOf($idAttribute).Value)"

                        if ($tNode.Attributes.ItemOf($idAttribute).Value -eq $Node.Attributes.ItemOf($idAttribute).Value)
                            {
                            $targetChildNode = $tNode 
                            break
                            }
                        }
                    }
                }
            if ($targetChildNode -eq $null)
            {
            Write-Verbose "$($node.Name) not found in target, appending"
            Write-Verbose "$($node.Name) namespace is $($node.NameSpaceURI)"
            $targetChildNode = $targetNode.AppendChild($ribbonXmlDoc.ImportNode($node, $False))
            Write-Verbose "Imported $($targetNode.Name) namespace is $($targetNode.NameSpaceURI)"
            #$targetChildNode.NameSpaceURI = $node.NameSpaceURI
            }
            MergeNodes $node $targetChildNode
            }
        }
}



# Actions
$RunCount = -1
foreach ($TargetFile in $TargetFiles)
    {
    $RunCount +=1
    $RibbonXmlFile = "$env:userprofile\AppData\Roaming\microsoft\office\"+$TargetFile
    $XmlToAdd = $InjectionStrings[$RunCount]
    
    $xmlToAddDoc = [Xml]$xmlToAdd
    
    if ([System.IO.File]::Exists($RibbonXmlFile))
        {
        # We have existing ribbon customisations, so load those
        $ribbonXml = Get-Content $RibbonXmlFile -Encoding UTF8
        }
    $ribbonXmlDoc = [Xml]$ribbonXml
       
    #Do it!
    MergeNodes $xmlToAddDoc.DocumentElement $ribbonXmlDoc.DocumentElement
    
    # Now save the updated file
    $ribbonXmlDoc.Save($RibbonXmlFile)
    }