DSCResources/MSFT_OfficeOnlineServerProductUpdate/MSFT_OfficeOnlineServerProductUpdate.psm1
$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' $script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'OfficeOnlineServerDsc.Util' Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'OfficeOnlineServerDsc.Util.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_OfficeOnlineServerProductUpdate' $script:OOSDscRegKey = "HKLM:\SOFTWARE\OOSDsc" function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [System.String] $SetupFile, [Parameter(Mandatory = $true)] [System.String[]] $Servers, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) if ($Ensure -eq "Absent") { throw [Exception] "Office Online Server does not support uninstalling updates." } Write-Verbose -Message "Getting install status of OOS binaries" Write-Verbose -Message $script:localizedData.CheckIfFileExists if (-not(Test-Path -Path $SetupFile)) { throw "Setup file cannot be found: {$SetupFile}" } if ($Servers -notcontains $env:COMPUTERNAME) { throw "Parameter Servers should contain the current server name {$env:COMPUTERNAME}" } Write-Verbose -Message "Checking file status of $SetupFile" $checkBlockedFile = $true if (Split-Path -Path $SetupFile -IsAbsolute) { $driveLetter = (Split-Path -Path $SetupFile -Qualifier).TrimEnd(":") Write-Verbose -Message "SetupFile refers to drive $driveLetter" $volume = Get-Volume -DriveLetter $driveLetter -ErrorAction SilentlyContinue if ($null -ne $volume) { if ($volume.DriveType -ne "CD-ROM") { Write-Verbose -Message $script:localizedData.VolumeIsFixedDrive } else { Write-Verbose -Message $script:localizedData.VolumeIsCDDrive $checkBlockedFile = $false } } else { Write-Verbose -Message $script:localizedData.VolumeNotFound } } if ($checkBlockedFile -eq $true) { Write-Verbose -Message $script:localizedData.CheckingStatus $zone = Get-Item -Path $SetupFile -Stream "Zone.Identifier" -EA SilentlyContinue if ($null -ne $zone) { throw ("Setup file is blocked! Please use 'Unblock-File -Path $SetupFile' " + ` "to unblock the file before continuing.") } Write-Verbose -Message $script:localizedData.FileNotBlocked } Write-Verbose -Message $script:localizedData.GetFileInfo $setupFileInfo = Get-ItemProperty -Path $SetupFile $fileVersion = $setupFileInfo.VersionInfo.FileVersion Write-Verbose -Message "Update has version $fileVersion" $fileVersionInfo = New-Object -TypeName System.Version -ArgumentList $fileVersion Write-Verbose -Message $script:localizedData.GetOOSInfo $OfficeVersionInfoFile = "C:\ProgramData\Microsoft\OfficeWebApps\Data\local\OfficeVersion.inc" if ((Test-Path -Path $OfficeVersionInfoFile) -eq $false) { $OfficeVersionInfoFile = "C:\Program Files\Microsoft Office Web Apps\AgentManager\OfficeVersion.inc" if ((Test-Path -Path $OfficeVersionInfoFile) -eq $false) { throw "Cannot find file $OfficeVersionInfoFile" } } $OfficeVersionInfo = Get-Content -Path $OfficeVersionInfoFile -Raw if ($OfficeVersionInfo -match "RMJ = ([0-9]*)\r*\nRMM = ([0-9]*)\r*\nRUP = ([0-9]*)\r*\nRPR = ([0-9]*)") { [System.Version]$versionInfo = $matches[1] + "." + $matches[2] + "." + $matches[3] + "." + $matches[4] } else { throw "Cannot read Version information from file $OfficeVersionInfoFile" } Write-Verbose -Message "The version of Office Online Server is $($versionInfo)" if ($versionInfo -lt $fileVersionInfo) { # Version of OfficeOnlineServer is lower than the patch version. Patch is not installed. return @{ SetupFile = $SetupFile Ensure = "Absent" } } else { # Version of OfficeOnlineServer is equal or greater than the patch version. Patch is installed. return @{ SetupFile = $SetupFile Ensure = "Present" } } } function Set-TargetResource { # Supressing the global variable use to allow passing DSC the reboot message [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] param ( [Parameter(Mandatory = $true)] [System.String] $SetupFile, [Parameter(Mandatory = $true)] [System.String[]] $Servers, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Setting install status of OOS Update binaries" if ($Ensure -eq "Absent") { throw [Exception] "Office Online Server does not support uninstalling updates." } Write-Verbose -Message "Check if the setup file exists" if (-not(Test-Path -Path $SetupFile)) { throw "Setup file cannot be found: {$SetupFile}" } if ($Servers -notcontains $env:COMPUTERNAME) { throw "Parameter Servers should contain the current server name {$env:COMPUTERNAME}" } $OfficeVersionInfoFile = "C:\ProgramData\Microsoft\OfficeWebApps\Data\local\OfficeVersion.inc" if ((Test-Path -Path $OfficeVersionInfoFile) -eq $false) { $OfficeVersionInfoFile = "C:\Program Files\Microsoft Office Web Apps\AgentManager\OfficeVersion.inc" if ((Test-Path -Path $OfficeVersionInfoFile) -eq $false) { throw "Cannot find file $OfficeVersionInfoFile. Is Office Online Server installed?" } } Write-Verbose -Message "Checking file status of $SetupFile" $checkBlockedFile = $true if (Split-Path -Path $SetupFile -IsAbsolute) { $driveLetter = (Split-Path -Path $SetupFile -Qualifier).TrimEnd(":") Write-Verbose -Message "SetupFile refers to drive $driveLetter" $volume = Get-Volume -DriveLetter $driveLetter -ErrorAction SilentlyContinue if ($null -ne $volume) { if ($volume.DriveType -ne "CD-ROM") { Write-Verbose -Message $script:localizedData.VolumeIsFixedDrive } else { Write-Verbose -Message $script:localizedData.VolumeIsCDDrive $checkBlockedFile = $false } } else { Write-Verbose -Message $script:localizedData.VolumeNotFound } } if ($checkBlockedFile -eq $true) { Write-Verbose -Message $script:localizedData.CheckingStatus $zone = Get-Item -Path $SetupFile -Stream "Zone.Identifier" -EA SilentlyContinue if ($null -ne $zone) { throw ("Setup file is blocked! Please use 'Unblock-File -Path $SetupFile' " + ` "to unblock the file before continuing.") } Write-Verbose -Message $script:localizedData.FileNotBlocked } Write-Verbose -Message $script:localizedData.GetFileInfo $setupFileInfo = Get-ItemProperty -Path $SetupFile $fileVersion = $setupFileInfo.VersionInfo.FileVersion Write-Verbose -Message "Update has version $fileVersion" $PatchVersion = New-Object -TypeName System.Version -ArgumentList $fileVersion Write-Verbose -Message $script:localizedData.GetOOSInfo $serversInfo = Get-ServerInfo -Servers $Servers # Get current server info $currentServer = $serversInfo | Where-Object -FilterScript { $_.Name -eq $env:COMPUTERNAME } if ($null -ne $currentServer.MasterMachine) { if ($env:COMPUTERNAME -eq $currentServer.MasterMachine) { # Server is MasterMachine if ($currentServer.Machines.Count -gt 1) { Write-Verbose -Message "There are $($currentServer.Machines.Count - 1) more servers in the farm." Write-Verbose -Message "Waiting for all other servers to be patched first." $count = 0 $maxcount = 30 $machinesCount = (Get-OfficeWebAppsFarm).Machines.Count while (($count -lt $maxCount) -and ($machinesCount -gt 1)) { Write-Verbose -Message ("$([DateTime]::Now.ToShortTimeString()) - " + ` "Waiting for other OOS servers to be patched first " + ` "(waited $count of $maxCount minutes)") Write-Verbose -Message "$($machinesCount -1) other servers still need to be patched" Start-Sleep -Seconds 60 $machinesCount = (Get-OfficeWebAppsFarm).Machines.Count $count++ } if ($machinesCount -gt 1) { throw ("[ERROR] Cannot complete patching. Other servers need to be patched first. " + ` "Waited $maxcount minutes for other servers complete patching.") } } Write-Verbose -Message "Current server is the last server in the OOS farm" Write-Verbose -Message "Remove server from OOS farm and install OOS patch" # Determine new MasterMachine (one of the already patched servers) $patchedMachines = $serversInfo | Where-Object -FilterScript { $_.Version -eq $PatchVersion } if ($null -eq $patchedMachines) { # No other machines are patched yet, probably single server. Set current machine to newMaster $newMaster = $env:COMPUTERNAME } else { # Determine newMaster from other machines $newMaster = $patchedMachines.MasterMachine | Sort-Object | Select-Object -Unique if ($newMaster -is [System.Array]) { Write-Verbose -Message "WARNING: Multiple masters found" $newMaster = $newMaster | Select-Object -First } elseif ($null -eq $newMaster) { throw "Unable to determine new master. Cannot continue patching server." } } Write-Verbose -Message "Determined the following server to be the new MasterMachine {$newMaster}" if ($currentServer.Version -lt $PatchVersion) { Write-Verbose -Message "Current server is running an older path level, patch required" Set-OOSDscRegKeys -NewMaster $newMaster -Config $currentServer.Config -Roles $currentServer.Roles Write-Verbose -Message "Removing server from OOS farm" Remove-OfficeWebAppsMachine Write-Verbose -Message "Installing OOS Patch" Install-OOSDscPatch -SetupFile $setupFile Write-Verbose -Message "Rebooting server to complete installation" $global:DSCMachineStatus = 1 } else { Write-Verbose -Message "Server already on correct patch level" } } else { Write-Verbose -Message "Current server is not the MasterMachine" # Determine new MasterMachine (one of the already patched servers) $patchedMachines = $serversInfo | Where-Object -FilterScript { $_.Version -eq $PatchVersion } if ($null -eq $patchedMachines) { # No other machines are patched yet, probably single server. Set current machine to newMaster $newMaster = $env:COMPUTERNAME } else { # Determine newMaster from other machines $newMaster = $patchedMachines.NewMaster | Sort-Object | Select-Object -Unique if ($newMaster -is [System.Array]) { Write-Verbose -Message "WARNING: Multiple masters found" $newMaster = $newMaster | Select-Object -First } } Write-Verbose -Message "Determined the following server to be the new MasterMachine {$newMaster}" if ($currentServer.Version -lt $PatchVersion) { Write-Output "Current version is running an older path level, patch required" Set-OOSDscRegKeys -NewMaster $newMaster -Config $currentServer.Config -Roles $currentServer.Roles Write-Verbose -Message "Removing server from OOS farm" Remove-OfficeWebAppsMachine Write-Verbose -Message "Installing OOS Patch" Install-OOSDscPatch -SetupFile $setupFile Write-Verbose -Message "Rebooting server to complete installation" $global:DSCMachineStatus = 1 } else { Write-Verbose -Message "Server already on correct patch level" } } } else { # Current server is not joined to a farm $resume = $false if (Test-Path -Path $OOSDscRegKey) { # Get NewMaster item $key = Get-Item -Path $OOSDscRegKey $state = $key.GetValue("State") if ($state -eq "Patching") { $resume = $true } } # Check if the patch is already installed if ($resume -eq $false) { Write-Verbose -Message "Server does not have the patch installed yet." Write-Verbose -Message "Probably new server which is not yet joined to a farm." Write-Verbose -Message "Installing OOS Patch" Install-OOSDscPatch -SetupFile $setupFile Write-Verbose -Message "Rebooting server to complete installation" $global:DSCMachineStatus = 1 } else { Write-Verbose -Message "Server is continuing after the required reboot." Write-Verbose -Message "Check if current machine needs to create new farm" if ($currentServer.Name -eq $currentServer.NewMaster) { Write-Verbose -Message "This server is the new Master and needs to create a new farm" Write-Verbose -Message "Read required config from registry" $config = $key.GetValue("Config") $objParams = $config | ConvertFrom-Json $params = @{ } $objParams | Get-Member -MemberType *Property | ForEach-Object -Process { if ([string]::IsNullOrEmpty($objParams.($_.name)) -eq $false) { $params.($_.name) = $objParams.($_.name) } } Write-Verbose -Message "Create new farm based on the old config" $null = New-OfficeWebAppsFarm @params -Force } else { Write-Verbose -Message "This server needs to join an existing farm" Write-Verbose -Message "Read server roles from registry" $roles = $key.GetValue("Roles") -split "," $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain Write-Verbose -Message "Creating new farm with Master server $($currentServer.NewMaster).$domain and roles $($roles -join ", ")" New-OfficeWebAppsMachine -MachineToJoin "$($currentServer.NewMaster).$domain" -Roles $roles } Write-Verbose -Message "Cleaning up registry data" Remove-Item -Path $OOSDscRegKey -ErrorAction SilentlyContinue } } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String] $SetupFile, [Parameter(Mandatory = $true)] [System.String[]] $Servers, [Parameter()] [ValidateSet("Present", "Absent")] [System.String] $Ensure = "Present", [Parameter()] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Testing install status of OOS Update binaries" $PSBoundParameters.Ensure = $Ensure if ($Ensure -eq "Absent") { throw [Exception] "Office Online Server does not support uninstalling updates." return } # Check if server is continuing after a reboot if (Test-Path -Path $OOSDscRegKey) { $key = Get-Item -Path $OOSDscRegKey $state = $key.GetValue("State") if ($state -eq "Patching") { Write-Verbose -Message "Server continuing after required reboot after patching." Write-Verbose -Message "Returning False." return $false } } $CurrentValues = Get-TargetResource @PSBoundParameters Write-Verbose -Message "Current Values: $(Convert-OosDscHashtableToString -Hashtable $CurrentValues)" Write-Verbose -Message "Target Values: $(Convert-OosDscHashtableToString -Hashtable $PSBoundParameters)" return Test-OosDscParameterState -CurrentValues $CurrentValues ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @("Ensure") } Export-ModuleMember -Function *-TargetResource function Get-ServerInfo { [OutputType([System.Array])] param ( [Parameter(Mandatory = $true)] [System.String[]] $Servers ) $serversInfo = @() foreach ($server in $Servers) { $serversInfo += Invoke-Command -ComputerName $server -ScriptBlock { $returnval = @{ Name = $env:COMPUTERNAME } $OfficeVersionInfoFile = "C:\ProgramData\Microsoft\OfficeWebApps\Data\local\OfficeVersion.inc" if ((Test-Path -Path $OfficeVersionInfoFile) -eq $false) { $OfficeVersionInfoFile = "C:\Program Files\Microsoft Office Web Apps\AgentManager\OfficeVersion.inc" } if ((Test-Path -Path $OfficeVersionInfoFile) -eq $true) { $OfficeVersionInfo = Get-Content -Path $OfficeVersionInfoFile -Raw if ($OfficeVersionInfo -match "RMJ = ([0-9]*)\r\nRMM = ([0-9]*)\r\nRUP = ([0-9]*)\r\nRPR = ([0-9]*)") { [System.Version]$versionInfo = $matches[1] + "." + $matches[2] + "." + $matches[3] + "." + $matches[4] } else { [System.Version]$versionInfo = "0.0.0.0" } } else { [System.Version]$versionInfo = "0.0.0.0" } $returnval.Version = $versionInfo try { $oosFarm = Get-OfficeWebAppsFarm -ErrorAction SilentlyContinue if ($null -ne $oosFarm) { $oosMachine = Get-OfficeWebAppsMachine $returnval.Machines = $oosFarm.Machines.MachineName $returnval.MasterMachine = $oosMachine.MasterMachineName $returnval.Roles = $oosMachine.Roles -join "," # Retrieving OOS Farm properties and convert to JSON $objProps = $oosFarm | Select-Object -Property * -ExcludeProperty ExcelEnableCrossForestKerberosAuthentication, Machines, OfficeAddinEnabled, OnlinePictureEnabled, OnlineVideoEnabled $jsonProps = $objProps | ConvertTo-Json $returnval.Config = $jsonProps } else { $returnval.Machines = $null $returnval.MasterMachine = $null } } catch { $returnval.Machines = $null $returnval.MasterMachine = $null } # Check if NewMaster OOSDscRegKey has been created $OOSDscRegKey = "HKLM:\SOFTWARE\OOSDsc" if (Test-Path -Path $OOSDscRegKey) { # Get NewMaster item $key = Get-Item $OOSDscRegKey $returnval.NewMaster = $key.GetValue("NewMaster") } else { $returnval.NewMaster = $null } return $returnval } } return $serversInfo } function Set-OOSDscRegKeys { param ( [Parameter(Mandatory = $true)] [System.String] $NewMaster, [Parameter(Mandatory = $true)] [System.String] $Config, [Parameter(Mandatory = $true)] [System.String] $Roles ) # Testing OOSDsc reg key if ((Test-Path -Path $OOSDscRegKey) -eq $false) { $null = New-Item -Path $OOSDscRegKey } # Set "Patching" registry key $null = New-ItemProperty -Path $OOSDscRegKey -Name State -PropertyType String -Value "Patching" -Force # Set "NewMaster" registry key to $newMaster $null = New-ItemProperty -Path $OOSDscRegKey -Name NewMaster -PropertyType String -Value $NewMaster -Force # Set "Config" registry key to $newMaster $null = New-ItemProperty -Path $OOSDscRegKey -Name Config -PropertyType String -Value $Config -Force # Set "Roles" registry key to $newMaster $null = New-ItemProperty -Path $OOSDscRegKey -Name Roles -PropertyType String -Value $Roles -Force } function Install-OOSDscPatch { param ( [Parameter(Mandatory = $true)] [System.String] $SetupFile ) Write-Verbose -Message "Beginning installation of the OOS update" Write-Verbose -Message $script:localizedData.CheckIfUNC $uncInstall = $false if ($SetupFile.StartsWith("\\")) { Write-Verbose -Message $script:localizedData.PathIsUNC $uncInstall = $true if ($SetupFile -match "\\\\(.*?)\\.*") { $serverName = $Matches[1] } else { throw "Cannot extract servername from UNC path. Check if it is in the correct format." } Set-OosDscZoneMap -Server $serverName } $setup = Start-Process -FilePath $SetupFile ` -ArgumentList "/quiet /passive" ` -Wait ` -PassThru if ($uncInstall -eq $true) { Write-Verbose -Message $script:localizedData.RemoveUNCPath Remove-OosDscZoneMap -ServerName $serverName } # Error codes: https://aka.ms/installerrorcodes switch ($setup.ExitCode) { 0 { Write-Verbose -Message $script:localizedData.InstallSucceeded } 17022 { Write-Verbose -Message $script:localizedData.RebootRequired } 17025 { Write-Verbose -Message $script:localizedData.AlreadyInstalled } Default { throw ("Office Online Server update install failed, exit code was $($setup.ExitCode). " + ` "Error codes can be found at https://aka.ms/installerrorcodes") } } } |