RDS Pool Monitoring

Whilst working at Focus we found that some of the service desk were disabling logins to specific servers in an RDS Pool whilst trying to troubleshoot an issue and forgetting to put the servers back in the pool. This was then generating lots of calls/tickets as the other servers eventually overloaded and resulting in even more issues.

As a result, we identified the need to create some form of pool monitoring and to generate email alerts to notify our service desk. I had a look around but couldn’t find a solution that did everything we wanted – so out came the Powershell!

The Planning

We decided to run the script via our RMM/Montioring system – Datto RRM as it offered the flexibility and visibility of the script, whilst also requiring the least amount of work to actually deploy across 100+ different RDS systems. This meant I couldn’t hardcode anything that referenced any servers or specific RDS Pool – it needed to be the same script that was ran for all RDS systems, without any customer specific tweaks inside the script.

Datto could identify the RD Gateway servers by creating a site filter with the reqirements of: “Operating System” contains “Server” and “Service Display Name” contains “Remote Desktop Gateway”. We also added a specific Datto UDF for “Exclude RDG Checks” so we for the servers that we didn’t support and Dual/Clustered RDG’s, so added a third check for “Exclude RDG Checks” does not contain “True”.

After a bit of planning with the team we needed it to do the following:

  • Find all RDS Pools
  • Check each pool for disabled logins
  • Notify via email for each RDG that had any server with logins disabled
  • Have the ability to skip specific servers that were known (without modifying the script)
  • Provide some basic information to help identify why the server was out of the pool
  • Be able to review the historic output from Datto to confirm when a server was in or out of the pool

We then decided that we would just run the script once a day at 7am, so the guys on the early shift could catch the issues prior to the end users logging in.

The Script

I’ve tried to keep comments throughout in order to try and explain which each part does and why.

<#
	===========================================================================
	 Created by:   	Rob Peters
	 Organization: 	Focus Group
	 Filename:      RDSLoginsDisabled.ps1	
	===========================================================================

    Simple script to be run on RDS Gateway to check collections for RDS Servers with logins disabled.
    If logins are detected it will send a formatted email with all RDS servers/collections and highlight disabled ones.
	Powershell must be run as administrator (or system) otherwise it can't access the RDP information.
#>
Import-Module RemoteDesktop

#Set path of file containing server names. If you have multiple list on each line. MUST INCLUDE FQDN (eg servername.domain.local)
$KnownServersFile = "C:\RDS\serversoutofpool.txt" 
If ((Test-Path $KnownServersFile) -eq $True) { $KnownServersOutOfPool = Get-Content -Path 'C:\RDS\serversoutofpool.txt' } 

#Get server information
ForEach ($CollectionName in Get-RDSessionCollection) #Get collections
{
	foreach ($Server in (get-rdsessionhost -collectionname $CollectionName.CollectionName)) #Get server names and states
	{
		#Check Uptime
		$ServerUptime = $null
		$ServerUptime = (Get-Date) - (Get-CimInstance Win32_OperatingSystem -ComputerName $Server.SessionHost).LastBootupTime
		$ServerUptimeReadable = ""+$serveruptime.days+" days "+$serveruptime.hours+" hours"
		
		#CheckOnline
		$ServerOnline = $null
		$ServerOnline = Test-connection -ComputerName $Server.SessionHost -Quiet -Count 1
		
		#Check if it's in the known out of pool list or not
		if ($KnownServersOutOfPool -notcontains $Server.SessionHost) {
		#Build array with items IN pool
		$AllServers += @([pscustomobject]@{ServerName=$Server.SessionHost;CollectionName=$CollectionName.CollectionName;ConnectionAllowed=$Server.NewConnectionAllowed;Online=$ServerOnline;ServerUptime=$ServerUptimeReadable}) 
		} else {
		#Build array with items OUT of pool
		$AllServersOOP += @([pscustomobject]@{ServerName=$Server.SessionHost;CollectionName=$CollectionName.CollectionName;ConnectionAllowed=$Server.NewConnectionAllowed;Online=$ServerOnline;ServerUptime=$ServerUptimeReadable})
		}
	}
}


