Zmiana kanału aktualizacji Office
📦 Office 365 POWERSHELL ChrisTitusTechZmienia kanał aktualizacji pakietu Office (np. Current, Monthly Enterprise, Semi-Annual Enterprise). Przydatny przy standaryzacji kanału aktualizacji w całej organizacji lub migracji między kanałami.
📄 Change-OfficeChannel.ps1
🕒 2026-04-13
📦 Źródło: christitustech
Change-OfficeChannel.ps1
param(
[Parameter()]
[ValidateSet("FirstReleaseCurrent","Current","FirstReleaseDeferred","Deferred",
"MonthlyTargeted","Monthly","SemiAnnualTargeted","SemiAnnual")]
[string]$Channel,
[Parameter()]
[switch]$RollBack,
[Parameter()]
[bool]$SendExitCode = $false,
[Parameter()]
[string]$LogFilePath
)
Function Get-ScriptPath() {
[CmdletBinding()]
param(
)
process {
#get local path
$scriptPath = "."
if ($PSScriptRoot) {
$scriptPath = $PSScriptRoot
} else {
$scriptPath = (Get-Item -Path ".\").FullName
}
return $scriptPath
}
}
Function Get-OfficeC2Rexe() {
[CmdletBinding()]
Param(
)
process {
$Office2RClientKey = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' #ClientFolder
#find update exe file
$OfficeUpdatePath = Get-ItemProperty -Path $Office2RClientKey | Select-Object -Property ClientFolder
$temp = Out-String -InputObject $OfficeUpdatePath
$temp = $temp.Substring($temp.LastIndexOf('-')+2)
$temp = $temp.Trim()
$OfficeUpdatePath = $temp
$OfficeUpdatePath+= '\OfficeC2RClient.exe'
return $OfficeUpdatePath
}
}
Function Wait-ForOfficeCTRUpdate() {
[CmdletBinding()]
Param(
[Parameter()]
[int] $TimeOutInMinutes = 120
)
begin {
$HKLM = [UInt32] "0x80000002"
$HKCR = [UInt32] "0x80000000"
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
}
process {
Write-Host "Waiting for Update process to Complete..."
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Waiting for Update process to Complete..." -LogFilePath $LogFilePath
[datetime]$operationStart = Get-Date
[datetime]$totalOperationStart = Get-Date
Start-Sleep -Seconds 10
$mainRegPath = Get-OfficeCTRRegPath
$scenarioPath = $mainRegPath + "\scenario"
$regProv = Get-Wmiobject -list "StdRegProv" -namespace root\default -ErrorAction Stop
[DateTime]$startTime = Get-Date
[string]$executingScenario = ""
$failure = $false
$cancelled = $false
$updateRunning=$false
[string[]]$trackProgress = @()
[string[]]$trackComplete = @()
[int]$noScenarioCount = 0
do {
$allComplete = $true
$executingScenario = $regProv.GetStringValue($HKLM, $mainRegPath, "ExecutingScenario").sValue
$scenarioKeys = $regProv.EnumKey($HKLM, $scenarioPath)
foreach ($scenarioKey in $scenarioKeys.sNames) {
if (!($executingScenario)) { continue }
if ($scenarioKey.ToLower() -eq $executingScenario.ToLower()) {
$taskKeyPath = Join-Path $scenarioPath "$scenarioKey\TasksState"
$taskValues = $regProv.EnumValues($HKLM, $taskKeyPath).sNames
foreach ($taskValue in $taskValues) {
[string]$status = $regProv.GetStringValue($HKLM, $taskKeyPath, $taskValue).sValue
$operation = $taskValue.Split(':')[0]
$keyValue = $taskValue
if ($status.ToUpper() -eq "TASKSTATE_FAILED") {
$failure = $true
}
if ($status.ToUpper() -eq "TASKSTATE_CANCELLED") {
$cancelled = $true
}
if (($status.ToUpper() -eq "TASKSTATE_COMPLETED") -or`
($status.ToUpper() -eq "TASKSTATE_CANCELLED") -or`
($status.ToUpper() -eq "TASKSTATE_FAILED")) {
if (($trackProgress -contains $keyValue) -and !($trackComplete -contains $keyValue)) {
$displayValue = $operation + "`t" + $status + "`t" + (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
#Write-Host $displayValue
$trackComplete += $keyValue
$statusName = $status.Split('_')[1];
if (($operation.ToUpper().IndexOf("DOWNLOAD") -gt -1) -or `
($operation.ToUpper().IndexOf("APPLY") -gt -1)) {
$operationTime = getOperationTime -OperationStart $operationStart
$displayText = $statusName + "`t" + $operationTime
Write-Host $displayText
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError $displayText -LogFilePath $LogFilePath
}
}
} else {
$allComplete = $false
$updateRunning=$true
if (!($trackProgress -contains $keyValue)) {
$trackProgress += $keyValue
$displayValue = $operation + "`t" + $status + "`t" + (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
$operationStart = Get-Date
if ($operation.ToUpper().IndexOf("DOWNLOAD") -gt -1) {
Write-Host "Downloading Update: " -NoNewline
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Downloading Update: " -LogFilePath $LogFilePath
}
if ($operation.ToUpper().IndexOf("APPLY") -gt -1) {
Write-Host "Applying Update: " -NoNewline
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Applying Update: " -LogFilePath $LogFilePath
}
if ($operation.ToUpper().IndexOf("FINALIZE") -gt -1) {
Write-Host "Finalizing Update: " -NoNewline
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Applying Update: " -LogFilePath $LogFilePath
}
#Write-Host $displayValue
}
}
}
}
}
if ($allComplete) {
break;
}
if ($startTime -lt (Get-Date).AddHours(-$TimeOutInMinutes)) {
throw "Waiting for Update Timed-Out"
break;
}
Start-Sleep -Seconds 5
} while($true -eq $true)
$operationTime = getOperationTime -OperationStart $operationStart
$displayValue = ""
if ($cancelled) {
$displayValue = "CANCELLED`t" + $operationTime
} else {
if ($failure) {
$displayValue = "FAILED`t" + $operationTime
} else {
$displayValue = "COMPLETED`t" + $operationTime
}
}
Write-Host $displayValue
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError $displayValue -LogFilePath $LogFilePath
$totalOperationTime = getOperationTime -OperationStart $totalOperationStart
[bool]$UpdateCompleted = $true
if ($updateRunning) {
if ($failure) {
$UpdateCompleted = $false
Write-Host "Update Failed"
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Update Failed" -LogFilePath $LogFilePath
throw "Update Failed"
} else {
Write-Host "Update Completed - Total Time: $totalOperationTime"
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Update Completed - Total Time: $totalOperationTime" -LogFilePath $LogFilePath
}
} else {
$UpdateCompleted = $false
Write-Host "Update Not Running"
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Update Not Running" -LogFilePath $LogFilePath
}
return $UpdateCompleted
}
}
Function Get-OfficeCTRRegPath() {
$path15 = 'SOFTWARE\Microsoft\Office\15.0\ClickToRun'
$path16 = 'SOFTWARE\Microsoft\Office\ClickToRun'
if (Test-Path "HKLM:\$path16") {
return $path16
}
else {
if (Test-Path "HKLM:\$path15") {
return $path15
}
}
}
Function getOperationTime() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[DateTime] $OperationStart
)
$operationTime = ""
$dateDiff = NEW-TIMESPAN –Start $OperationStart –End (GET-DATE)
$strHours = formatTimeItem -TimeItem $dateDiff.Hours.ToString()
$strMinutes = formatTimeItem -TimeItem $dateDiff.Minutes.ToString()
$strSeconds = formatTimeItem -TimeItem $dateDiff.Seconds.ToString()
if ($dateDiff.Days -gt 0) {
$operationTime += "Days: " + $dateDiff.Days.ToString() + ":" + $strHours + ":" + $strMinutes + ":" + $strSeconds
}
if ($dateDiff.Hours -gt 0 -and $dateDiff.Days -eq 0) {
if ($operationTime.Length -gt 0) { $operationTime += " " }
$operationTime += "Hours: " + $strHours + ":" + $strMinutes + ":" + $strSeconds
}
if ($dateDiff.Minutes -gt 0 -and $dateDiff.Days -eq 0 -and $dateDiff.Hours -eq 0) {
if ($operationTime.Length -gt 0) { $operationTime += " " }
$operationTime += "Minutes: " + $strMinutes + ":" + $strSeconds
}
if ($dateDiff.Seconds -gt 0 -and $dateDiff.Days -eq 0 -and $dateDiff.Hours -eq 0 -and $dateDiff.Minutes -eq 0) {
if ($operationTime.Length -gt 0) { $operationTime += " " }
$operationTime += "Seconds: " + $strSeconds
}
return $operationTime
}
Function formatTimeItem() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $TimeItem = ""
)
[string]$returnItem = $TimeItem
if ($TimeItem.Length -eq 1) {
$returnItem = "0" + $TimeItem
}
return $returnItem
}
Function Test-UpdateSource() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $UpdateSource = $NULL,
[Parameter()]
[string]$LogFilePath
)
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
$uri = [System.Uri]$UpdateSource
[bool]$sourceIsAlive = $false
if($uri.Host){
$sourceIsAlive = Test-Connection -Count 1 -computername $uri.Host -Quiet
}else{
$sourceIsAlive = Test-Path $uri.LocalPath -ErrorAction SilentlyContinue
}
if ($sourceIsAlive) {
$sourceIsAlive = Validate-UpdateSource -UpdateSource $UpdateSource
}
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "sourceIsAlive set to $sourceIsAlive" -LogFilePath $LogFilePath
return $sourceIsAlive
}
Function Test-Url() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $Url = $NULL
)
# First we create the request.
$HTTP_Request = [System.Net.WebRequest]::Create($Url)
# We then get a response from the site.
$HTTP_Response = $HTTP_Request.GetResponse()
# We then get the HTTP code as an integer.
$HTTP_Status = [int]$HTTP_Response.StatusCode
# Finally, we clean up the http request by closing it.
$HTTP_Response.Close()
If ($HTTP_Status -eq 200) {
return $true
}
Else {
return $false
}
}
function Test-ItemPathUNC() {
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[String]$Path,
[Parameter()]
[String]$FileName = $null
)
Process {
$pathExists = $false
if ($FileName) {
$filePath = "$Path\$FileName"
$pathExists = [System.IO.File]::Exists($filePath)
} else {
$pathExists = [System.IO.Directory]::Exists($Path)
if (!($pathExists)) {
$pathExists = [System.IO.File]::Exists($Path)
}
}
return $pathExists;
}
}
Function Validate-UpdateSource() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $UpdateSource = $NULL,
[Parameter()]
[string] $OfficeClientEdition,
[Parameter()]
[string] $Bitness = "x86",
[Parameter()]
[string[]] $OfficeLanguages = $null,
[Parameter()]
[bool]$ShowMissingFiles = $true,
[Parameter()]
[string]$LogFilePath
)
Set-Alias -name LINENUM -value Get-CurrentLineNumber
$currentFileName = Get-CurrentFileName
if(!$OfficeClientEdition)
{
#checking if office client edition is null, if not, set bitness to client office edition
}
else
{
$Bitness = $OfficeClientEdition
}
[bool]$validUpdateSource = $true
[string]$cabPath = ""
if ($UpdateSource) {
$mainRegPath = Get-OfficeCTRRegPath
if ($mainRegPath) {
$configRegPath = $mainRegPath + "\Configuration"
$currentplatform = (Get-ItemProperty HKLM:\$configRegPath -Name Platform -ErrorAction SilentlyContinue).Platform
$updateToVersion = (Get-ItemProperty HKLM:\$configRegPath -Name UpdateToVersion -ErrorAction SilentlyContinue).UpdateToVersion
$llcc = (Get-ItemProperty HKLM:\$configRegPath -Name ClientCulture -ErrorAction SilentlyContinue).ClientCulture
}
$currentplatform = $Bitness
$mainCab = "$UpdateSource\Office\Data32.cab"
$bitness = "32"
if ($currentplatform -eq "x64") {
$mainCab = "$UpdateSource\Office\Data64.cab"
$bitness = "64"
}
if (!($updateToVersion)) {
$cabXml = Get-CabVersion -FilePath $mainCab
if ($cabXml) {
$updateToVersion = $cabXml.Version.Available.Build
}
}
[xml]$xml = Get-ChannelXml
if ($OfficeLanguages) {
$languages = $OfficeLanguages
} else {
$languages = Get-InstalledLanguages
}
$checkFiles = $xml.UpdateFiles.File | Where { $_.language -eq "0" }
foreach ($language in $languages) {
$checkFiles += $xml.UpdateFiles.File | Where { $_.language -eq $language.LCID}
}
foreach ($checkFile in $checkFiles) {
$fileName = $checkFile.name -replace "%version%", $updateToVersion
$relativePath = $checkFile.relativePath -replace "%version%", $updateToVersion
$fullPath = "$UpdateSource$relativePath$fileName"
if ($fullPath.ToLower().StartsWith("http")) {
$fullPath = $fullPath -replace "\", "/"
} else {
$fullPath = $fullPath -replace "/", "\"
}
$updateFileExists = $false
if ($fullPath.ToLower().StartsWith("http")) {
$updateFileExists = Test-URL -url $fullPath
} else {
if ($fullPath.StartsWith("\")) {
$updateFileExists = Test-ItemPathUNC -Path $fullPath
} else {
$updateFileExists = Test-Path -Path $fullPath
}
}
if (!($updateFileExists) -and ($checkFile.relativePath -notmatch "Experiment")) {
$fileExists = $missingFiles.Contains($fullPath)
if (!($fileExists)) {
$missingFiles.Add($fullPath)
if($ShowMissingFiles){
Write-Host "Source File Missing: $fullPath"
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Source File Missing: $fullPath" -LogFilePath $LogFilePath
}
Write-Log -Message "Source File Missing: $fullPath" -severity 1 -component "Office 365 Update Anywhere"
}
$validUpdateSource = $false
}
}
}
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "validUpdateSource set to $validUpdateSource" -LogFilePath $LogFilePath
return $validUpdateSource
}
Function Get-LatestVersion() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $UpdateURLPath,
[Parameter()]
[string]$LogFilePath
)
process {
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
[array]$totalVersion = @()
$Version = $null
$isUrl = $UpdateURLPath -like 'http*'
$tempUpdateURLPath = "$UpdateURLPath/Office/Data/v32.cab"
if ($isUrl) {
$cabXml = Get-UrlCabXml -UpdateURLPath $tempUpdateURLPath
if ($cabXml) {
$availNode = $cabXml.Version.Available
$currentVersion = $availNode.Build
if ($currentVersion) {
$Version = $currentVersion
}
}
} else {
$LatestBranchVersionPath = $UpdateURLPath + '\Office\Data'
if(Test-Path $LatestBranchVersionPath){
$DirectoryList = Get-ChildItem $LatestBranchVersionPath
Foreach($listItem in $DirectoryList){
if($listItem.GetType().Name -eq 'DirectoryInfo'){
$totalVersion+=$listItem.Name
}
}
}
$totalVersion = $totalVersion | Sort-Object -Descending
#sets version number to the newest version in directory for channel if version is not set by user in argument
if($totalVersion.Count -gt 0){
$Version = $totalVersion[0]
}
}
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Latest Version set to $Version" -LogFilePath $LogFilePath
return $Version
}
}
Function Get-PreviousVersion() {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $UpdateURLPath,
[Parameter()]
[string]$LogFilePath
)
process {
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
[array]$totalVersion = @()
$Version = $null
$LatestBranchVersionPath = $UpdateURLPath + '\Office\Data'
if(Test-Path $LatestBranchVersionPath){
$DirectoryList = Get-ChildItem $LatestBranchVersionPath
Foreach($listItem in $DirectoryList){
if($listItem.GetType().Name -eq 'DirectoryInfo'){
if ($listItem.Name -match '\d{2}\.\d\.\d{4}\.\d{4}') {
$totalVersion+=$listItem.Name
}
}
}
}
$totalVersion = $totalVersion | Sort-Object -Descending
#sets version number to the newest version in directory for channel if version is not set by user in argument
if($totalVersion.Count -gt 1){
$Version = $totalVersion[1]
} else {
return $null
}
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "Previous Version set to $Version" -LogFilePath $LogFilePath
return $Version
}
}
function Change-UpdatePathToChannel {
[CmdletBinding()]
param(
[Parameter()]
[string] $UpdatePath,
[Parameter()]
[string] $Channel,
[Parameter()]
[string]$LogFilePath
)
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
$newUpdatePath = $UpdatePath
$branchShortName = "DC"
if ($Channel.ToString().ToLower() -eq "current") {
$branchShortName = "CC"
}
if ($Channel.ToString().ToLower() -eq "firstreleasecurrent") {
$branchShortName = "FRCC"
}
if ($Channel.ToString().ToLower() -eq "firstreleasedeferred") {
$branchShortName = "FRDC"
}
if ($Channel.ToString().ToLower() -eq "deferred") {
$branchShortName = "DC"
}
if ($Channel.ToString().ToLower() -eq "monthlytargeted") {
$branchShortName = "MTC"
}
if ($Channel.ToString().ToLower() -eq "monthly") {
$branchShortName = "MC"
}
if ($Channel.ToString().ToLower() -eq "semiannualtargeted") {
$branchShortName = "SATC"
}
if ($Channel.ToString().ToLower() -eq "semiannual") {
$branchShortName = "SAC"
}
$channelNames = @("FRCC", "CC", "FRDC", "DC", "MTC", "MC", "SATC", "SAC")
$madeChange = $false
foreach ($channelName in $channelNames) {
if ($UpdatePath.ToUpper().EndsWith("\$channelName")) {
$newUpdatePath = $newUpdatePath -replace "\$channelName", "\$branchShortName"
$madeChange = $true
}
if ($UpdatePath.ToUpper().Contains("\$channelName\")) {
$newUpdatePath = $newUpdatePath -replace "\$channelName\", "\$branchShortName\"
$madeChange = $true
}
if ($UpdatePath.ToUpper().EndsWith("/$channelName")) {
$newUpdatePath = $newUpdatePath -replace "\/$channelName", "/$branchShortName"
$madeChange = $true
}
if ($UpdatePath.ToUpper().Contains("/$channelName/")) {
$newUpdatePath = $newUpdatePath -replace "\/$channelName\/", "/$branchShortName/"
$madeChange = $true
}
}
if (!($madeChange)) {
if ($newUpdatePath.Contains("/")) {
if ($newUpdatePath.EndsWith("/")) {
$newUpdatePath += "$branchShortName"
} else {
$newUpdatePath += "/$branchShortName"
}
}
if ($newUpdatePath.Contains("\")) {
if ($newUpdatePath.EndsWith("\")) {
$newUpdatePath += "$branchShortName"
} else {
$newUpdatePath += "\$branchShortName"
}
}
}
try {
$pathAlive = Test-UpdateSource -UpdateSource $newUpdatePath
} catch {
$pathAlive = $false
}
if ($pathAlive) {
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "newUpdatePath set to $newUpdatePath" -LogFilePath $LogFilePath
return $newUpdatePath
} else {
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "UpdatePath set to $UpdatePath" -LogFilePath $LogFilePath
return $UpdatePath
}
}
function Test-UpdateSourceTcpPort {
Param(
[parameter(ParameterSetName='URL', Position=0)]
[string]
$URL,
[parameter(ParameterSetName='IP', Position=0)]
[System.Net.IPAddress]
$IPAddress,
[parameter(Mandatory=$true , Position=1)]
[int]
$Port,
[parameter()]
[string]$UpdateSource = $null,
[Parameter()]
[string]$LogFilePath
)
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
$sourceIsAlive = $false
$RemoteServer = If ([string]::IsNullOrEmpty($URL)) {$IPAddress} Else {$URL};
$test = New-Object System.Net.Sockets.TcpClient;
Try
{
$test.Connect($RemoteServer, $Port);
$sourceIsAlive = $true
} Catch {}
Finally
{
$test.Close();
}
if ($sourceIsAlive) {
$sourceIsAlive = Validate-UpdateSource -UpdateSource $UpdateSource
}
WriteToLogFile -LNumber $(LINENUM) -FName $currentFileName -ActionError "sourceIsAlive set to $sourceIsAlive" -LogFilePath $LogFilePath
return $sourceIsAlive
}
function Detect-Channel {
param(
[Parameter()]
[string]$LogFilePath
)
Process {
$currentFileName = Get-CurrentFileName
Set-Alias -name LINENUM -value Get-CurrentLineNumber
$channelXml = Get-ChannelXml
$UpdateChannel = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration -Name UpdateChannel -ErrorAction SilentlyContinue).UpdateChannel
$GPOUpdatePath = (Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\office\16.0