SPProductUpdate: Add possibility to use Zero-Downtime Patching with SPDsc
@andikrueger and I have been discussing the possibility to patch SharePoint using Zero-Downtime Patching with SharePointDsc. Since this requires directing traffic to other servers in the SharePoint farm, this will require implementing a solution to make sure the server is not serving traffic.
We both see that a lot of companies don't configure their load balancing the proper way. Their appliances only ping the SharePoint WFE IP addresses. If the IIS services are stopped or not responding (intentionally or because of some sort of issue), the server doesn't serve any requests, but the load balancer still gets a ping response and considers the server as operational.
Therefore load balancer solutions should at least check for HTTP status codes or better yet, retrieve a page and check for specific content every 10 to 15 seconds.
If this is the case, there is a possibility to do ZDP with SPDsc:
- Prepare WFE by fooling the LB by either: a. Create a IIS Rewrite rule, to send out 503 status codes b. Changing the IP of the SharePoint Web application binding - changing the HTTP listening port c. Setting a firewall rule, that blocks all traffic coming from the load balancer IP (which is the easiest to create) d. Create a new IIS Web Application that catches all the traffic to that server and does a redirect to the IP address of any other WFE (with the host-header attached) e. Have a PowerShell script check the local web applications and create an HTML file in an anonymous web site. If the checks are good, the file contains the text "Ok". If the checks fail, the file contains the text "Failed". The LB checks this HTML file and depending on the result, considers the server as operational or not. f. Any other ways to temporally disable this WFE from taking request issued by the LB.
- Now the LB should route the traffic to the remaining WFEs.
- Enable Side-by-side on WFE 1
- Install the patches
- Reboot the server
- Disable the LB fooling solution on this WFE
- Prepare WFE 2 by using either method mentioned in 1.
- Proceed with the patching on this server, etc
We are curious what other think of this solution? If there are any other ideas that might be better as the ones suggested above.
Assuming, we do have a proper LB solution in place, there would still be the need to create a new SPZeroDownTimePatching resource. This resource would be needed two times per WFE per configuration to enable side-by-side and disable side-by-side properly.
I do not agree with creating a new resource. The reason for this is that one resource would have the enabled state and the other the disabled state, so these will always be in conflict with each other and the server will never be in a compliant state. Each time a configuration is applied, the first resource would enabled SbS and the second would disable it again. If "Apply and Autocorrect" is configured, this would constantly enabled/disabled during each consistency run.
I rather would extend the SPProductUpdate resource to enable side-by-side at the beginning of the set method and to disable it at the end of the set method.
Wouldn’t extending the Set-Method lead to one WFE being more up-to-date than another (not yet patched) WFE and in this way impact the user experience?
I did some drawing this morning. The following picture shows the several steps needed to patch a 2 WFE with several App Servers Farm with DSC and ZDP.

Following color codes for the drawing:
- red - could be problematic in one configuration as these resources would conflict (assuming to use NetworkingDsc with the Firewall Resource)
- blue - these resources could be challenging.
- Reboot: Reboot should onlz occure, if needed. Additional Parameters should be used: https://powershell.org/forums/topic/handling-reboots-in-dsc/
- SPConfigWizard should only run, if really needed. @ykuijs Is this already the case?
I have thought about this and the best thought I had was a script wrapper around the DSC resources to achieve the goal. I thought the enabling of SideBySide was best handled with a Function as well as setting the proper version. Then for dealing with the farm to do true ZDP, you can only execute on half the farm resources. I worked with Nik and this seems feasible within my DSC script to identify the Nodes AllNodes = @( @{ NodeName = "Servername-CA" CertificateFile = "\FAAMECK003\E$\DSCConfig\1Certificate\DSCAuthor.cer" Thumbprint = "?97815e8e2c4906e5b22bae09eaf3aec0e801f432" PSDscAllowDomainUser = $true Role = "WebSet1" }
The Role allows me to have a set of WFE, App, and DC servers. Then when calling the upgrade installation and such you just have
Node $AllNodes.Where{$_.Role -eq $NodeType}.NodeName The $NodeType is passed or changed in the wrapper for which "SET" of WFE's to do, Set1 or Set2, so that the DSC execution is controlled and operating on exactly the half of the farm controlled by the wrapper. All of this to be run from an external server to the farm so that each server could be rebooted without losing context of where the upgrade process was.
Apologies if this is considered insane, I just was trying to figure out a method to do it on my own, I should have checked here sooner ..
Hi @jimbrayDel Not entirely sure what you mean here. Can you elaborate a little more?
As I see it @jimbrayDel would introduce a new key-value pair Role="WebSet1" or Role="WebSet2" for every node. With having this information in the configuration file, we would be able to handle the patch process more efficiently, as we would be able to identify the farm servers more easily, that needs to be patched simultaneously.
Using Role is ambiguous, as it it conflicts with the MinRoles. I would use ServerGroup as Key.
@andikrueger Exactly what I was trying to say, I agree with your thought that Role could be confusing, and ServerGroup would work just fine. The Goal I am working on is to have the ZDP be completely automated without any manual intervention required.
@ykuijs @andikrueger Trying to make this as simple as possible.
- Don't really care which server is WFE. If node is performing SPProductUpdate, block load balancer IP with Firewall Rule. When SPProductUpdate is done, return to load balancer.
- Make sure EnableSideBySide = $true for all WebApplications (This never gets set to $false)
- Assume redundant environment. Must have 2 Servers for each role or role group.
- Can be done with Script Resource and WaitForAll Resource
- Requires Admin to enter IP of load balancer in ConfigData section; Everything else is automatic
- Assumed Environment where ServerGroup # is in the node name, i.e., SPWEBFE1 is ServerGroup1 and SPWEBFE2 is ServerGroup2 (Easily changed).
Steps:
- Server #1 of the role gets placed into ServerGroup1, Server #2 of the role gets placed into ServerGroup2.
- Remove ServerGroup1 from load balancer
- SPProductUpdate ServerGroup1
- Return ServerGroup1 to load balancer
- Remove ServerGroup2 from load balancer
- SPProductUpdate ServerGroup2
- Return ServerGroup2 to load balancer
- PSConfigWizard ServerGroup1
- PSConfigWizard ServerGroup2
- Set SideBySideToken = highest # folder name in hive on all servers (foreach WebApplication)
Configuration SPFarmUpdateZDT
{
$CredsSPFarm = Get-Credential -Message "Farm Account Service Account"
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
Import-DscResource -ModuleName SharePointDSC -ModuleVersion 3.6.0.0
Import-DscResource -ModuleName PSDesiredStateConfiguration
#ServerGroup 1
Node $AllNodes.Where{$_.ServerGroup -eq 1}.NodeName
{
Script EnableSideBySideFarmWide
{
SetScript =
{
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$WebApps = Get-SPWebApplication
Write-Verbose "Enabling Side By Side Farm Wide"
foreach($webApp in $webapps){
$Webapp.WebService.EnableSideBySide = $true
$WebApp.Update()
}
}
TestScript =
{
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$WebApps = Get-SPWebApplication
foreach($webApp in $webapps){
if($Webapp.WebService.EnableSideBySide -eq $false){
return $false
}
}
return $true
}
GetScript =
{
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$WebApps = Get-SPWebApplication
foreach($webApp in $webapps){
if($Webapp.WebService.EnableSideBySide -eq $false){
return $false
}
}
return $true
}
}
Script BlockLoadBalancer{
GetScript = {
Return @{
Result = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
}
}
TestScript = {
$rule = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
if ($rule) {
Write-Verbose "Enabled"
return $true
}
else {
Write-Verbose "Disabled"
Return $false
}
}
SetScript = {
$remoteAddress = ($AllNodes | Select-Object -first 1).LoadBalancerIPAddress
Write-Verbose "Blocking communication with LoadBalancer"
New-NetFirewallRule -Direction Inbound -DisplayName "Block Load Balancer" -Name "BlockLoadBalancer" -RemoteAddress $remoteAddress -Action Block
}
}
SPProductUpdate ServerGroup1
{
SetupFile = "C:\Patch\CU.exe"
ShutdownServices = $true
BinaryInstallDays = @("sat", "sun")
BinaryInstallTime = "12:00am to 4:00am"
PsDscRunAsCredential = $CredsSPFarm
}
WaitForAll SPProuductUpdateAllNodes
{
ResourceName = "[SPProductUpdate]ServerGroup2"
NodeName = $AllNodes.Where{$_.ServerGroup -eq 2}.NodeName
RetryIntervalSec = 300
RetryCount = 720
DependsOn = "[SPProductUpdate]ServerGroup1"
}
SPConfigWizard ServerGroup1
{
Ensure = "Present"
DatabaseUpgradeDays = @("sat", "sun")
DatabaseUpgradeTime = "12:00am to 4:00am"
PsDscRunAsCredential = $CredsSPFarm
IsSingleInstance = "Yes"
DependsOn = "[WaitForAll]SPProuductUpdateAllNodes"
}
Script UnBlockLoadBalancerServerGroup1{
GetScript = {
Return @{
Result = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
}
}
TestScript = {
$rule = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
if ($rule) {
Write-Verbose "Enabled"
return $false
}
else {
Write-Verbose "Disabled"
Return $true
}
}
SetScript = {
$remoteAddress = ($AllNodes | Select-Object -first 1).LoadBalancerIPAddress
Write-Verbose "Blocking communication with LoadBalancer"
New-NetFirewallRule -Direction Inbound -DisplayName "Block Load Balancer" -Name "BlockLoadBalancer" -RemoteAddress $remoteAddress -Action Block
}
DependsOn = "[SPProductUpdate]ServerGroup2"
}
}
#ServerGroup 2
Node $AllNodes.Where{$_.ServerGroup -eq 2}.NodeName
{
WaitForAll UnBlockServerGroup1
{
ResourceName = "[Script]UnBlockLoadBalancerServerGroup1"
NodeName = $AllNodes.Where{$_.ServerGroup -eq 1}.NodeName
RetryIntervalSec = 300
RetryCount = 720
}
Script BlockLoadBalancerServerGroup2{
GetScript = {
Return @{
Result = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
}
}
# Must return a boolean: $true or $false
TestScript = {
$rule = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
if ($rule) {
Write-Verbose "Enabled"
return $true
}
else {
Write-Verbose "Disabled"
Return $false
}
}
SetScript = {
$remoteAddress = ($AllNodes | Select-Object -first 1).LoadBalancerIPAddress
Write-Verbose "Blocking communication with LoadBalancer"
New-NetFirewallRule -Direction Inbound -DisplayName "Block Load Balancer" -Name "BlockLoadBalancer" -RemoteAddress $remoteAddress -Action Block
}
DependsOn = "[WaitForAll]UnblockServerGroup1"
}
SPProductUpdate ServerGroup2
{
SetupFile = "C:\Patch\CU.exe"
ShutdownServices = $true
BinaryInstallDays = @("sat", "sun")
BinaryInstallTime = "12:00am to 4:00am"
PsDscRunAsCredential = $CredsSPFarm
DependsOn = "[Script]BlockLoadBalancerServerGroup2"
}
WaitForAll SPConfigWizardServerGroup1
{
ResourceName = "[SPConfigWizard]ServerGroup1"
NodeName = $AllNodes.Where{$_.ServerGroup -eq 1}.NodeName
RetryIntervalSec = 300
RetryCount = 720
DependsOn = "[SPProductUpdate]ServerGroup2"
}
SPConfigWizard ServerGroup2
{
Ensure = "Present"
DatabaseUpgradeDays = @("sat", "sun")
DatabaseUpgradeTime = "12:00am to 4:00am"
PsDscRunAsCredential = $CredsSPFarm
IsSingleInstance = "Yes"
DependsOn = "[WaitForAll]SPConfigWizardServerGroup1"
}
Script SetSideBySideTokenFarmWide
{
SetScript =
{
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$path = [Microsoft.SharePoint.Utilities.SPUtility]::GetGenericSetupPath("TEMPLATE\Layouts")
Folders = Get-ChildItem $path -Directory | Where-Object name -match '\d+.\d+.\d+.\d+'
$latest = ''
switch($folders.count){
0: {break;}
1: {$latest = $folders.name}
2: {
if( ($folders[0].name).replace('.','') -gt ($folders[1].name).replace('.','') ){
$latest = $folders[0].name
}
else{$latest = $folders[1].name}
}
}
$WebApps = Get-SPWebApplication
foreach($webApp in $webapps){
if($WebaApp.WebService.EnableSideBySide -eq $true){
$Webapp.WebService.SideBySideToken = $latest
$WebApp.Update()
}
}
}
TestScript = {
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$path = [Microsoft.SharePoint.Utilities.SPUtility]::GetGenericSetupPath("TEMPLATE\Layouts")
Folders = Get-ChildItem $path -Directory | where name -match '\d+.\d+.\d+.\d+'
$latest = ''
switch($folders.count){
0: {break;}
1: {$latest = $folders.name}
2: {
if( ($folders[0].name).replace('.','') -gt ($folders[1].name).replace('.','') ){
$latest = $folders[0].name
}
else{$latest = $folders[1].name}
}
}
$WebApps = Get-SPWebApplication
foreach($webApp in $webapps){
if($Webapp.WebService.SideBySideToken -ne $latest -and $WebApp.WebService.EnableSideBySide){
return $false
}
}
return $true
}
GetScript = {
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$WebApps = Get-SPWebApplication
return @{Result = $WebApps | ForEach-Object{
@{DisplayName = $_.DisplayName; Url = $_.Url; SideBySideToken = $_.WebService.SideBySiteToken}
}
}
}
DependsOn = "[SPConfigWizard]ServerGroup2"
}
Script UnBlockLoadBalancer{
GetScript = {
Return @{
Result = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
}
}
TestScript = {
$rule = Get-NetFirewallRule -Name "BlockLoadBalancer" -ErrorAction SilentlyContinue
if ($rule) {
Write-Verbose "Enabled"
return $false
}
else {
Write-Verbose "Disabled"
Return $true
}
}
SetScript = {
$remoteAddress = ($AllNodes | Select-Object -first 1).LoadBalancerIPAddress
Write-Verbose "Blocking communication with LoadBalancer"
New-NetFirewallRule -Direction Inbound -DisplayName "Block Load Balancer" -Name "BlockLoadBalancer" -RemoteAddress $remoteAddress -Action Block
}
DependsOn = "[SPProductUpdate]ServerGroup2"
}
}
}
$ConfigData = @{
AllNodes = [array] ((Get-SPFarm).Servers | Where-Object Role -ne "Invalid" | ForEach-Object{
@{
NodeName = $_.Address;
ServerGroup = $_.Address -replace '\D', '';
LoadBalancerIPAddress = "10.10.10.1";
PSDscAllowPlainTextPassword = $true;
PSDscAllowDomainUser = $true;
}
})
}
SPFarmUpdateZDT -ConfigurationData $ConfigData
New to DSC, would appreciate feedback, comments.
Hi @kayodebristol
Thanks for sharing your idea. I see one major issue with this idea: DSC isn't meant for deploying separate configurations for each change. You basically update a configuration with the change you want to implement and DSC goes out and does that change for you. So you specify the state you want the environment to be in.
In your example, you specified two script resource per server group that will cause issues: For ServerGroup1, these are "BlockLoadBalancer" and "UnBlockLoadBalancerServerGroup1" For ServerGroup2, these are "BlockLoadBalancerServerGroup2" and "UnBlockLoadBalancer"
The reason these two resource will cause issues is that the desired state of these resources are the exact opposite of each other. So it is impossible that the server will ever be in a compliant state, since it will always be incompliant with one or the other.
So basically you created a configuration that specifies a value to be 0 and 1 at the same time. If you configure the Local Configuration Manager as "Apply and Autocorrect", this means every 15 minutes (by default) traffic is blocked by the BlockLoadBalancer resource (since the firewall rule doesn't exist and therefore the server is incompliant) and shortly after that the traffic is allowed again by the UnBlockLoadBalancer resource (since the firewall rule does exist and therefore the server is incompliant).
If we want to enable this scenario, we need to implement this routine in the SPProductUpdate resource.
@ykuijs Great explanation, thank you. Totally get it. There might be another way, if one could detect when SPProductUpdate was running... maybe with a mutex? Nix the UnBlockLoadBalancer script resources and change the BlockLoadBalancer Script resource to something like this:
Script BlockLoadBalancerWhilePatching{ SetScript { Try { $mutex = [Theading.Mutex]::OpenExisting('Global\[SPProductUpdate Mutex Name]'); #not sure if there is such a thing, but if there is, Loadbalancer will be blocked New-NetFirewallRule -Direction Inbound -DisplayName "SP Block Load Balancer While Patching - DSC" -Name "SPBLBWPDSC" -RemoteAddress $remoteAddress -Action Block -ErrorAction SilentlyContinue; $mutex = $null; } Catch { #OpenExitisting throws an exemption if the mutex name is not found if($null -eq $mutex){ $Remove-NetFireWallRule -Name SPBLBWPDSC } else{$mutex = $null} } } }
Initially, I thought I could use 'Global_MSIExecute' to detect it, but that didn't work in testing. Did I get lost in the rabbit hole, again, maybe?
Did you have a look at this idea: #1097?
@andikrueger Yes. #1097 Deals with EnableSideBySide & SideBySideToken. I handle that with EnableSideBySideFarmWide and SetSideBySideTokenFarmWide Script resources in SPFarmUpdateZDT Configruation, above.
@ykuijs pointed out that my handling of the loadbalancer (UnBlockLoadBalancer & BlockLoadBalancer script resources) won't work. So, my previous post was specific to addressing the loadbalancer part of the problem.
I read somewhere that writing script resources was a good first step for creating custom resources... I think I'm too green to write a custom resource, but I want to contribute. Thought submitting script resources that could accomplish ZDTP would be beneficial. No?
@kayodebristol your input is highly appreciated!
Yes, the networking part is challenging. As @ykuijs pointed out we can’t have to resources within one configuration that do say “do it” and “don’t do it” at the same time. The same applies to the SideBySidePart.
The easiest solution I can think of (and @ykuijs mentioned above) is to extend the SPProductUpdate Resource with a switch parameter ZDP. This switch would work like ShutdownServices and would disable incoming network traffic.
We won’t need it for
- Reboot
- SPConfigWizard as they will shutdown the services anyway.
Doing it this way won’t conflict in the configuration.
I’m happy to create some draft resources for ZDP with DSC.
What’s your opinion on the extension described above?
@andikrueger
Looks like I missed something. Node should be pulled out of loadbalancer during SPProductUpdate and SPConfigWizard steps. It doesn't matter that SPConfigWizard shuts down services. Dumb loadbalancer will still direct traffic to node until TCP response is blocked.
Think I'm in favor of a separate resource instead of extending existing... I would ideally like the node removed from the loadbalancer during all "patch" activities (Windows Update, SPProductUpdate, & SPConfigWizard). Perhaps a property of this resource would be a Trigger = All (default) | Windows | SharePoint. Just a thought.
Still not tracking on the SideBySide piece, however. Assumptions:
- Once EnableSideBySide is set to $true for a SPWebApplication, it can stay enabled for the life of the SPWebApplication. zero downtime patching steps never mention setting EnableSideBySide back to false.
- SideBySideToken value needs to change, but the configuration stays the same, latest (highest version number folder name). The zero downtime patching steps, above, set the SideBySideToken to the latest at the end of the patching process, that's it. The SetSideBySideTokenFarmWide script resource accomplishes that and DependsOn = "[SPConfigWizard]ServerGroup2" (all servers have the latest code). The SideBySideToken value will change, but always to the latest folder version. The configuration, apply the latest, remains the same throughout.
Am i still missing something?
I think, there is a challenge with your approach of a seperate resource. Within the resource that removes the node from load-balancing we would need to know, if this node would need to install any updates. Otherwise this node would block traffic and would need to allow traffic seconds later during a configuration run.
This block and do not block is my main concern. This is similar to https://github.com/PowerShell/SharePointDsc/issues/1096#issuecomment-549985611
AFAIK running config wizard will stop the IIS service. This is equal to set blocking firewall rules in place. The server wont answer to your request.
This leaves us with the SPProductUpdate resource. Within the Set-Method of this resource, we could easily block the network traffic and unblock the traffic too. Doing so will not conflict.
The part about the SideBySide Token:
- SharePoint allows to set and unset the property. We should have this ability in DSC too. That's why there should be option available to disable side by side.
- Yes. We would need to set to the highest version number at the end of the process.
@andikrueger I disagree. User gets 404 if server doesn't reply. That's not zero down time. The blocking firewall rule is used because even the most basic load balancer does a ping (except perhaps DNS load balancing, but you can't help everyone). If the node doesn't ping, it is removed.
SideBySide settings have the specific goal of having users get the same code. It doesn't facilitate zero down time at all. Just consistency.
Zero down time to me means that the user never gets a 404. So, the load balancer never sends a request to a server that's not ready to handle that request. So, node should be removed from load balancer during "patch" activities. Placed back in load balancer patch activity completes.
The only requirement, as far as timing goes, is that the node must be placed back in the load balancer before it's partner node or ServerGroup is removed. Seconds not required. WaitForAll can do that.
SideBySideToken and EnableSideBySide are 2 separate SPWebApplication Settings.
$WebApp.WebService.EnableSideBySide = $false (default) | $true #This directs SharePoint to copy code to the version named folder
$WebApp.WebService.SideBySideToken = "" (Default) | x.x.x.x (folder name) #Tells SharePoint which folder to serve code from.
If you turn on ZDT patching with it's own resource, i.e. Enabled = $true, SideBySideToken = [Latest folder] & EnableSideBySide = $true. Enabled = $false, SideBySideToken = '' & EnableSideBySide = $false. But, once you go ZDT why would you go back? Lost a node, so you can't do high availability. OK, still doesn't make sense to turn it off. Imagine a single server farm, if you patch it you have down time. No matter your SideBySide setting your down time will be the same. Actually, thinking about, it should be on by default. But that's a different team.
Yes, figuring out how to implement the block during certain times is a challenge, but you have to extend both SPProductUpdate and PSConfigWizard with a redundant setting. And it doesn't help during Windows patching. A separate resource is harder, I get it, but it's the more elegant solution, if we can figure it out.
The only problem I see is being able to detect when SPConfigWizard, PSProductUpdate, or Windows Updates are in progress... If can't figure that out, then extending both resources is the only option.
You are right about the 404 status code. I'm totally with you in case of ZDP, the user should not be confronted with any 404 or any other error messages. From a users perspektiv: SharePoint should be up an running.
@ykuijs and I had a discussion about this technical debt before and we came to the conclusion, that we would need a basic LB-guidance:
Therefore load balancer solutions should at least check for HTTP status codes or better yet, retrieve a page and check for specific content every 10 to 15 seconds. see: https://github.com/PowerShell/SharePointDsc/issues/1096#issue-469922845
This would eliminate every ping based solution, which is very error prone and basically not recommended for production.
@kayodebristol I think what @andikrueger is referring to for the Load balancer is most intelligent devices today especially BigIP can be configured to check for a static page on IIS and based on the text can remove or add a server to the working pool. In our case we have a html page with a simple up/down that the BigIp reads if the text is "Down" then it removes the server from the pool, and vice versa. We had already automated this ability for some of our scripting.
Function F5Status ($status,$WebServ) # used to set Status.HTML for servers to remove from loadbalancing and to add back to load balancing
{
$line1 = "<HTML>"
$line3 = "</HTML>"
$nwline = "rn"
$content = $line1 + $nwline + $status + $nwline + $line3
$uncPath = "F$\inetpub\wwwroot"
Remove-Item "\$webserv$uncPath\status.html" -ErrorAction SilentlyContinue
New-Item "\$webserv$uncPath\status.html" -type File
Add-Content -Path "\$webserv$uncPath\status.html" $content
if (Select-String -Path "\$webserver$uncPath\status.html" -Pattern $status) {Write-Output "\$webserver$uncPath\status.html done."}
}
Interesting the pasting action removed some text from the function. here it is with the characters escaped
Function F5Status ($status,$WebServ) # used to set Status.HTML for servers to remove from loadbalancing and to add back to load balancing
{
$line1 = "'<HTML>'"
$line3 = "'</HTML>'"
$nwline = "rn"
$content = $line1 + $nwline + $status + $nwline + $line3
$uncPath = "E$\inetpub\wwwroot"
Remove-Item "\$webserv$uncPath\status.html" -ErrorAction SilentlyContinue
New-Item "\$webserv$uncPath\status.html" -type File
Add-Content -Path "\$webserv$uncPath\status.html" $content
if (Select-String -Path "\$webserver$uncPath\status.html" -Pattern $status) {Write-Output "\$webserver$uncPath\status.html done."}
}
$line1 = "\ H.T.M.L" $line3 = "</H.T.M.L>'"
anyway if this doesn't work, line 1 and line 3 are the open and closed anchors for H-T-M-L
@jimbrayDel thank you for the example. This is what I was referring to. There are so many appliances that offer these options to check for status pages or static content.
@jimbrayDel In theory, Yes. In practice, not so much. ADFS authentication sends back a redirect, which even the most advanced load balancers have issues with. A static, anonymous auth site, sure. But now I've got my security guys yelling at me. Maybe Windows auth on the site with a service account on my load balancer. Now one service account can take down my whole farm (deleted, password changed, expired, etc...).
The firewall rule is the most elegant, and handles most cases, almost all loadbalancers/configs. I thought the only question was if both SPProductUpdate and PSConfigWizard would have to be extended. I think so.
I'm not saying something else wouldn't work... just that it's not as elegant and doesn't handle as many use cases.
Would love to be able to say, "Patching SharePoint? Just run this DSC, fogetaboutit!" Disclaimer: Must have high availability, minimum to TCP health monitoring, avoid contact with eyes and skin and avoid inhaling fumes. Don't try this in your living room; these are trained professionals.
@kayodebristol I am not sure I understand about the ADFS, what I was refeering to is removing the server from the load balancer pool. This would eliminate any traffic to the server which would be before any ADFS authentication would even take place. Also on pre-existing users that are already on a server the load balancer should relocate them once you take the server out of the pool. That is unless you are using "Persistence" or "Sticky" configurations that locks the Load balancer on a particular server, but that practice is not recommended. We have Distributed Cache service so we should not need sticky or persistence any more, we can allow users to bounce around. What am I missing or overlooked?
@jimbrayDel I get it. Some security Nazis in certain environments will call that unauthenticated content. Crazy, I agree. But some folks even block icmp between servers. Even using authenticated content can be problematic. Blocking all inbound traffic with a firewall rules doesn't work in every case, but it works in most cases, and requires less effort than altering a status.html file, is all.
Was reminded that Windows Firewall is also disabled in some environments in favor of other 3rd party tools like McAfee. Perhaps we leave load balancing out for the time being and just focus on EnableSideBySide & SideBySideToken? Folks with intelligent load balancers will benefit and folks without will at least have one less manual step.
Good point with 3rd Party tools. That’s why we would need to focus on one scenario. I would still go with Windows Firewall for the time being and make this option optional.
Side by side: YES!
Let me try to provide an experimental PR for ZDP. I have something in mind.
@andikrueger Awesome! I revised my example configuration (SPZDPDSCConfig). Removed load balancing stuff, and cleaned up a bit to only do the SideBySide stuff on localhost. Maybe it will be useful in some way.
Well, you should consider that some companies will use GPOs to configure Firewall settings, In this case you won't be able to activate or deactivate any firewall rules...PS will not return any errors, however the setting will not be effective.
Why not simply stopping IIS, this should cause any LB to switch to any other WFE...
Regards, Thomas
Good feedback on Firewall Settings beeing controlled by GPO.
@ykuijs and I discussed "stopping IIS" before. By stopping IIS we will not be able to do some manual testing during the update process.