Adding Individual Office 365 Product to Existing Installation

Share on:

I was having a discussion last week about how if you want to add a new product to an existing Office 365 ProPlus installation (such as adding Microsoft Access after already laying down Word, Excel, and PowerPoint), you need to supply an XML with the apps you want to exclude (no longer including Access) but you don't always know what's on the system to start with during a large deployment.

For example, if half your systems have Word, Excel, Outlook, and PowerPoint, and the other half only have Word and Excel, you can't use the same XML file to install Access on them because you'd end up installing/uninstalling other things.  In short - there's no "IncludeApp", only "ExcludeApp" in the XML.  I wrote a solution for this problem.

First, a couple of things up front:

  • I wrote this two years ago.  I'm still using it today, but I dug it up because it came up in conversation at MMS last week.
  • Second, I wrote this two years ago, there might be better ways to do this now with new versions of the installer that I haven't looked into.
  • Third, I wrote this two years ago, please don't judge some of my PS mistakes too harshly.  I've learned a lot since then.  :-)
  • And finally, this isn't meant to be run as is - you'll probably need to tweak it slightly for your environment.

The script works by taking a baseline XML (that you can modify if you'd like), and then adjusts the architecture, version, and language attributes of the XML to match what the target system has installed.  It then looks to see which apps are already excluded, and adds them into the XML file as excluded apps.  It ignores, however, the excluded app you're trying to install, such as Access.

The output of the script is the XML file that you'll need to call setup against.

  1param([string]$product="")
  2
  3function Get-ScriptDirectory {
  4<#
  5	.SYNOPSIS
  6		Get-ScriptDirectory returns the proper location of the script.
  7
  8	.OUTPUTS
  9		System.String
 10	
 11	.NOTES
 12		Returns the correct path within a packaged executable.
 13#>
 14	[OutputType([string])]
 15	param ()
 16	if ($null -ne $hostinvocation) {
 17		Split-Path $hostinvocation.MyCommand.path
 18	} else {
 19		Split-Path $script:MyInvocation.MyCommand.Path
 20	}
 21}
 22
 23Clear-Host
 24
 25#############################################################################################################################################################
 26# VARIABLES THAT CAN BE MODIFIED BY USER                                                                                                                    #
 27#############################################################################################################################################################
 28
 29$outputXML = "$($env:temp)\Office365_Custom_Config.xml"
 30$addProduct = $product
 31
 32#############################################################################################################################################################
 33# FUNCTIONS REQUIRED FOR APPLICATION                                                                                                                        #
 34#############################################################################################################################################################
 35
 36Function _QuitScript {
 37
 38    Param(
 39        $exitCode
 40    )
 41
 42    if($psISE) {
 43        throw $exitCode
 44    } else {
 45        Exit $exitCode
 46    }
 47
 48}
 49
 50function WriteXmlToScreen ([xml]$xml) {
 51    
 52    #Source: http://stackoverflow.com/questions/6142053/powershell-output-xml-to-screen
 53    $StringWriter = New-Object System.IO.StringWriter;
 54    $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter;
 55    $XmlWriter.Formatting = "indented";
 56    $xml.WriteTo($XmlWriter);
 57    $XmlWriter.Flush();
 58    $StringWriter.Flush();
 59    Write-Host $StringWriter.ToString();
 60
 61}
 62
 63#############################################################################################################################################################
 64# SCRIPT STARTS HERE                                                                                                                                        #
 65#############################################################################################################################################################
 66
 67[xml]$oXMLDocument = @'
 68<Configuration> 
 69    <Add OfficeClientEdition="32" Channel="Deferred" Version="16.0.0.0" OfficeMgmtCOM="TRUE"> 
 70        <Product ID="O365ProPlusRetail"> 
 71            <Language ID="en-us" /> 
 72            <ExcludeApp ID="Groove" /> 
 73            <ExcludeApp ID="OneDrive" /> 
 74            <ExcludeApp ID="Access" /> 
 75        </Product> 
 76    </Add> 
 77    <Display Level="None" AcceptEULA="TRUE" /> 
 78    <Property Name="AUTOACTIVATE" Value="0" /> 
 79    <Property Name="FORCEAPPSHUTDOWN" Value="TRUE" /> 
 80    <Property Name="SharedComputerLicensing" Value="0" /> 
 81</Configuration>
 82'@
 83
 84# Clear Out the Output File
 85If(Get-ChildItem $outputXML -ErrorAction SilentlyContinue) { Remove-Item $outputXML }
 86If(Get-ChildItem $outputXML -ErrorAction SilentlyContinue) {  _QuitScript -exitCode 1004 }
 87
 88# Find the O365ProPlusRetail Product Code
 89$thisProduct = $oXMLDocument.Configuration.Add.Product | ? { $_.ID -eq "O365ProPlusRetail" }
 90if(!$thisProduct) {  _QuitScript -exitCode 1005 }
 91
 92# If there are any "ExcludeApp" items in there, remove them
 93foreach($child in $thisProduct.ChildNodes | ? { $_.Name -eq "ExcludeApp" }) {
 94    [void]$thisProduct.RemoveChild($child)
 95}
 96
 97# If there are any "Languages" items in there, remove them
 98foreach($child in $thisProduct.ChildNodes | ? { $_.Name -eq "Language" }) {
 99    [void]$thisProduct.RemoveChild($child)
100}
101
102# Find the currently excluded applications
103$activeConfigurationGuid = (Get-ItemProperty "HKLM:SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs").ActiveConfiguration
104if(!$activeConfigurationGuid) { _QuitScript -exitCode 1002 }
105
106$excludedApps = (Get-ItemProperty "HKLM:SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\$activeConfigurationGuid\O365ProPlusRetail.16").ExclusionApps
107if(!$excludedApps) { _QuitScript -exitCode 1003 }
108
109# Find the currently installed language packs
110$languages = Get-ChildItem "HKLM:SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\$activeConfigurationGuid\O365ProPlusRetail.16" -Name
111
112# Add currently installed languages to XML file, en-us should go first.
113[System.XML.XMLElement]$oXMLLanguage = $oXMLDocument.CreateElement("Language")
114$oXMLLanguage.SetAttribute("ID", "en-us")
115[void]$thisProduct.AppendChild($oXMLLanguage)
116
117$languages | ? { $_ -ne "x-none" -and $_ -ne "en-us" } | % {
118    [System.XML.XMLElement]$oXMLLanguage = $oXMLDocument.CreateElement("Language")
119    $oXMLLanguage.SetAttribute("ID", $_)
120    [void]$thisProduct.AppendChild($oXMLLanguage)
121}
122
123# Remove what we're trying to add from the current exclusion list
124$excludedApps = $excludedApps.Split(",")
125$addProduct = $addProduct.Split(",")
126
127$addProduct | % {
128    $add = $_
129    $excludedApps = $excludedApps | ? { $_ -ne $add }
130}
131
132# Add New Excluded Apps to XML
133
134$excludedApps | % {
135    [System.XML.XMLElement]$oXMLExclude = $oXMLDocument.CreateElement("ExcludeApp")
136    $oXMLExclude.SetAttribute("ID",$_)
137    [void]$thisProduct.AppendChild($oXMLExclude)
138}
139
140# Get Version Number of Installed Office 365 Client
141
142$version = (Get-ItemProperty -Path HKLM:\Software\Microsoft\Office\ClickToRun\Configuration).VersionToReport
143
144if(!$version) { _QuitScript -exitCode 1007 }
145
146<#
147if (!(Get-ChildItem "$(Get-ScriptDirectory)\O365Setup\Office\Data\$version" -ErrorAction SilentlyContinue)) {
148	_QuitScript -exitCode 1008
149}
150#>
151
152# Set the Version Number in the ADD Node.
153[System.XML.XMLElement]$addNode = $thisProduct = $oXMLDocument.Configuration.Add
154$addNode.SetAttribute("Version",$version)
155
156# Get Architecture of Installed Office 365 Client
157$arch = (Get-ItemProperty -Path HKLM:\Software\Microsoft\Office\ClickToRun\Configuration).Platform
158if (!$arch) {
159	_QuitScript -exitCode 1009
160}
161	
162if ($arch -eq "x86") {
163	$archXML = "32"
164} elseif ($arch = "x64") {
165	$archXML = "64"
166} else {
167	_QuitScript -exitCode 1010
168}
169
170# Set the Architecture in the ADD Node
171[System.XML.XMLElement]$addNode = $thisProduct = $oXMLDocument.Configuration.Add
172$addNode.SetAttribute("OfficeClientEdition", $archXML)
173
174# Save XML Out to File
175
176$oXMLDocument.Save($outputXML)
177If(!(Get-ChildItem $outputXML)) {  _QuitScript -exitCode 1006 }
178
179WriteXmlToScreen $oXMLDocument


No comments