├── Invoke-OPPContentUpdate.ps1 └── Set-OPPUpdateChannel.ps1 /Invoke-OPPContentUpdate.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Update the content of an Office 365 ProPlus application created in ConfigMgr. 4 | 5 | .DESCRIPTION 6 | This script will ensure that the latest content version is updated when this script is either manually triggered or scheduled to run by doing the following: 7 | - Download the latest Office Deployment Tool executable and replace existing setup.exe in application content source path 8 | - Update the Office 365 ProPlus application content to the latest version 9 | - Update the detection method of the application in ConfigMgr with the latest versioning details 10 | 11 | .PARAMETER SiteServer 12 | Site Server where the SMS Provider is installed. 13 | 14 | .PARAMETER OfficePackagePath 15 | Specify the full path to the Office application content source. 16 | 17 | .PARAMETER OfficeApplicationName 18 | Specify the Office application display name. 19 | 20 | .PARAMETER OfficeConfigurationFile 21 | Specify the Office application configuration file name, e.g. 'configuration.xml'. 22 | 23 | .PARAMETER SkipDetectionMethodUpdate 24 | When True, update the detection method for the specified Office 365 ProPlus application. 25 | 26 | .EXAMPLE 27 | # Update the content of an Office 365 ProPlus application named 'Office 365 ProPlus 64-bit' to the latest version: 28 | .\Invoke-OPPContentUpdate.ps1 -SiteServer "CM01" -OfficePackagePath "C:\Source\Apps\Office365\x64" -OfficeApplicationName "Office 365 ProPlus 64-bit" -OfficeConfigurationFile "configuration.xml" -SkipDetectionMethodUpdate $false -Verbose 29 | 30 | # Update the content of an Office 365 ProPlus application named 'Office 365 ProPlus 64-bit' to the latest version, but don't update the application detection method: 31 | .\Invoke-OPPContentUpdate.ps1 -SiteServer "CM01" -OfficePackagePath "C:\Source\Apps\Office365\x64" -OfficeApplicationName "Office 365 ProPlus 64-bit" -OfficeConfigurationFile "configuration.xml" -SkipDetectionMethodUpdate $true -Verbose 32 | 33 | .NOTES 34 | FileName: Invoke-OPPContentUpdate.ps1 35 | Author: Nickolaj Andersen 36 | Contact: @NickolajA 37 | Created: 2019-10-22 38 | Updated: 2019-10-26 39 | 40 | Version history: 41 | 1.0.0 - (2019-10-22) Script created 42 | 1.0.1 - (2019-10-25) Added the SkipDetectionMethodUpdate parameter to provide functionality that will not update the detection method 43 | 1.0.2 - (2019-10-26) Added so that Distribution Points will automatically be updated 44 | 1.0.3 - (2020-09-09) Added SiteServer parameter and improved ConfigurationManager module loading to support scheduling this script in system context on a site server with the ConfigMgr console installed 45 | 1.0.4 - (2020-09-09) Fixed an issue when the SkipDetectionMethodUpdate parameter was set to True, it would remove any additional rules manually added 46 | #> 47 | [CmdletBinding(SupportsShouldProcess = $true)] 48 | param( 49 | [parameter(Mandatory = $true, HelpMessage = "Site Server where the SMS Provider is installed.")] 50 | [ValidateNotNullOrEmpty()] 51 | [ValidateScript({Test-Connection -ComputerName $_ -Count 1 -Quiet})] 52 | [string]$SiteServer, 53 | 54 | [parameter(Mandatory = $false, HelpMessage = "Specify the full path to the Office application content source.")] 55 | [ValidateNotNullOrEmpty()] 56 | [string]$OfficePackagePath = "E:\CMSource\Apps\Microsoft\Office 365 ProPlus\x64", 57 | 58 | [parameter(Mandatory = $false, HelpMessage = "Specify the Office application display name.")] 59 | [ValidateNotNullOrEmpty()] 60 | [string]$OfficeApplicationName = "Office 365 ProPlus 64-bit (Semi-Annual)", 61 | 62 | [parameter(Mandatory = $false, HelpMessage = "Specify the Office application configuration file name, e.g. 'configuration.xml'.")] 63 | [ValidateNotNullOrEmpty()] 64 | [string]$OfficeConfigurationFile = "configuration.xml", 65 | 66 | [parameter(Mandatory = $false, HelpMessage = "When True, update the detection method for the specified Office 365 ProPlus application.")] 67 | [ValidateNotNullOrEmpty()] 68 | [bool]$SkipDetectionMethodUpdate = $true 69 | ) 70 | Begin { 71 | # Determine SiteCode from WMI 72 | try { 73 | Write-Verbose -Message "Determining Site Code for Site server: '$($SiteServer)'" 74 | $SiteCodeObjects = Get-WmiObject -Namespace "root\SMS" -Class SMS_ProviderLocation -ComputerName $SiteServer -ErrorAction Stop 75 | foreach ($SiteCodeObject in $SiteCodeObjects) { 76 | if ($SiteCodeObject.ProviderForLocalSite -eq $true) { 77 | $SiteCode = $SiteCodeObject.SiteCode 78 | Write-Verbose -Message "Site Code: $($SiteCode)" 79 | } 80 | } 81 | } 82 | catch [System.Exception] { 83 | Write-Warning -Message "Unable to determine site code from specified Configuration Manager site server, specify the site server where the SMS Provider is installed"; break 84 | } 85 | 86 | # Import ConfigMgr module, required to update the detection method of the Office application 87 | try { 88 | Import-Module -Name ConfigurationManager -ErrorAction Stop 89 | } 90 | catch [System.Exception] { 91 | try { 92 | Import-Module (Join-Path -Path (($env:SMS_ADMIN_UI_PATH).Substring(0,$env:SMS_ADMIN_UI_PATH.Length-5)) -ChildPath "\ConfigurationManager.psd1") -Force -ErrorAction Stop 93 | if ((Get-PSDrive $SiteCode -ErrorAction SilentlyContinue | Measure-Object).Count -ne 1) { 94 | New-PSDrive -Name $SiteCode -PSProvider "AdminUI.PS.Provider\CMSite" -Root $SiteServer -ErrorAction Stop 95 | } 96 | } 97 | catch [System.Exception] { 98 | Write-Warning -Message "Failed to load the ConfigurationManager module. Error message: $($_.Exception.Message)"; break 99 | } 100 | } 101 | } 102 | Process { 103 | # Functions 104 | function Start-DownloadFile { 105 | param( 106 | [parameter(Mandatory=$true)] 107 | [ValidateNotNullOrEmpty()] 108 | [string]$URL, 109 | 110 | [parameter(Mandatory=$true)] 111 | [ValidateNotNullOrEmpty()] 112 | [string]$Path, 113 | 114 | [parameter(Mandatory=$true)] 115 | [ValidateNotNullOrEmpty()] 116 | [string]$Name 117 | ) 118 | Begin { 119 | # Construct WebClient object 120 | $WebClient = New-Object -TypeName System.Net.WebClient 121 | } 122 | Process { 123 | # Create path if it doesn't exist 124 | if (-not(Test-Path -Path $Path)) { 125 | New-Item -Path $Path -ItemType Directory -Force | Out-Null 126 | } 127 | 128 | # Start download of file 129 | $WebClient.DownloadFile($URL, (Join-Path -Path $Path -ChildPath $Name)) 130 | } 131 | End { 132 | # Dispose of the WebClient object 133 | $WebClient.Dispose() 134 | } 135 | } 136 | 137 | Write-Verbose -Message "Initiating Office application content update process" 138 | 139 | try { 140 | # Download latest Office Deployment Tool 141 | $ODTDownloadURL = "https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117" 142 | $WebResponseURL = ((Invoke-WebRequest -Uri $ODTDownloadURL -UseBasicParsing -ErrorAction Stop -Verbose:$false).links | Where-Object { $_.outerHTML -like "*click here to download manually*" }).href 143 | $ODTFileName = Split-Path -Path $WebResponseURL -Leaf 144 | $ODTFilePath = (Join-Path -Path $env:windir -ChildPath "Temp") 145 | Write-Verbose -Message "Attempting to download latest Office Deployment Toolkit executable" 146 | Start-DownloadFile -URL $WebResponseURL -Path $ODTFilePath -Name $ODTFileName 147 | 148 | try { 149 | # Extract latest ODT file 150 | $ODTExecutable = (Join-Path -Path $ODTFilePath -ChildPath $ODTFileName) 151 | $ODTExtractionPath = (Join-Path -Path $ODTFilePath -ChildPath (Get-ChildItem -Path $ODTExecutable).VersionInfo.ProductVersion) 152 | $ODTExtractionArguments = "/quiet /extract:$($ODTExtractionPath)" 153 | 154 | # Extract ODT files 155 | Write-Verbose -Message "Attempting to extract the setup.exe executable from Office Deployment Toolkit" 156 | Start-Process -FilePath $ODTExecutable -ArgumentList $ODTExtractionArguments -Wait -ErrorAction Stop 157 | 158 | try { 159 | # Determine if ODT needs to be updated in Office package folder 160 | $ODTCurrentVersion = (Get-ChildItem -Path (Join-Path -Path $OfficePackagePath -ChildPath "setup.exe") -ErrorAction Stop).VersionInfo.ProductVersion 161 | Write-Verbose -Message "Determined current Office Deployment Toolkit version as: $($ODTCurrentVersion)" 162 | $ODTLatestVersion = (Get-ChildItem -Path (Join-Path -Path $ODTExtractionPath -ChildPath "setup.exe") -ErrorAction Stop).VersionInfo.ProductVersion 163 | Write-Verbose -Message "Determined latest Office Deployment Toolkit version as: $($ODTLatestVersion)" 164 | 165 | try { 166 | if ([System.Version]$ODTLatestVersion -gt [System.Version]$ODTCurrentVersion) { 167 | # Replace existing setup.exe in Office package path with extracted 168 | Write-Verbose -Message "Current Office Deployment Toolkit version needs to be updated to latest version, attempting to copy latest setup.exe" 169 | Copy-Item -Path (Join-Path -Path $ODTExtractionPath -ChildPath "setup.exe") -Destination (Join-Path -Path $OfficePackagePath -ChildPath "setup.exe") -Force -ErrorAction Stop 170 | } 171 | 172 | try { 173 | # Cleanup downloaded ODT content and executable 174 | Write-Verbose -Message "Attempting to remove downloaded Office Deployment Toolkit temporary content files" 175 | Remove-Item -Path $ODTExtractionPath -Recurse -Force -ErrorAction Stop 176 | Remove-Item -Path $ODTExecutable -Force -ErrorAction Stop 177 | 178 | try { 179 | # Determine existing Office package version in \office\data folder 180 | Write-Verbose -Message "Attempting to detect currect version information for existing Office 365 ProPlus content" 181 | $OfficeDataFolderRoot = (Join-Path -Path $OfficePackagePath -ChildPath "office\data") 182 | $OfficeDataFolderCurrent = Get-ChildItem -Path $OfficeDataFolderRoot -Directory -ErrorAction Stop 183 | $OfficeDataFileCurrent = Get-ChildItem -Path $OfficeDataFolderRoot -Filter "v*_*.cab" -ErrorAction Stop 184 | 185 | try { 186 | # Construct arguments for setup.exe and call the executable and let it complete before we continue 187 | $OfficeArguments = "/download $($OfficeConfigurationFile)" 188 | Write-Verbose -Message "Attempting to update the Office 365 ProPlus application content based on configuration file" 189 | Start-Process -FilePath "setup.exe" -ArgumentList $OfficeArguments -WorkingDirectory $OfficePackagePath -Wait -ErrorAction Stop 190 | 191 | # Cleanup older Office data folder versions 192 | Write-Verbose -Message "Checking to see if previous Office 365 ProPlus application content version should be removed" 193 | if ((Get-ChildItem -Path $OfficeDataFolderRoot -Directory | Measure-Object).Count -ge 2) { 194 | Write-Verbose -Message "Previous Office 365 ProPlus application content should be cleaned up" 195 | 196 | # Remove old data folder 197 | Write-Verbose -Message "Attempting to remove Office 365 ProPlus application content directory: $($OfficeDataFolderCurrent.Name)" 198 | Remove-Item -Path $OfficeDataFolderCurrent.FullName -Recurse -Force 199 | 200 | # Remove old data cab file 201 | Write-Verbose -Message "Attempting to remove Office 365 ProPlus application content cabinet file: $($OfficeDataFileCurrent.Name)" 202 | Remove-Item -Path $OfficeDataFileCurrent.FullName -Force 203 | } 204 | 205 | try { 206 | # Get latest Office data version 207 | $OfficeDataLatestVersion = (Get-ChildItem -Path $OfficeDataFolderRoot -Directory -ErrorAction Stop).Name 208 | Write-Verbose -Message "Office 365 ProPlus application content is now determined at version: $($OfficeDataLatestVersion)" 209 | 210 | try { 211 | # Set location to ConfigMgr drive 212 | Write-Verbose -Message "Changing location to ConfigMgr drive: $($SiteCode):" 213 | Set-Location -Path ($SiteCode + ":") -ErrorAction Stop -Verbose:$false 214 | 215 | try { 216 | # Get Office application deployment type object 217 | Write-Verbose -Message "Attempting to retrieve Office 365 ProPlus application deployment type for application: $($OfficeApplicationName)" 218 | $OfficeDeploymentType = Get-CMDeploymentType -ApplicationName $OfficeApplicationName -ErrorAction Stop -Verbose:$false 219 | 220 | if ($SkipDetectionMethodUpdate -eq $false) { 221 | try { 222 | # Create a new registry detection method 223 | Write-Verbose -Message "Attempting to create new registry detection clause object for Office 365 ProPlus application deployment type: $($OfficeDeploymentType.LocalizedDisplayName)" 224 | $DetectionClauseArgs = @{ 225 | ExpressionOperator = "GreaterEquals" 226 | Hive = "LocalMachine" 227 | KeyName = "Software\Microsoft\Office\ClickToRun\Configuration" 228 | PropertyType = "Version" 229 | ValueName = "VersionToReport" 230 | ExpectedValue = $OfficeDataLatestVersion 231 | Value = $true 232 | ErrorAction = "Stop" 233 | Verbose = $false 234 | } 235 | $DetectionClauseRegistryKeyValue = New-CMDetectionClauseRegistryKeyValue @DetectionClauseArgs 236 | 237 | try { 238 | # Construct string array with logical name of enhanced detection method registry name 239 | [string[]]$OfficeApplicationDetectionMethodLogicalName = ([xml]$OfficeDeploymentType.SDMPackageXML).AppMgmtDigest.DeploymentType.Installer.CustomData.EnhancedDetectionMethod.Settings.SimpleSetting | Where-Object { $PSItem.DataType -like "Version" } | Select-Object -ExpandProperty LogicalName 240 | Write-Verbose -Message "Enhanced detection method logical name for existing registry detection clause was determined as: $($OfficeApplicationDetectionMethodLogicalName)" 241 | 242 | # Remove existing detection method and add new with updated version info 243 | Write-Verbose -Message "Attempting to replace existing detection clause with new containing latest Office 365 ProPlus application content version" 244 | Set-CMScriptDeploymentType -InputObject $OfficeDeploymentType -RemoveDetectionClause $OfficeApplicationDetectionMethodLogicalName -AddDetectionClause $DetectionClauseRegistryKeyValue -ErrorAction Stop -Verbose:$false 245 | } 246 | catch [System.Exception] { 247 | Write-Warning -Message "Failed to update registry detection clause for Office 365 ProPlus application deployment type. Error message: $($_.Exception.Message)" 248 | } 249 | } 250 | catch [System.Exception] { 251 | Write-Warning -Message "Failed to create new registry detection clause object. Error message: $($_.Exception.Message)" 252 | } 253 | } 254 | 255 | try { 256 | # Update Distribution Points 257 | Write-Verbose -Message "Attempting to update Distribution Points for application: $($OfficeApplicationName)" 258 | Update-CMDistributionPoint -ApplicationName $OfficeApplicationName -DeploymentTypeName $OfficeDeploymentType.LocalizedDisplayName -ErrorAction Stop -Verbose:$false 259 | 260 | Write-Verbose -Message "Successfully completed Office 365 ProPlus application content update process" 261 | } 262 | catch [System.Exception] { 263 | Write-Warning -Message "Failed to update Distribution Points for application. Error message: $($_.Exception.Message)" 264 | } 265 | } 266 | catch [System.Exception] { 267 | Write-Warning -Message "Failed to retrieve deployment type object for Office 365 ProPlus application. Error message: $($_.Exception.Message)" 268 | } 269 | } 270 | catch [System.Exception] { 271 | Write-Warning -Message "Failed to change current location to ConfigMgr drive. Error message: $($_.Exception.Message)" 272 | } 273 | } 274 | catch [System.Exception] { 275 | Write-Warning -Message "Failed to determine the latest Office 365 application content version. Error message: $($_.Exception.Message)" 276 | } 277 | } 278 | catch [System.Exception] { 279 | Write-Warning -Message "Failed to update Office 365 ProPlus application content. Error message: $($_.Exception.Message)" 280 | } 281 | } 282 | catch [System.Exception] { 283 | Write-Warning -Message "Failed to detect currect version information for existing Office 365 ProPlus content. Error message: $($_.Exception.Message)" 284 | } 285 | } 286 | catch [System.Exception] { 287 | Write-Warning -Message "Failed to cleanup downloaded Office Deployment Toolkit content from temporary location. Error message: $($_.Exception.Message)" 288 | } 289 | } 290 | catch [System.Exception] { 291 | Write-Warning -Message "Failed to copy new setup.exe to Office application content source. Error message: $($_.Exception.Message)" 292 | } 293 | } 294 | catch [System.Exception] { 295 | Write-Warning -Message "Failed to determine version numbers for Office Deployment Toolkit for comparison. Error message: $($_.Exception.Message)" 296 | } 297 | } 298 | catch [System.Exception] { 299 | Write-Warning -Message "Failed to extract Office Deployment Toolkit. Error message: $($_.Exception.Message)" 300 | } 301 | } 302 | catch [System.Exception] { 303 | Write-Warning -Message "Failed to download the latest Office Deployment Toolkit. Error message: $($_.Exception.Message)" 304 | } 305 | } 306 | End { 307 | # Set location back to filesystem drive 308 | Set-Location -Path $env:SystemDrive 309 | } -------------------------------------------------------------------------------- /Set-OPPUpdateChannel.ps1: -------------------------------------------------------------------------------- 1 | # Define the name of the update channel 2 | $UpdateChannelName = "Broad" # Valid options are: Monthly, Targeted, Broad and Insiders 3 | 4 | switch ($UpdateChannelName) { 5 | "Monthly" { 6 | $UpdateChannel = "http://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6ddf67d60" 7 | } 8 | "Targeted" { 9 | $UpdateChannel = "http://officecdn.microsoft.com/pr/b8f9b850-328d-4355-9145-c59439a0c4cf" 10 | } 11 | "Broad" { 12 | $UpdateChannel = "http://officecdn.microsoft.com/pr/7ffbc6bf-bc32-4f92-8982-f9dd17fd3114" 13 | } 14 | "Insiders" { 15 | $UpdateChannel = "http://officecdn.microsoft.com/pr/64256afe-f5d9-4f86-8936-8840a6a4f5be" 16 | } 17 | } 18 | 19 | # Change update channel if existing does not match desired update channel 20 | Write-Output -InputObject "Chosen update channel '$($UpdateChannelName)' translated to: $($UpdateChannel)" 21 | $OfficeC2RClientPath = Join-Path -Path $env:CommonProgramW6432 -ChildPath "Microsoft Shared\ClickToRun\OfficeC2RClient.exe" 22 | $C2RConfiguration = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" 23 | $CurrentUpdateChannel = Get-ItemProperty -Path $C2RConfiguration | Select-Object -ExpandProperty CDNBaseUrl 24 | if ($CurrentUpdateChannel -notlike $UpdateChannel) { 25 | Write-Output -InputObject "Current update channel does not match specified update channel, calling procedure to change update channel" 26 | 27 | # Update registy configuration for new update channel 28 | Set-ItemProperty -Path $C2RConfiguration -Name "UpdateChannel" -Value $UpdateChannel -Force 29 | Write-Output -InputObject "Updated registry configuration with new update channel data endpoint" 30 | 31 | # Call OfficeC2RClient.exe and change the update channel 32 | $OfficeC2RClientParameters = "/changesetting channel=$($UpdateChannelName)" 33 | Write-Output -InputObject "Calling OfficeC2RClient.exe with the following parameters: $($OfficeC2RClientParameters)" 34 | Start-Process -FilePath $OfficeC2RClientPath -ArgumentList $OfficeC2RClientParameters -Wait 35 | 36 | # Call OfficeC2RClient.exe and update Office applications 37 | $OfficeC2RClientParameters = "/update user updateprompt=false forceappshutdown=true displaylevel=true" 38 | Write-Output -InputObject "Calling OfficeC2RClient.exe with the following parameters: $($OfficeC2RClientParameters)" 39 | Start-Process -FilePath $OfficeC2RClientPath -ArgumentList $OfficeC2RClientParameters 40 | 41 | # Trigger hardware inventory 42 | Invoke-WmiMethod -Namespace "root\ccm" -Class "SMS_Client" -Name "TriggerSchedule" -ArgumentList "{00000000-0000-0000-0000-000000000001}" 43 | } 44 | else { 45 | Write-Output -InputObject "Current update channel matches specified update channel, will not attempt to change update channel" 46 | } --------------------------------------------------------------------------------