DSCResources/MSFT_SPProductUpdate/MSFT_SPProductUpdate.psm1
function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String] $SetupFile, [parameter(Mandatory = $false)] [System.Boolean] $ShutdownServices, [parameter(Mandatory = $false)] [ValidateSet("mon","tue","wed","thu","fri","sat","sun")] [System.String[]] $BinaryInstallDays, [parameter(Mandatory = $false)] [System.String] $BinaryInstallTime, [parameter(Mandatory = $false)] [ValidateSet("Present","Absent")] [System.String] $Ensure = "Present", [parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] $InstallAccount ) if ($Ensure -eq "Absent") { throw [Exception] "SharePoint does not support uninstalling updates." return } Write-Verbose -Message "Getting install status of SP binaries" $languagepack = $false $servicepack = $false $language = "" # Get file information from setup file if (-not(Test-Path $SetupFile)) { throw "Setup file cannot be found." } $setupFileInfo = Get-ItemProperty $SetupFile $fileVersion = $setupFileInfo.VersionInfo.FileVersion Write-Verbose -Message "Update has version $fileVersion" $products = Invoke-SPDSCCommand -Credential $InstallAccount -ScriptBlock { return Get-SPDscFarmProductsInfo } if ($setupFileInfo.VersionInfo.FileDescription -match "Language Pack") { Write-Verbose -Message "Update is a Language Pack Service Pack." # Retrieve language from file and check version for that language pack. $languagepack = $true # Extract language from filename if ($setupFileInfo.Name -match "\w*-(\w{2}-\w{2}).exe") { $language = $matches[1] } else { throw "Update does not contain the language code in the correct format." } try { $cultureInfo = New-Object -TypeName System.Globalization.CultureInfo -ArgumentList $language } catch { throw "Error while converting language information: $language" } # try/catch is required for some versions of Windows, other version use the LCID value of 4096 if ($cultureInfo.LCID -eq 4096) { throw "Error while converting language information: $language" } # Extract English name of the language code if ($cultureInfo.EnglishName -match "(\w*,*\s*\w*) \(\w*\)") { $languageEnglish = $matches[1] if ($languageEnglish.contains(",")) { $languages = $languageEnglish.Split(",") $languageEnglish = $languages[0] } } # Extract Native name of the language code if ($cultureInfo.NativeName -match "(\w*,*\s*\w*) \(\w*\)") { $languageNative = $matches[1] if ($languageNative.contains(",")) { $languages = $languageNative.Split(",") $languageNative = $languages[0] } } # Build language string used in Language Pack names $languageString = "$languageEnglish/$languageNative" Write-Verbose -Message "Update is for the $languageEnglish language" # Find the product name for the specific language pack $productName = "" foreach ($product in $products) { if ($product -match $languageString) { $productName = $product } } if ($productName -eq "") { throw "Error: Product for language $language is not found." } else { Write-Verbose -Message "Product found: $productName" } $versionInfo = Invoke-SPDSCCommand -Credential $InstallAccount ` -Arguments $productName ` -ScriptBlock { $productToCheck = $args[0] return Get-SPDscFarmVersionInfo -ProductToCheck $productToCheck } } elseif ($setupFileInfo.VersionInfo.FileDescription -match "Service Pack") { Write-Verbose -Message "Update is a Service Pack for SharePoint." # Check SharePoint version information. $servicepack = $true $versionInfo = Invoke-SPDSCCommand -Credential $InstallAccount ` -Arguments $productName ` -ScriptBlock { $productToCheck = $args[0] return Get-SPDscFarmVersionInfo -ProductToCheck "Microsoft SharePoint Server 2013" } } else { Write-Verbose -Message "Update is a Cumulative Update." # Cumulative Update is multi-lingual. Check version information of all products. $versionInfo = Invoke-SPDSCCommand -Credential $InstallAccount ` -Arguments $productName ` -ScriptBlock { $productToCheck = $args[0] return Get-SPDscFarmVersionInfo } } Write-Verbose -Message "The lowest version of any SharePoint component is $($versionInfo.Lowest)" if ($versionInfo.Lowest -lt $fileVersion) { # Version of SharePoint is lower than the patch version. Patch is not installed. return @{ SetupFile = $SetupFile ShutdownServices = $ShutdownServices BinaryInstallDays = $BinaryInstallDays BinaryInstallTime = $BinaryInstallTime Ensure = "Absent" } } else { # Version of SharePoint is equal or greater than the patch version. Patch is installed. return @{ SetupFile = $SetupFile ShutdownServices = $ShutdownServices BinaryInstallDays = $BinaryInstallDays BinaryInstallTime = $BinaryInstallTime 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 = $false)] [System.Boolean] $ShutdownServices, [parameter(Mandatory = $false)] [ValidateSet("mon","tue","wed","thu","fri","sat","sun")] [System.String[]] $BinaryInstallDays, [parameter(Mandatory = $false)] [System.String] $BinaryInstallTime, [parameter(Mandatory = $false)] [ValidateSet("Present","Absent")] [System.String] $Ensure = "Present", [parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Setting install status of SP Update binaries" if ($Ensure -eq "Absent") { throw [Exception] "SharePoint does not support uninstalling updates." return } # Check if setup file exists if (-not(Test-Path $SetupFile)) { throw "Setup file cannot be found." } $now = Get-Date if ($BinaryInstallDays) { # BinaryInstallDays parameter exists, check if current day is specified $currentDayOfWeek = $now.DayOfWeek.ToString().ToLower().Substring(0,3) if ($BinaryInstallDays -contains $currentDayOfWeek) { Write-Verbose -Message ("Current day is present in the parameter BinaryInstallDays. " + ` "Update can be run today.") } else { Write-Verbose -Message ("Current day is not present in the parameter BinaryInstallDays, " + ` "skipping the update") return } } else { Write-Verbose -Message "No BinaryInstallDays specified, Update can be ran on any day." } # Check if BinaryInstallTime parameter exists if ($BinaryInstallTime) { # Check if current time is inside of time window $upgradeTimes = $BinaryInstallTime.Split(" ") $starttime = 0 $endtime = 0 if ($upgradeTimes.Count -ne 3) { throw "Time window incorrectly formatted." } else { if ([datetime]::TryParse($upgradeTimes[0],[ref]$starttime) -ne $true) { throw "Error converting start time" } if ([datetime]::TryParse($upgradeTimes[2],[ref]$endtime) -ne $true) { throw "Error converting end time" } if ($starttime -gt $endtime) { throw "Error: Start time cannot be larger than end time" } } if (($starttime -lt $now) -and ($endtime -gt $now)) { Write-Verbose -Message ("Current time is inside of the window specified in " + ` "BinaryInstallTime. Starting update") } else { Write-Verbose -Message ("Current time is outside of the window specified in " + ` "BinaryInstallTime, skipping the update") return } } else { Write-Verbose -Message ("No BinaryInstallTime specified, Update can be ran at " + ` "any time. Starting update.") } # To prevent an endless loop: Check if an upgrade is required. if ((Get-SPDSCInstalledProductVersion).FileMajorPart -eq 15) { $wssRegKey ="hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\15.0\WSS" } else { $wssRegKey ="hklm:SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\16.0\WSS" } # Read LanguagePackInstalled and SetupType registry keys $languagePackInstalled = Get-SPDSCRegistryKey -Key $wssRegKey -Value "LanguagePackInstalled" $setupType = Get-SPDSCRegistryKey -Key $wssRegKey -Value "SetupType" # Determine if LanguagePackInstalled=1 or SetupType=B2B_Upgrade. # If so, the Config Wizard is required, so the installation will be skipped. if (($languagePackInstalled -eq 1) -or ($setupType -eq "B2B_UPGRADE")) { Write-Verbose -Message ("An upgrade is pending. " + ` "To prevent a possible loop, the install will be skipped") return } if ($ShutdownServices) { Write-Verbose -Message "Stopping services to speed up installation process" $searchPaused = $false $osearchStopped = $false $hostControllerStopped = $false $osearchSvc = Get-Service -Name "OSearch15" $hostControllerSvc = Get-Service -Name "SPSearchHostController" $result = Invoke-SPDSCCommand -Credential $InstallAccount ` -ScriptBlock { $searchPaused = $true $searchSAs = Get-SPEnterpriseSearchServiceApplication foreach ($searchSA in $searchSAs) { $searchSA.Pause() } } if($osearchSvc.Status -eq "Running") { $osearchStopped = $true Set-Service -Name "OSearch15" -StartupType Disabled $osearchSvc.Stop() } if($hostControllerSvc.Status -eq "Running") { $hostControllerStopped = $true Set-Service "SPSearchHostController" -StartupType Disabled $hostControllerSvc.Stop() } $hostControllerSvc.WaitForStatus('Stopped','00:01:00') Write-Verbose -Message "Search Services are stopped" Write-Verbose -Message "Stopping other services" Set-Service -Name "IISADMIN" -StartupType Disabled Set-Service -Name "SPTimerV4" -StartupType Disabled $iisreset = Start-Process -FilePath "iisreset.exe" ` -ArgumentList "-stop -noforce" ` -Wait ` -PassThru $timerSvc = Get-Service -Name "SPTimerV4" if($timerSvc.Status -eq "Running") { $timerSvc.Stop() } } Write-Verbose -Message "Beginning installation of the SharePoint update" $result = Invoke-SPDSCCommand -Credential $InstallAccount ` -Arguments $SetupFile ` -ScriptBlock { $setupFile = $args[0] $setup = Start-Process -FilePath $setupFile ` -ArgumentList "/quiet /passive" ` -Wait ` -PassThru # Error codes: https://aka.ms/installerrorcodes switch ($setup.ExitCode) { 0 { Write-Verbose -Message "SharePoint update binary installation complete." } 17022 { Write-Verbose -Message ("SharePoint update binary installation complete, " + ` "however a reboot is required.") $global:DSCMachineStatus = 1 } Default { throw ("SharePoint update install failed, exit code was $($setup.ExitCode). " + ` "Error codes can be found at https://aka.ms/installerrorcodes") } } } if ($ShutdownServices) { Write-Verbose -Message "Restart stopped services" Set-Service -Name "SPTimerV4" -StartupType Automatic Set-Service -Name "IISADMIN" -StartupType Automatic $timerSvc = Get-Service -Name "SPTimerV4" $timerSvc.Start() $iisreset = Start-Process -FilePath "iisreset.exe" ` -ArgumentList "-start" ` -Wait ` -PassThru $osearchSvc = Get-Service -Name "OSearch15" $hostControllerSvc = Get-Service -Name "SPSearchHostController" # Ensuring Search Services were stopped by script before Starting" if($osearchStopped -eq $true) { Set-Service -Name "OSearch15" -StartupType Manual $osearchSvc.Start() } if($hostControllerStopped -eq $true) { Set-Service "SPSearchHostController" -StartupType Automatic $hostControllerSvc.Start() } # Resuming Search Service Application if paused### $result = Invoke-SPDSCCommand -Credential $InstallAccount ` -ScriptBlock { if($searchPaused -eq $true) { $searchSAs = Get-SPEnterpriseSearchServiceApplication foreach ($searchSA in $searchSAs) { $searchSA.Resume() } } } Write-Verbose -Message "Services restarted." } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String] $SetupFile, [parameter(Mandatory = $false)] [System.Boolean] $ShutdownServices, [parameter(Mandatory = $false)] [ValidateSet("mon","tue","wed","thu","fri","sat","sun")] [System.String[]] $BinaryInstallDays, [parameter(Mandatory = $false)] [System.String] $BinaryInstallTime, [parameter(Mandatory = $false)] [ValidateSet("Present","Absent")] [System.String] $Ensure = "Present", [parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] $InstallAccount ) Write-Verbose -Message "Testing install status of SP Update binaries" $PSBoundParameters.Ensure = $Ensure if ($Ensure -eq "Absent") { throw [Exception] "SharePoint does not support uninstalling updates." return } $CurrentValues = Get-TargetResource @PSBoundParameters return Test-SPDscParameterState -CurrentValues $CurrentValues ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @("Ensure") } Export-ModuleMember -Function *-TargetResource |