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\Data 32.cab" $bitness = "32" if ($currentplatform -eq "x64") { $mainCab = "$UpdateSource\Office\Data 64.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