Getting from MSI to C2R Office With Magic!

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!

  1. Deploy Office 365
  2. Visio/Project removed (and Office of course)
  3. Office 365 Installed
  4. Wait a while for the required deployments of Visio/Project to evaluate
  5. ???
  6. 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.

  1. Deploy Office 365
  2. Visio/Project removed (and Office of course)
  3. Office 365 Installed
  4. Scheduled task for policy refresh created and set to run in 1 minute
  5. F5 F5 F5 F5 F5 F5 F5
  6. 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.

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.


#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


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


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

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 

@CodyMathis123

https://sccmf12twice.com/author/cmathis/

Cody Mathis

Hey!

I am a 'Senior Systems Engineer' who has an odd level of enthusiasm for SCCM, PowerShell, Automation, and SQL as well as many other things. I am always willing to take a bit of extra time (if it isn't an emergency) to figure out how to do something in PowerShell, shave off a few seconds of run time, make a process easily repeatable, or simply make it look pretty.

Add comment

5 + 1 =

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Follow us

Don't be shy, get in touch. We love meeting interesting people and making new friends.

%d bloggers like this: