search
top

Using PowerShell to Failover Cluster Resources for Patching

Introduction

So you are having to patch cluster nodes and would like a way to automate the failover of the resources and log what when it was done and who did the work. In this post we will cover a simple PowerShell script that will drain, pull and balance resources in a 2 or more node active / passive or active / active cluster.

About the Script

The purpose of the PowerShell script is to allow three operations to occur, they are to drain all the resources on the node you are running the script on to the other node in the two node cluster and the other is to bring all (pullall) the resources from a node to the node. Last option is for active / active clusters called balance, this will move the resource to the preferred owner. While doing this to also create a local log file  and also to create a Event in the Application event log. Using this script you can automate patching of clusters with SCCM and other methods. Another note is that the script be able to work on Windows Server 2008, 2008 R2, 2012, 2012 R2 and 2016 versions.

To achieve this we will create three functions and also set a string parameter to look for drain, pullall or balance at the PowerShell command line. For the PowerShell cluster operations we will be using the FailOverClusters module and several get cmdlets such as Get-ClusterGroup, Get-ClusterResource, Move-ClusterGroup and Get-ClusterNode.

For the logging events portion we will be capturing who is doing the failover, the date and time and what operation is performed into the local logfile stored in c:\support directory using Out-File and Add-Content cmdlets. For the Event logging we will use the Write-EventLog cmdlet and create the event in Application Log. Below is the script and is fairly easy to read and can be modified to your liking. In a future post we will add the balance feature to allow this script to be used to send Cluster Groups to preferred owners in Active / Active clusters and some error checking.

The Script

