Getting from MSI to C2R Office With Magic! In One Easy Step!!
Getting from MSI to C2R Office With Magic!
TLDR: Here is the link to the github repo that has a short readme.
https://github.com/CodyMathis123/CM-Ramblings/tree/master/Office365
Recently my organization has had some motivators to move to Office 365. That seems to be a trend, yeah? We all know how much IT loves trends. This one does seem to be sticking around. With that, my job is to ensure we can GET to Office 365 efficiently, and effectively. It is good to keep in mind that a move to Office 365 does have other implications such as reinstalling Visio, and Project under some conditions. From what I am told, some people do indeed use this software at my organization.
Now with this nugget of office suite eradication in mind we need to find a good way to not get fired. Specifically, we need to ensure that if a user has Microsoft Visio/Project Pro/Standard before the Office 365 deployment, they should have an appropriate equivalent after the Office 365 deployment as well. First… let’s chat about how NOT to do this migration.
It worked but it didn’t
I will try to keep this bit short and sweet. For a first attempt at this migration I hoped to take advantage of our existing deployments of these various products. Currently many of our licensed applications deployed to collections as required. For a quick solution to our problem these existing applications need a bit of jazzing up. Nothing too crazy though, just a deployment type for MSI, and one for C2R.
The above theme continued for Project Professional, Visio Standard, and Visio Professional. Four applications, two deployment types each. The deployment types had a simple requirement set to determine if Office 365 was installed. In the case of Office 365 being installed, the C2R deployment type is used, otherwise the MSI version is used.
Awesome!
- Deploy Office 365
- Visio/Project removed (and Office of course)
- Office 365 Installed
- Wait a while for the required deployments of Visio/Project to evaluate
- ???
- Finally, they reinstalled based on old deployments and collections
That user experience is not great. Some light at the end of the tunnel was found though. Previously I posted about some fun scheduled task stuff, specifically Post OSD Scheduled Task. This turned out to be very handy for the above Office 365 deployment scenario. By creating a scheduled task to kick off some machine policy, and application evaluations quickly after the Office 365 install the user experience got a little better.
- Deploy Office 365
- Visio/Project removed (and Office of course)
- Office 365 Installed
- Scheduled task for policy refresh created and set to run in 1 minute
- F5 F5 F5 F5 F5 F5 F5
- Visio/Project reinstalled almost immediately after Office 365 based on old deployments and collections
The above method was tested a fair bit with VMs, and some kind testers in the organization. It went well enough for us to roll it out to 400 machines in a pilot wave. We had some oddities happen of course. Office 365 doesn’t seem to like running 2-3 C2R setups back to back on occasion. It was common enough for the Project or Visio installs to hang with no solution or consistency that I had to go back to the drawing board… or Twitter.
Hey, that looks cool!
Mr. @RowdyChildren made a tweet that showed a very crafty way of achieving my goal of not getting fired.
It was a pain in the ass, but it should retain Project 2016 and Visio 2016 at the correct edition if it's installed. #sccm #ConfigMgr #Office365 pic.twitter.com/JqQV09ZQrF
— TheDuck (@RowdyChildren) February 26, 2019
In the thread I mention my current approach that, in the end, didn’t pan out. But that picture got me thinking a bit. The idea seemed simple enough, set requirements per deployment type so we can dynamically select an appropriate XML.
Simple concept, tedious to execute. What if we can just magically generate an application with a deployment type per office suite combination, and assign requirements scriptomatically!
Scriptomatically
I believe the old adage of ‘a picture is worth a thousand words’ is relevant here.
But… some words should still be said. I was able to take the example @RowdyChildren gave, and provide a simple, repeatable method of generating the application.
The main contributor to all of this ‘working’ is a set of global conditions. These global conditions detect if Visio/Project Pro/Standard are installed. They are added as requirements to the various deployment types to guide your ConfigMgr client on it’s spiritual journey to Office 365.
A splash of regex, some querying of the registry, and bam! Global conditions.
Also, towards the end of the below snippet I am using some newer ConfigMgr cmdlets which I did not know existed and had created my own function. But, they are some awesome new cmdlets that make this whole thing possible, and easy to do.
With our rules created, we will also need to add them to the deployment types as needed. There is a second snippet of code below in this section that handles this. We have an array of Product IDs from the XML files (we can talk XML later). That array of IDs is passed into a switch statement for each deployment type, and some regex does the rest. Each match that occurs will add the appropriate requirement.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region create global conditions if they don't exist and find OS GC | |
#region GC Office Product function | |
function Get-CMOfficeGlobalCondition { | |
[CmdletBinding()] | |
param ( | |
[parameter(Mandatory = $true)] | |
[validateset('Project Professional', 'Project Standard', 'Project', 'Visio Professional', 'Visio Standard', 'Visio')] | |
[string]$Application | |
) | |
$GC_Name = [string]::Format('Condition Detection – Microsoft {0}', $Application) | |
switch ($Application) { | |
'Project Professional' { | |
$MSI_App = 'PRJPRO' | |
$C2R_App = 'PROJECTPRO' | |
} | |
'Project Standard' { | |
$MSI_App = 'PRJSTD' | |
$C2R_App = 'PROJECTSTD' | |
} | |
'Project' { | |
if (-not ($GC = Get-CMGlobalCondition –Name $GC_Name)) { | |
Write-Warning "Global condition not found: Creating GC '$GC_Name'" | |
$ruleProjPro = Get-CMOfficeGlobalCondition –Application 'Project Professional' | New-CMRequirementRuleBooleanValue –Value $true | |
$ruleProjStd = Get-CMOfficeGlobalCondition –Application 'Project Standard' | New-CMRequirementRuleBooleanValue –Value $true | |
$expressionProject = New-CMRequirementRuleExpression –AddRequirementRule $ruleProjPro, $ruleProjStd –ClauseOperator Or | |
$GC = New-CMGlobalConditionExpression –Name $GC_Name –DeviceType Windows –RootExpression $expressionProject | |
} | |
else { | |
Write-Verbose "Using existing Global Condition with name $($GC.LocalizedDisplayName)" | |
} | |
return $GC | |
} | |
'Visio Professional' { | |
$MSI_App = 'VISPRO' | |
$C2R_App = 'VISIOPRO' | |
} | |
'Visio Standard' { | |
$MSI_App = 'VISSTD' | |
$C2R_App = 'VISIOSTD' | |
} | |
'Visio' { | |
if (-not ($GC = Get-CMGlobalCondition –Name $GC_Name)) { | |
Write-Warning "Global condition not found: Creating GC '$GC_Name'" | |
$ruleVisPro = Get-CMOfficeGlobalCondition –Application 'Visio Professional' | New-CMRequirementRuleBooleanValue –Value $true | |
$ruleVisStd = Get-CMOfficeGlobalCondition –Application 'Visio Standard' | New-CMRequirementRuleBooleanValue –Value $true | |
$expressionVisio = New-CMRequirementRuleExpression –AddRequirementRule $ruleVisPro, $ruleVisStd –ClauseOperator Or | |
$GC = New-CMGlobalConditionExpression –Name $GC_Name –DeviceType Windows –RootExpression $expressionVisio | |
} | |
else { | |
Write-Verbose "Using existing Global Condition with name $($GC.LocalizedDisplayName)" | |
} | |
return $GC | |
} | |
} | |
$GC_Script = @" | |
`$MSI_App = '$MSI_App' | |
`$C2R_App = '$C2R_App' | |
`$RegMSIx86Uninstall = Get-ChildItem -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\' | |
`$RegMSIx64Uninstall = Get-ChildItem -Path 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | |
`$RegC2R = Get-ItemProperty -Path REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration | |
`$MSIx86 = `$RegMSIx86Uninstall | Where-Object { `$_.PSChildName -match "^OFFICE[0-9]{2}\.`$MSI_App`$" } | |
`$MSIx64 = `$RegMSIx64Uninstall | Where-Object { `$_.PSChildName -match "^OFFICE[0-9]{2}\.`$MSI_App`$" } | |
`$C2R = `$RegC2R | Where-Object { `$_.ProductReleaseIDs -match `$C2R_App -and `$_.Platform -eq '$Bitness' } | |
if (`$MSIx86 -or `$MSIx64 -or `$C2R) { | |
`$true | |
} | |
else { | |
`$false | |
} | |
"@ | |
if (-not ($GC = Get-CMGlobalCondition –Name $GC_Name)) { | |
Write-Warning "Global condition not found: Creating GC '$GC_Name'" | |
$GC = New-CMGlobalConditionScript –DataType Boolean –ScriptText $GC_Script –ScriptLanguage PowerShell –Name $GC_Name | |
} | |
else { | |
Write-Verbose "Using existing Global Condition with name $($GC.LocalizedDisplayName)" | |
} | |
return $GC | |
} | |
#endregion GC Office Product function | |
Set-Location –Path $SiteCodePath | |
Write-Output $('–' * 50) | |
Write-Output "Identifying and creating global conditions as needed for detection of Visio/Project Pro/Standard" | |
$VisStandard_GC = Get-CMOfficeGlobalCondition –Application 'Visio Standard' | |
$VisPro_GC = Get-CMOfficeGlobalCondition –Application 'Visio Professional' | |
$Vis_GC = Get-CMOfficeGlobalCondition –Application Visio | |
$ProjPro_GC = Get-CMOfficeGlobalCondition –Application 'Project Professional' | |
$ProjStandard_GC = Get-CMOfficeGlobalCondition –Application 'Project Standard' | |
$Proj_GC = Get-CMOfficeGlobalCondition –Application Project | |
$OS_GC = Get-CMGlobalCondition –Name 'Operating System' | Where-Object { $_.ModelName -eq 'GLOBAL/OperatingSystem' } | |
Write-Output "All global conditions identified or created" | |
#endregion create global conditions if they don't exist and find OS GC | |
#region create our requirements for use in the DeploymentTypes | |
Write-Output $('–' * 50) | |
Write-Output "Creating CMRequirementRules that can be applied to deployment types based on our global conditions" | |
$2016_Rule = $OS_GC | New-CMRequirementRuleOperatingSystemValue –PlatformString Windows/All_x64_Windows_7_Client, Windows/All_x64_Windows_8_Client, Windows/All_x64_Windows_8.1_Client –RuleOperator OneOf | |
$2019_Rule = $OS_GC | New-CMRequirementRuleOperatingSystemValue –PlatformString Windows/All_x64_Windows_10_and_higher_Clients –RuleOperator OneOf | |
$VisStandard_Rule = $VisStandard_GC | New-CMRequirementRuleBooleanValue –Value $true | |
$VisPro_Rule = $VisPro_GC | New-CMRequirementRuleBooleanValue –Value $true | |
$Vis_Rule = $Vis_GC | New-CMRequirementRuleBooleanValue –Value $true | |
$ProjStandard_Rule = $ProjStandard_GC | New-CMRequirementRuleBooleanValue –Value $true | |
$ProjPro_Rule = $ProjPro_GC | New-CMRequirementRuleBooleanValue –Value $true | |
$Proj_Rule = $Proj_GC | New-CMRequirementRuleBooleanValue –Value $true | |
Write-Output "CMRequirementRules created" | |
#endregion create our requirements for use in the DeploymentTypes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region determine which Requirements we need to add for this deployment type based on ProductIDs | |
$Requirements = [System.Collections.ArrayList]::new() | |
switch –Regex ($DT.ProductIDs) { | |
'^VisioPro(X|2019)Volume$' { | |
$null = $Requirements.Add($VisPro_Rule) | |
} | |
'^VisioStd(X|2019)Volume$' { | |
$null = $Requirements.Add($VisStandard_Rule) | |
} | |
'^VisioProRetail$' { | |
$null = $Requirements.Add($Vis_Rule) | |
} | |
'^ProjectPro(X|2019)Volume$' { | |
$null = $Requirements.Add($ProjPro_Rule) | |
} | |
'^ProjectStd(X|2019)Volume$' { | |
$null = $Requirements.Add($ProjStandard_Rule) | |
} | |
'^ProjectProRetail$' { | |
$null = $Requirements.Add($Proj_Rule) | |
} | |
} | |
switch –Regex ($DT.ProductIDs) { | |
'2019' { | |
$null = $Requirements.Add($2019_Rule) | |
break | |
} | |
'XVolume' { | |
$null = $Requirements.Add($2016_Rule) | |
break | |
} | |
} | |
$addCMScriptDeploymentTypeSplat.AddRequirement = $Requirements | |
#endregion determine which Requirements we need to add for this deployment type based on ProductIDs |
Use the XML Luke
Even with requirements made, we do still need to make the application, and more importantly the deployment types. Previously we had our trusty setup.exe /admin option for MSI based office customization. Now we are fortunate to have some incredibly useful XML configuration files. These are incredibly useful because we can edit them on the fly very easily.
This script will take some input parameters such as your company name, the channel of office you intend to deploy, the bitness of the installer, license type for Visio and Project, and optionally the software version. With this info we manipulate the XML files to ensure a consistent build.
Outside of editing the XML, you’ll notice I’m sorting my $DeploymentTypes object. This is an important step to guarantee proper deployment type selection based on requirements. If a base install of Office 365 with no requirements were priority 1, or any priority but the lowest for that matter, then this wouldn’t work as intended. A base install has no requirements, so any computer will see this and use that deployment type if it gets that far down the chain. Instead, we sort by $AppName.Length, then $AppName. Really you could sort by the count of ProductIDs in the XML, but name length makes the end result look pretty, and function the same.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region generate PSCustomObject that we will loop through to create DeploymentTypes | |
$DeploymentTypes = foreach ($XML in $FilteredXML_Configs) { | |
#region Load XML and manipulate based on input parameters, and gather information | |
$Config = $XML.Name | |
$ConfigXML = [xml]::new() | |
$ConfigXML.PreserveWhitespace = $true | |
$ConfigXML.Load($XML.FullName) | |
$ConfigXML.Configuration.AppSettings.Setup.Value = $Company | |
$ConfigXML.Configuration.Add.OfficeClientEdition = $XML_Bitness | |
$ConfigXML.Configuration.Add.Version = $FullBuildNumber | |
$ConfigXML.Configuration.Add.Channel = $XML_Channel | |
$ConfigXml.Configuration.Add.AllowCdnFallback = $($AllowCdnFallback | Out-String) | |
$ConfigXml.Configuration.Display.Level = $DisplayLevel | |
$ConfigXML.Save($XML.FullName) | |
$AppName = $ConfigXML.Configuration.Info.Description | |
$ProductIDs = $ConfigXML.Configuration.Add.Product.ID | |
#endregion Load XML and manipulate based on input parameters, and gather information | |
[PSCustomObject]@{ | |
Config = $Config | |
AppName = $AppName | |
AppSource = $AppRoot | |
ProductIDs = $ProductIDs | |
NameLength = $($AppName.Length) | |
} | |
} | |
# We sort the deployment types so that the priority order ensures proper installation depending on existing apps | |
$DeploymentTypes = $DeploymentTypes | Sort-Object –Property NameLength, AppName –Descending | |
#endregion generate PSCustomObject that we will loop through to create DeploymentTypes |
License Variations
There are a couple of licensing options available when it comes to Microsoft Visio, and Project. Specifically, volume license, and online licensing. The script has four parameters to let you select which license type you have in your organization. So, It is possible to specify ‘volume’ or ‘online’ for Visio, and Project Pro and Standard. Below is a set of snapshots for 4 of the application combination possibilities.
Some Good To Knows
The comment based help for this script covers the parameters and functionality pretty well. There are some highlights that I’ll cover.
Content!
You’ll notice that this creates a LOT of deployment types. Thankfully, and interestingly, every single combination of applications in the XML can be served up from the same set of binaries. You simply need to run ‘setup.exe /download o365.xml’ once. As long as the version, and architecture is the same across your XML files then these binaries that get downloaded will be valid for each deployment type. The script takes care of ensuring the version, and architecture are consistent.
AllowCDNFallback
The Office 365 XML has the option to fallback to the Content Delivery Network (CDN). This has some interesting advantages. In the XML I’ve provided I have the language for Office set to <languageid=”matchos”fallback=”en-us”>. When you combine MatchOS with AllowCDNFallback we are able to cache niche cases of languages that we may not have been aware of. You may, or may not want this as an option of course which is why it is a boolean parameter. If binaries are not in the package, this will allow the client to download all content from the CDN as well.</languageid=”matchos”fallback=”en-us”>
DOINC
Luckily, we do have the same binaries for all these deployment types, but wouldn’t it be great if we didn’t need binaries in the source at all for this (and not take down your internet link)? Soon it should be possible to leverage DOINC and AllowCDNFallback to do just this. When this is live, you’ll be able to deploy Office 365 with setup.exe and an XML file, no other content needed. Your devices will hit your Distribution Point and share between eachother with Delivery Optimization.
The office team is looking at lighting this up in the Monthly Channel during H2 2019
— Narkis Engler (@narkissit) April 11, 2019
Consideration…
If you do need to update content on these applications, particularly the one with 17 deployment types, it will cause a lot of revisions. However, Depending on what you are editing in the various deployment types it may be easier to regenerate the application from scratch using the script again. Thankfully that is very easy to do!
That’s All Folks
Somewhere in all of this, I believe I achieved my goal of not getting fired. This script provides a quick, consistent way to move from MSI based Office suite installation to ClickToRun. In our organization we have had great success by leveraging this. Feel free to let me know if you have any issues with this and I’ll do my best to be of assistance.
GIT it here: https://github.com/CodyMathis123/CM-Ramblings/tree/master/Office365
Add comment