PowerShell to failover cluster

by sql_jarret at 2012-12-02 14:05:37

Hello everyone,

I am new to PowerShell, and am trying to write a script to failover a 2 node Windows Server 2012 cluster. Â I have the code to do the actual failover, but it is hardcoded and am wanting to make this more dynamic. My goal is to be able to put a single PowerShell script on all of my clustered nodes and have it run without issue.

Here is my code to do the failover (node names are hardcoded). This will check to see if the script is ran on NodeA, then move all cluster groups to NodeB, and vice-versa.

if ($env:COMPUTERNAME -ieq "NodeA")
Get-ClusterGroup | Move-ClusterGroup -Node "NodeB"
elseif ($env:COMPUTERNAME -ieq "NodeB")
Get-ClusterGroup | Move-ClusterGroup -Node "NodeA"

I would prefer to have the script determine the inactive node and move all cluster groups to it. I have attempted to implement this functionality with the following code, but I am having some issues.

$ClusterName = Get-Cluster | Select-Object Name
#######$ClusterName = "WindowsClusterName"

$ClusterOwner = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ClusterName) | Select-Object Name
$AllNodes = Get-ClusterNode | Select-Object Name

$InactiveNode = $AllNodes -notmatch $ClusterOwner

This generates the following error. However, if I hardcode the cluster name (the commented line above), the code executes. I was trying to use the clustering cmdlet’s to get the current owner ($ClusterOwner = Get-ClusterGroup | Select-Object OwnerNode -Unique), but wasn’t having much luck with that either.

Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At line:4 char:18
+ $ClusterOwner = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Cluste …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:slight_smile: [Get-WmiObject], COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

And, when I add the code to perform the failover (Get-ClusterGroup | Move-ClusterGroup -Node $InactiveNode), I get the following error:

Move-ClusterGroup : Cannot convert ‘System.Object’ to the type ‘System.String’ required by parameter ‘Node’. Specified method is
not supported.

How do I convert the $InactiveNode variable to a string so the command will execute (assuming that is the only issue with that line of code)?

I am not married to any of this code, please feel free to modify any way you see fit, I am a beginner and just starting to try to learn the power of PowerShell. Like I said, my goal here is to be able to execute a PS script on any cluster and have it failover all cluster groups to the inactive node.

Any help is much appreciated! Thanks in advance for your time!

by DonJ at 2012-12-02 15:13:08
So, second problem first. $inactivenode has more than one object in it. That’s the problem. Also, you probably need to refer to a specific property (Name?), not the entire object.

First problem: WMI can’t connect to the WMI service on the specified machine. Firewall, name resolution, whatever. WMI isn’t clustered - not sure if you can query that service using the cluster name or not.
by sql_jarret at 2012-12-02 16:31:55
Thanks for your reply.

Ok, that makes sense for the 2nd problem with the $InactiveNode, I’ll try to fix that.

As for the first problem, would you know why it works if I hard code the name into the $ClusterName variable? Like this:

$ClusterName = "WindowsClusterName"
$ClusterOwner = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ClusterName) | Select-Object Name

This is confusing me… If I hardcode the $ClusterName variable, then the Get-WmiObject call works fine. If I use Get-Cluster to set the $ClusterName variable, then I get the error on the Get-WmiObject call. This generates the error.

$ClusterName = Get-Cluster | Select-Object Name
$ClusterOwner = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ClusterName) | Select-Object Name

After further testing, I can see the difference now in the results of setting the variable each way.

$ClusterName = (Get-Cluster | Select-Object Name) | Select-Object Name
$ClusterName ### Results in "@{Name=WindowsClusterName}"
$ClusterName = "WindowsClusterName"
$ClusterName ### Results in "WindowsClusterName"

So, what am I doing wrong? If I do some string replacements, it works.

$ClusterName = (Get-Cluster | Select-Object Name) | Select-Object Name
$ClusterName ### Results in "@{Name=WindowsClusterName"
$ClusterName = $ClusterName.Replace("@{Name=", "").Replace("}", "")
$ClusterName ### Results in "WindowsClusterName"

$ClusterOwner = (Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ClusterName) | Select-Object Name

The above works, but should I have to do all that string manipulation? Or, is there an easier way to only get back the string that I’m looking for?
by DonJ at 2012-12-02 17:15:03
Use Select-object -expand to get the name. You’re not creating a string, you’re creating an object with a name property.
by sql_jarret at 2012-12-03 08:16:32
Thanks Don! The "-ExpandProperty" is exactly what I was missing. With your help, I was also able to get the cluster owner via the Get-ClusterGroup cmdlet, instead of the WMI call. Here is my code now.

### Get current cluster owner ###
$ClusterOwner = Get-ClusterGroup | Select-Object -ExpandProperty OwnerNode -Unique

### If the cluster groups are distributed among both nodes, the owner is the node hosting SQL Server ###
if ($ClusterOwner.Length -gt 1) {
$ClusterOwner = Get-ClusterGroup "SQL Server (MSSQLSERVER)" | Select-Object -ExpandProperty OwnerNode -Unique

### Get all cluster nodes in an array ###
$AllNodes = Get-ClusterNode | Select-Object Name

### Determine inactive node as a string ###
$InactiveNode = ($AllNodes -notmatch $ClusterOwner) | Select-Object -ExpandProperty Name

### Move cluster to inactive node ###
Get-ClusterGroup | Move-ClusterGroup -Node $InactiveNode

This is for a clustered SQL Server environment, so I added some logic so that if the cluster groups are spread between nodes, the "SQL Server (MSSQLSERVER)" is the deciding factor for which node would be considered the ‘active’ node. I guess I don’t technically need the if statement, and could initially check for the specific cluster group, but you get the idea.

Thanks for all your help!