Draining Citrix servers automatically (1 of 2)

Posted: January 14, 2017 in Citrix, Powershell, Scripting, Uncategorized

In my job, I am on a team that manages hundreds of Citrix servers, and as any one who manages a large farm knows, there is a lot of work to be done, and automation is critical.

We manage XenApp 6.5 farms, and I came up with this script to drain servers a lot easier to manage, especially when used with a large number of farms.  The intent is to run this from a central management point, from an elevated powershell prompt in the security context of one of the Farm Administrators with local admin rights on each of the servers.

function Run-SystemDrain
{
   cmdletbinding()]
   param()

   change.exe logon /drain</pre>

This is a very straightforward opening to the script. I use the [cmdletbinding()] and the param() block to enable the -verbose parameter.  This was primarily for troubleshooting and diagnostics, but this could easily be left out.  The change.exe logon /drain puts the system in drain mode, and under XenApp 6.5, Citrix will detect this change and communicate it to the farm, and raise the load level of that server to 10000.  That process typically only takes a few seconds.

The key to this script is the do/until loop, and a couple of WMI calls.  I used WMI since this script needed to be Powershell 2.0 compatible.  The loop opens and gets the current sessions on the server, gets the disconnected sessions, and the count of the disconnected sessions.  If there are disconnected sessions, those sessions are logged off.

do
   {
      $TotalSessions = (Get-WmiObject -class Win32_TerminalService -Property TotalSessions).TotalSessions
      $DiscSessions = Get-WmiObject -Namespace Root/Citrix -Class MetaFrame_Session -Filter 'SessionState = 4'
      $DiscCount = (DiscSessions | Measure-Object).Count

      if ($DiscCount -gt 0)

The loop starts and uses WMI to get the total session count.  This is a straight Microsoft call, and has an easy property TotalSessions that does show all the sessions.  This does include the listeners (ICA-TCP, RDP-TCP, etc.).  If your servers are configured to have more than these 2 listers, you’ll need adjust the until line.

Unfortunately, the Win32_TerminalService object really does not contain the individual sessions with the necessary capabilities.  The key capability that we need is the ability to get the SessionID and to log that session off.  I dug through the object, and could not find an easy way to get that information from this object.  The other Win32* objects didn’t have this information either.

The Citrix MetaFrame_Session object does contain the SessionID and very importantly, a Logoff method.  These 2 items made this a natural selection.  In an RDP situation, you’d need to rely on the builtin qwinsta/quser commands and parse their output to accomplish this.  Also note that MetaFrame_Session exists under the Root/Citrix namespace, and not the default Root/CIMV2. The SessionState property of the MetaFrame_Session object shows the current state of the session.  The complete list of states is:

  • 0 = userLoggedOn (Active)
  • 1 = connectedToClient (Connected but not active)
  • 2 = connectingToClient (starting to connect, but not connected yet)
  • 3 = shadowingOtherSession (Currently shadowing another session)
  • 4 = loggedOnButNoClient (Disconnected)\
  • 5 = waitingForConnection()
  • 6 = listeningForConnection(Listeners only)
  • 7 = resetInProgress (being reset)
  • 8 = downDueToError (Down)
  • 9 = initializing ()

Another interesting note was that the $DiscSessions object with all the disconnected sessions didn’t seem to have a .Count property like virtually every other object I’ve worked with.   With that, I had to turn to the Measure-Object command to get that count.  Alternatively, I could have gone with:

$DiscCount = DiscSessions | Measure-Object | Select-Object -PropertyCount

The Select-Object cmdlet is slower than directly accessing the property though the (). mechanics, although I haven’t measured it. From a day to day perspective, the difference is very minimal, so I stuck with my “usual” preferred method.

This is the key loop section

{
   foreach ($Session in $DiscSessions)
   {
      $Session.Logoff()
   }
}

As we go through each $Session in the $DiscSessions collection, and use the Logoff() method to gracefully log out the session.

And then I follow it up with a start-sleep statement and the loop repeats until all of the sessions other than the listeners are gone.

    start-sleep -seconds 30
  }
  until ($TotalSessions -le 2)

And as I mentioned above, if you have more listeners than the default 2, you have to adjust this number to match.

So, the total script is:

function Run-SystemDrain
{
   cmdletbinding()]
   param()

   change.exe logon /drain
   do
   {
	  $TotalSessions = (Get-WmiObject -class Win32_TerminalService -Property TotalSessions).TotalSessions
	  $DiscSessions = Get-WmiObject -Namespace Root/Citrix -Class MetaFrame_Session -Filter 'SessionState = 4'
	  $DiscCount = (DiscSessions | Measure-Object).Count

	  if ($DiscCount -gt 0)
	  {
		  foreach ($Session in $DiscSessions)
		  {
			 $Session.Logoff()
		  }
	  }
		  start-sleep -seconds 30
	}
	until ($TotalSessions -le 2)
}

In the next post, I’ll cover using this script in practice..

David F.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s