#Remove comments below to add a fake out of pool connections entries (used for testing)
#$AllServers += @([pscustomobject]@{ServerName='Test Server 1';CollectionName='Test Collection';ConnectionAllowed='No';Online="False";ServerUptime="11 billion years"})
#$AllServers += @([pscustomobject]@{ServerName='Test Server 2';CollectionName='Test Collection';ConnectionAllowed='No';Online="True";ServerUptime="3 billion years"})


#If any servers in the AllServers list have logins denied then send email (and aren't in the exclusions file)
if ($AllServers.ConnectionAllowed -contains "No") {

$Table = [PSCustomobject]$AllServers| ConvertTo-Html -Fragment -As Table #Convert array to HTML Table for later
$Table = $Table -replace "<td>True</td>", "<td>Yes</td>" #Dirty fix to make "True" into "Yes" (easier than recursively building the table with IF statements)
$Table = $Table -replace "<td>False</td>", "<td>No</td>" #Dirty fix to make "False" into "No"
$Table = $Table -replace "<td>No</td>", "<td style='background-color:#FF8080'>No</td>" #Dirty fix to make "No" cell red
$CountDisabledServers =  $AllServers.Where({$_.ConnectionAllowed -eq "No"}).Count #Count the numbers of servers that have logins disabled

#Don't add excluded servers info if no servers are excluded
if ($KnownServersOutOfPool) {
$TableOOP = [PSCustomobject]$AllServersOOP | ConvertTo-Html -Fragment -As Table #Convert array to HTML Table for later
$OOPOutput = "<br><br><p>The below servers are out of pool <b>on purpose</b> so please don't add them.</p> $($TableOOP)"
}

#Create Email
$SMTPUsername = "[email protected]"
$SMTPPassword = "password" 
$SMTPServer = "mail.server.com"
$SMTPPort = "25"
$SMTPCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $SMTPUsername, ($SMTPPassword | ConvertTo-SecureString -AsPlainText -Force)
$SMTPFrom = "Focus RDS Notifications <[email protected]>"
$SMTPRcptTo = "[email protected]"
$SMTPSubject = "$CountDisabledServers RDS server(s) found with logins disabled on $env:computername.$env:userdnsdomain"
$SMTPBody = @"
<html>
<head>
	<style>
	TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
	TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #808080;}
	TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
	</style>
	<title>$EmailSubject</title>
    </head>
<body>
	<p>There has been <b><u>$($CountDisabledServers) RDS server(s)</b></u> found with logins disabled.<br>This ticket should be considered a P2.<br></p>
	<p>Please check recent tickets for this customer and find out why logins are disabled.<br>If they are going to be disabled for a while please consider excluding them from this check.</p>
	<p>RDS Gateway Name: $($env:computername)<br></p>
	$($Table)
	$($OOPOutput)
	<br />
	<br />
</body>
</html>
"@

#Send Email
Send-MailMessage -From $SMTPFrom -To $SMTPRcptTo -SmtpServer $SMTPServer -Port $SMTPPort -Credential $SMTPCredentials  -Subject $SMTPSubject -BodyAsHtml $SMTPBody
echo "Email has been sent!"
} else {
echo "Nothing to send"
}

#Output info so can be viewed via Datto Stdout 
echo ""
echo $AllServers | ft
echo ""
echo "Excluded servers"
echo $AllServersOOP | ft
#Clear array if it has items do can be re-run in the same job
$AllServers.Clear()
if ($AllServersOOP) {$AllServersOOP.Clear()}

Here is also a copy of the script for easy download

Further Information

  • The script must be run in as an administrator or as system. Datto runs as system so that deals with it for us – it’s just something to be aware of if you run this via Task Scheduler.
  • If you wish for a server to be excluded from the pool, you must make a text file in the following path: C:\RDS\serversoutofpool.txt. Once created, you must add each server (including FQDN) on a new line.
  • Windows Firewall occasionally blocks the Uptime and Online status checks. If it’s run via Task Schedulder as a user, it doesn’t normally have this problem but it can do when running via Datto. I’m sure there’s a better way to check uptime, but it was just a “nice to have” feature rather than a requirement.
  • (Obvously) You will need to specificy your SMTP servers. We use SMTP2GO for this, but any email provider should work.

Hopefully this will help someone in the same way it’s helped us!