Display “Private Properties” of a cluster resource using PowerShell and C#

As with all other product management technologies, the Failover Cluster management API is moving to PowerShell.  Over the past few month the product group has released information about using PowerShell to gather information about a failover cluster and its resources.  In this blog post, I am going to focus on a couple of the basic failover cluster cmdlets that will allow a programmer to retrieve information about the “Private Properties” of a cluster resource using C# and PowerShell.

We start with a client machine that has Visual Studio 2013 and the Windows SDK for 8.1 installed.  We then install the failover cluster management tools as illustrates in the following link:

Installing the Failover Cluster Feature and Tools in Windows Server 2012

Once the management tools are installed and they have been tested in a PowerShell.exe host window, you can then create a Visual Studio 2013 C# project.   Keep in mind, the Failover Cluster CMDLETS are x64 only.  Be sure to build your project targeting the x64 CPU architecture.

NOTE:
If the cluster modules and cmlets will not load in a PowerShell host on the client you are developing or intending to run you C# code, then they will not work inside the C# application.  I strongly suggest that the cmdlets be tested in the default PowerShell console ( PowerShell .exe ) before proceeding to  Visual Studio.  Here are 3 simple steps to test the cmdlets:

1. Start PowerShell.Exe
2. At the PowerShell command prompt type:

import-module FailoverClusters
Get-Cluster –Domain mydomain.com

The cmdlet should return cluster objects available in the domain.

If the cmdlets successful run in the PowerShell console.  You are now ready to open your Visual Studio project and create a simple PowerShell automation sample.

Start by adding reference to System.Management.Automation  assembly by browsing to a location similar to:

%ProgramFiles(x86)%\Reference Assemblies\Microsoft\WindowsPowerShell\3.0

The cmdlets we will be using in this example are the following 4:

  1. Import-Module : this cmdlet is the first cmdlet that will be called.  We will use it to import the “FailoverClusters” module into the Powershell runspace.  This will load the failover cluster cmdlets into the runspace.   There is another method for importing the cmdlets into the runspace using the InitialSessionState object, a good example of using this object can be found in this blog post: How to run an Active Directory (AD) cmdlet from .Net (C#)
  2. Get-Cluster – we will use this cmdlet to locate all of the clusters in a given domain using the –Domain parameter and then work with the first cluster in the collection.
  3. Get-ClusterResource – we will use this cmdlet to retrieve a collection of cluster resources from the a given cluster object.
  4. Get-ClusterParameter – using the collection of resources returned by the Get-ClusterResource cmdlet, we will enumerate all of the private properties for each cluster resource.  Private properties of a cluster resource are wrapped by Microsoft.FailoverClusters.PowerShell.ClusterParameter objects.

Below is the source code that illustrates how to display the private properties of a set of cluster resources using Powershell from C#:

/*######################################################################
#
# DISCLAIMER:
#
# This sample is provided as is and is not meant for use on a production environment.
# It is provided only for illustrative purposes. The end user must test and modify the
# sample to suit their target environment.
#
# Microsoft can make no representation concerning the content of this sample. Microsoft
# is providing this information only as a convenience to you. This is to inform you that
# Microsoft has not tested the sample and therefore cannot make any representations
# regarding the quality, safety, or suitability of any code or information found here.
#
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;
using System.Collections.ObjectModel;

namespace PowershellClusterCmdlets
{
class Program
{
static void Main(string[] args)
{
//
// Prerequisites:
// FailoverCluster cmdlets must be installed on the client
// there are several ways to import the cluster module.
// below is the brute force method using Import-Module
//
PowerShell ps = PowerShell.Create();
ps.AddCommand("Import-Module");
ps.AddParameter("Name", "FailoverClusters");
Collection<PSObject> commandResults = ps.Invoke();
Collection<ErrorRecord> ErrStr;
if( ps.HadErrors )
{
//
// There was an error in the execution of the cmdlet.
// Display the error
//
ErrStr = ps.Streams.Error.ReadAll();
foreach( ErrorRecord er in ErrStr )
{
Console.WriteLine("Error: {0}", er.ToString());
}
ps.Dispose();
return;
}
Console.WriteLine("Failover Cluster module loaded successfully");
ps.Commands.Clear();
//
// Lets get the clusters in a given domain.
// Replace the "contoso.com" domain name with the target domain.
// Then use the Get-Cluster with the Domain parameter
// technet.microsoft.com/en-us/library/hh847254.aspx
              //
ps.AddCommand("Get-Cluster");
ps.AddParameter("Domain", "contoso.com");
commandResults = ps.Invoke();
if (ps.HadErrors)
{
//
// Errors trying to retrieve clusters on the domain
//
ErrStr = ps.Streams.Error.ReadAll();
foreach (ErrorRecord er in ErrStr)
{
Console.WriteLine("Error: {0}", er.ToString());
}
Console.WriteLine("Terminating");
ps.Dispose();
return;
}
ps.Commands.Clear();
//
// Retrieve the resources and parameters for the first cluster returned
// using Get-ClusterResource:
// technet.microsoft.com/en-us/library/hh847304.aspx
             //
PSObject pso = commandResults[0];
Console.WriteLine("Retrieve resources for cluster {0}" , pso.Properties["Name"].Value);
ps.AddCommand("Get-ClusterResource");
ps.AddParameter("InputObject", pso);
Collection<PSObject> ClusterResources = ps.Invoke();
if (ps.HadErrors)
{
//
// Errors trying to retrieve cluster resources
//
ErrStr = ps.Streams.Error.ReadAll();
foreach (ErrorRecord er in ErrStr)
{
Console.WriteLine("Error: {0}", er.ToString());
}
Console.WriteLine("Terminating");
ps.Dispose();
return;
}
ps.Commands.Clear();
//
// Loop through the resources and get the parameters or
// Private Properties using the Get-ClusterParameter
//  technet.microsoft.com/en-us/library/hh847319(v=wps.620).aspx
             //
foreach(PSObject rsc in ClusterResources)
{
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("Private Properties for resource \"{0}\"\n", rsc.ToString());
ps.AddCommand("Get-ClusterParameter");
ps.AddParameter("InputObject", rsc);
Collection<PSObject> psparms = ps.Invoke();
if( !ps.HadErrors )
{
foreach (PSObject psparm in psparms)
{
Console.WriteLine("{0} -> {1}", psparm.Properties["Name"].Value.ToString(), psparm.Properties["Value"].Value.ToString());
}
}
else
{
//
// Errors trying to retrieve cluster resources
//
ErrStr = ps.Streams.Error.ReadAll();
foreach (ErrorRecord er in ErrStr)
{
Console.WriteLine("Error: {0}", er.ToString());
}
Console.WriteLine("Terminating");
}
//
// Clear the pipeline
//
ps.Commands.Clear();
}
Console.WriteLine("Done");
//
// Dispose of the Powershell Object so native resources are freed.
//
ps.Dispose();
}
}
}