Here is the script in it’s entirety. [easy_media_download url=”https://lifeofageekadmin.com/wp-content/uploads/2016/08/ClusterResourceMover2.zip“]

<# 
.SYNOPSIS 
This script will pullall, drain or balance cluster resources from the command line. 

.DESCRIPTION 
The script will pullall, drain or balance cluster resources from the command line. 

.NOTES 
File Name : ClusterResourceMover.ps1
Author : Mark Harris
Requires : Windows Server 2012 R2 PowerShell
Windows Server 2012 R2 Clustering Cmdlets 
Windows Server 2008 R2 PowerShell
Windows Server 2008 R2 Clustering Cmdlets
Windows Server 2016 PowerShell
Windows Server 2016 Clustering Cmdlets 
Version : 1.0 (June 26 2017)

.PARAMETER drain or pullall or balance
Specifies send, retrieve or delegate cluster resources 

.INPUTS 
ClusterResourceMover accepts drain, pullall and balance 

.OUTPUTS 
Log to c:\support directory and log to Application EventLog and sends email to teams or individuals. 

.EXAMPLE 
C:\PS> .\ClusterResourceMover drain # drains all cluster resources to the other node. 

.EXAMPLE 
C:\PS> .\ClusterResourceMover pullall # Retrives all cluster resources to the current node.

.EXAMPLE 
C:\PS> .\ClusterResourceMover balance # Moves resource to preferred owner node.

#> 
# Get User input and execute function Must be first line in code

Param([Parameter(Mandatory=$true,HelpMessage="Enter in drain, pullall, balance")][String]$ClusOp)

# Create EventLog Source, will not create if exists
$Result = test-path -path 'HKLM:\SYSTEM\ControlSet001\Services\EventLog\Application\ClusterHealth'
if ($Result -eq "True")
{ Write-host "No changes made to registry, all good" }
Else {
New-EventLog -Source "ClusterHealth" -LogName "Application" -MessageResourceFile "%SystemRoot%\System32\EventCreate.exe"
}

# Create Local logfile
Import-Module FailoverClusters
$node = hostname
$date1=Get-Date -Format "yyyy-MM-dd hh:mm tt"
$clustervip=Get-Cluster 
$clustervipname=($clustervip.Name)
$admin = (Get-Content -Path env:username).ToString()
$LogFileName = "c:\Support\ClusterResMove-"+ $node +".txt"
Out-File -append -FilePath $LogFileName -Encoding ASCII
Add-Content -Path $LogFileName -Value "Computer: $node" -Encoding ASCII
Add-Content -Path $LogFileName -Value "*****************************" -Encoding ASCII

Function Balance {
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 101 -EntryType Information -Message "($clustervipname) Cluster Balance Operation Started for Windows Patching"
Import-Module FailoverClusters
Add-Content -Path $LogFileName -Value "Date: $date1" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Executed By: $admin" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Current Cluster Assignments" -Encoding ASCII
$computer = get-content env:computername
$computer = $computer.ToLower()

Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName

$clustergroups = Get-ClusterGroup | Where-Object {$_.IsCoreGroup -eq $false}
foreach ($cg in $clustergroups)
{
$CGName = $cg.Name
Add-Content -Path $LogFileName -Value "Getting Cluster Group Owners" -Encoding ASCII
$CurrentOwner = $cg.OwnerNode.Name
$POCount = (($cg | Get-ClusterOwnerNode).OwnerNodes).Count
if ($POCount -eq 0)
{
Add-Content -Path $LogFileName -Value "Info: $CGName doesn't have a preferred owner!" -Encoding ASCII
}
else
{
$PreferredOwner = ($cg | Get-ClusterOwnerNode).Ownernodes[0].Name
Add-Content -Path $LogFileName -Value "$PreferredOwner" -Encoding ASCII
if ($CurrentOwner -ne $PreferredOwner)
{
Add-Content -Path $LogFileName -Value "Moving resource to $PreferredOwner" -Encoding ASCII
$cg | Move-ClusterGroup -Node $PreferredOwner
}
else
{
Add-Content -Path $LogFileName -Value "Resource is already on preferred owner! ($PreferredOwner)" -Encoding ASCII
}
}
}
Add-Content -Path $LogFileName -Value "Cluster Balance Operation Completed" -Encoding ASCII
Add-Content -Path $LogFileName -Value "New Cluster Assignments" -Encoding ASCII
Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 201 -EntryType Information -Message "($clustervipname) Cluster Balance Operation completed for Windows Patching"
# Uncomment FuncMail below to use email sending option uncomment for P&C clusters.
# FuncMail -To "destinationemail@me.com" -From "frommail@me.com" -Subject "($clustervipname) Cluster Balance Opertaion completed by ($node)." -Body "($clustervipname) Cluster Balance Operation completed for Windows Patching" -smtpServer "smtpmailserver"
}

Function Drain {
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 101 -EntryType Information -Message "Cluster Drain Operation started for Windows Patching"
Import-Module FailoverClusters
Add-Content -Path $LogFileName -Value "Date: $date1" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Executed By: $admin" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Current Cluster Assignments" -Encoding ASCII
Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName 
$computer = get-content env:computername
$computer = $computer.ToLower()
$destnode = Get-clusternode | select Name

# Convert to string for use in foreach-object
[string]$drainnode = ($destnode.Name -ne $computer)

Get-ClusterGroup |
foreach-object `
{
If ($_.Name -ne $computer)
{
Move-ClusterGroup -Name $_.Name -Node $drainnode
}

}

Add-Content -Path $LogFileName -Value "($clustervipname) Drain Operation Completed to $drainnode " -Encoding ASCII
Add-Content -Path $LogFileName -Value "New Cluster Assignments" -Encoding ASCII
Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 201 -EntryType Information -Message "($clustervipname) Cluster Drain Operation completed for Windows Patching"
# Uncomment FuncMail below to use email sending option uncomment for P&C clusters.
# FuncMail -To "destinationemail@me.com" -From "frommail@me.com" -Subject "($clustervipname) Cluster Drain Opertaion completed by ($node)." -Body "($clustervipname) Cluster Drain Operation completed for Windows Patching" -smtpServer "smtpmailserver"
}

Function PullAll {
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 101 -EntryType Information -Message "Cluster PullAll Operation Started for Windows Patching"
Import-Module FailoverClusters
Add-Content -Path $LogFileName -Value "Date: $date1" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Executed By: $admin" -Encoding ASCII
Add-Content -Path $LogFileName -Value "Current Cluster Assignments" -Encoding ASCII
Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName 
$computer = get-content env:computername
$computer = $computer.ToLower()
Get-clusternode | Get-ClusterGroup |
foreach-object `
{

If ($_.Name -ne $computer)
{ 
Move-ClusterGroup -Name $_.Name -Node $computer
}
}
Add-Content -Path $LogFileName -Value "($clustervipname) PullAll Operation Completed to $computer " -Encoding ASCII
Add-Content -Path $LogFileName -Value "New Cluster Assignments" -Encoding ASCII
Get-ClusterGroup | Out-File -append -Encoding ASCII -FilePath $LogFileName
Write-EventLog -LogName "Application" -Source "ClusterHealth" -EventID 201 -EntryType Information -Message "($clustervipname) Cluster PullAll Operation completed for Windows Patching"
# Uncomment FuncMail below to use email sending option uncomment for P&C clusters.
# FuncMail -To "destinationemail@me.com" -From "frommail@me.com" -Subject "($clustervipname) Cluster PullAll Opertaion completed by ($node)." -Body "($clustervipname) Cluster PullAll Operation completed for Windows Patching" -smtpServer "smtpmailserver"
}

# Take the parameter and validate the input and call the functions.
If ("drain","pullall","balance" -NotContains $ClusOp) 
{ 
Throw "Not a valid option! Please use drain, pullall or balance option" | Out-File -append -Encoding ASCII -FilePath $LogFileName 
}

function FuncMail {
#param($strTo, $strFrom, $strSubject, $strBody, $smtpServer)
param($To, $From, $Subject, $Body, $smtpServer)
$msg = new-object Net.Mail.MailMessage
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = $From
$msg.To.Add($To)
$msg.Subject = $Subject
$msg.IsBodyHtml = 1
$msg.Body = $Body
$smtp.Send($msg)
}

# All parameters are valid call function 
If ($ClusOp -eq "drain") { Drain }
If ($ClusOp -eq "pullall") { PullAll }
If ($ClusOp -eq "balance") { Balance }

 

So we have the script, how do we run it and what does it look like?  By running ClusterResourceMover2 drain or ClusterResourceMover2 pullall  or ClusterResourceMover2.ps1 balance we can make the script do its magic.

clustermovepullall

Conclusion

So we see it is possible to use the FailoverClusters PowerShell module to automate patching of Windows Servers 2008 / 2012 / 2016 clusters and also log operations for tracking.

3 Responses to “Using PowerShell to Failover Cluster Resources for Patching”

  1. Scott Buzbee says:

    Great script. Have you completed the balance portion of this script yet?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

top