Hi How do I do if I want to run a PowerShell command on remote server and display the count result in a graph, the command I want to run is this and just display the count as the result. "$(Get-RDUserSession -connectionbroker "FQDN.local").count"
Total user count RDS Windows 2016
Votes:
0
Best Answer
Votes:
3
Okay - here you go:
param( $ServerNameSource, #Numeric script config parameter: 1 = Parameter ServerNames list / 2 = Parameter InputFile / 3 = LDAP query with name-prefix / 4 = LDAP query with filter on OU / 5 = LDAP query with name-prefix and OU filter $ServerNames, #depending on ServerNameSource (1) - a simple, coma separated list of server names e.g. @("server1", "server2", "server3") $ServerNamesInputFile, #depending on ServerNameSource (2) - path and name of a file that holds the server names, one servername per line - e.g.: "C:\servernames.txt" $ServerNamesLDAPnameFilter, #depending on ServerNameSource (3 and/or 5) - name search string - can hold wildcards (*) e.g.: "RDS*" $ServerNamesLDAPOUFilter, #depending on ServerNameSource (4 and/or 5) - e.g.: "OU=RDS hosts,DC=domain,DC=local" $SubstractSessionsPerHost = 2, #substract this amount of sessions per host - due to e.g. two RDS listeners (would make 2 sessions) or additional e.g. 1x ICA listener (would make 3 sessions) etc... $UseWMI = $false #use WMI - if not set it will use QWINSTA by default: enable with: $true ) #function to convert a string to proper XML and write it as output/screen Function WriteXmlToScreen ([xml]$xml) #just to make it clean XML code... { $StringWriter = New-Object System.IO.StringWriter; $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter; $XmlWriter.Formatting = "indented"; $xml.WriteTo($XmlWriter); $XmlWriter.Flush(); $StringWriter.Flush(); Write-Output $StringWriter.ToString(); } $ServerNameList; If ($ServerNameSource -gt 0 -and $ServerNameSource -lt 6) { Switch ($ServerNameSource) { 1 { $ServerNameList = $ServerNames; } 2 { $ServerNameList = Get-Content $ServerNamesInputFile; } 3 { $ServerNameList = (Get-ADComputer -LDAPFilter "(name=$ServerNamesLDAPnameFilter)" | Select Name).Name; } 4 { $ServerNameList = (Get-ADComputer -Filter "*" -SearchBase $ServerNamesLDAPOUFilter | Select Name).Name; } 5 { $ServerNameList = (Get-ADComputer -LDAPFilter "(name=$ServerNamesLDAPnameFilter)" -SearchBase $ServerNamesLDAPOUFilter | Select Name).Name; } } } Else { Write-Output "Missing Parameter or wrong value for: ServerNameSource"; Exit; } #if the array holds entries If ($ServerNameList.Count -gt 0) { $SessionsTotal = 0; $SessionsActive = 0; $ServersNotReached = 0; #go through the list ForEach ($ServerName in $ServerNameList) { #try to connect - on error jump to catch Try { $FoundTotal = 0; $FoundActive = 0; If ($UseWMI -eq $false) { #less control in theory - due to reformatting and searching through the results #single execution of the query / command $QWinstaResults = qwinsta /server $ServerName | ForEach-Object { $_.Trim() -replace "\s+","," } | ConvertFrom-Csv -Header "SessionName","UserName","ID","State","Type","Device"; #we add manual headers here, since this otherwise can cause issues with the filter later on $FoundTotal = $QWinstaResults.Count-1; #if we use QWINSTA we need to substract one more session from the results $FoundActive = ($QWinstaResults | ? { $_.State -eq "Active" }).Count; } else { #more control - but might run slower #in theory you can invoke all server-names at once, but then you need to run through the table and you lose some control over what was reached and what wasn't - first run might always be slowest depending on amount of servers $FoundTotal = (Get-WmiObject -Query "SELECT TotalSessions FROM Win32_TerminalService" -ComputerName $ServerName | Select TotalSessions).TotalSessions; $FoundActive = (Get-WmiObject -Query "SELECT ActiveSessions FROM Win32_PerfFormattedData_LocalSessionManager_TerminalServices" -ComputerName $ServerName | Select ActiveSessions).ActiveSessions; } #SessionsTotal might need a substract depending on the parameter input $FoundTotal = $FoundTotal - $SubstractSessionsPerHost; #we need to avoid that the substractions is negative, to avoid false substraction If ($FoundTotal -le 0){ $FoundTotal = 0; } #we now add the results to the current counters $SessionsTotal += $FoundTotal; $SessionsActive += $FoundActive; } Catch { $ServersNotReached += 1; } Finally { #nothing to do here } } #Lets put together the results $PRTGstring="<prtg> <result> <channel>Total Servers checked</channel> <value>" + $ServerNameList.Count + "</value> </result> <result> <channel>Total Servers responded</channel> <value>" + ($ServerNameList.Count - $ServersNotReached) + "</value> </result> <result> <channel>Total Servers not reached</channel> <value>$ServersNotReached</value> </result> <result> <channel>Total Sessions found</channel> <value>$SessionsTotal</value> </result> <result> <channel>Total Sessions active</channel> <value>$SessionsActive</value> </result> </prtg>" #we call the function WriteXmlToScreen to transform the $PRTGstring to XML and output it WriteXmlToScreen "$PRTGstring" } Else { Write-Output "Parameter Error or List did not contain any names / no server names found" Exit; }
Re-Wrote the whole script, it is now multi-variable - you can input ServerNames, a CSV file with server-names, LDAP name filter or LDAP OU filter or those two combined.
You can decide if you want to use WMI or QWinSta - WMI in my tests took to long to authenticate in the first run, QWINSTA is quicker - you might want to test what works best for you. The results will be the same.
I had issues with the QWINSTA and the later filtering - so I re-wrote the whole code and made sure it performs better by just executing it once instead of twice per server.
The results are now once response, 5x channels will be created and auto-reformatted as XML.
A detection of not query-able servers has been included - again - compare WMI against QWinSta to find what works best for you - the amount of servers you query is pretty intense.
You should be able to use your external file as server-name source, but I hope you can actually improve it with the combination of a name and OU filter per LDAP - cause a text-file is static and the amount of RDS systems in your case might be subject to change..
Let me know how it goes - I tried to test it thoroughly and will blog it out as well on my own blog.. this sure is a keeper. (https://www.it-admins.com/monitor-the-total-amount-of-sessions-on-your-rds-farm/). I mentioned there as well that I took the original script and created a total new version of it. Don't want to claim all the fame here!
Regards
Florian Rossmark
24 Replies
Votes:
0
Hello,
Thank you for the KB-Post. You can run Powershell Scripts with the Custom Exe/Script Advanced Sensor in PRTG. You need to make sure that the Powershell script contains code to connect to a target machine, because PRTG can only run the script on a Probe host machine.
Please bear in mind, as with all things customising, which includes script sensors, customizing on pages in PRTG (reports, map objects, etc.), users are on their own. This has to be done by the user. While we do have a few explanations and rough guides in our KB for that, as well as the API-Documentation, be aware, that it's only supposed to work as a base, and using it is at your own risk.
Please see our Guide For PowerShell Based Custom Sensors to start the project.
best regards.
Votes:
0
Did you look at this?
https://www.paessler.com/manuals/prtg/rdp_remote_desktop_sensor
This sensor shows you the active RDP handlers. Now, those are always +2 cause Windows works like this.
In theory - you could set this sensor on each of your session hosts - if you have multiple and want one single graph - showing the single hosts and additional one summarized number / graph - you could engage the Sensor Factory sensor - like this:
1. channel: Summarize each of the hosts sensors - from all subtract 2x sessions to have a one 100% number
2. to n. channel: Grab for each channel just one host - subtract 2x sessions to have the accurate number...
This sensor could be added to the broker e.g. - cause this would be the most accurate device to place it - while it does not really need a device at all - at least not a real one...
Regards
Florian Rossmark
Votes:
4
We are using a Powershell Script in Combination with qwinsta to monitor our whole Farm - so just 1 Sensor for all RDSH Servers. Something like this:
$Servers = Get-ADComputer -Filter {Name -like "rdsh*"} $SumTotal = 0 $SumActive = 0 $PRTGstring="<prtg>`r`n" #Für jeden Server werden nun die Sessions abgefragt und in den PRTGString gespeichert ForEach ($Server in $Servers) { $ServerName = $Server.Name $SessionsTotal = (qwinsta /server:$ServerName | ForEach-Object {$_.Trim() -replace "\s+",","} | ConvertFrom-Csv).Count-3 $SessionsActive = ((qwinsta /server:$ServerName | ForEach-Object {$_.Trim() -replace "\s+",","} | ConvertFrom-Csv) | ? { $_.$qwinstaState -eq $qwinstaActive }).Count #Da -3 oben gerechnet wird kann es sein das Total negativ ist. Ist das der Fall wird es auf 0 gesetzt if ($SessionsTotal -le 0){ $SessionsTotal = 0 } #Jetzt wird der PRTGString mit Channels und Werten zusammengesetzt $PRTGstring+=" <result>`r`n" $PRTGstring+=" <channel>"+$ServerName+" Total"+"</channel>`r`n" $PRTGstring+=" <value>"+$SessionsTotal+"</value>`r`n" $PRTGstring+=" </result>`r`n" $PRTGstring+=" <result>`r`n" $PRTGstring+=" <channel>"+$ServerName+" Active"+"</channel>`r`n" $PRTGstring+=" <value>"+$SessionsActive+"</value>`r`n" $PRTGstring+=" </result>`r`n" #Um am Ende auch die Summer aller Server zu erhalten $SumTotal += $SessionsTotal $SumActive += $SessionsActive } #Summe aller Server am Ende noch zusammensetzen $PRTGstring+=" <result>`r`n" $PRTGstring+=" <channel>"+"Summe Total"+"</channel>`r`n" $PRTGstring+=" <value>"+$SumTotal+"</value>`r`n" $PRTGstring+=" </result>`r`n" $PRTGstring+=" <result>`r`n" $PRTGstring+=" <channel>"+"Summe Active"+"</channel>`r`n" $PRTGstring+=" <value>"+$SumActive+"</value>`r`n" $PRTGstring+=" </result>`r`n" $PRTGstring+="</prtg>" #Ausgabe write-host $PRTGstring
Created on Jan 10, 2019 5:34:47 AM
Last change on Jan 10, 2019 6:20:10 AM by
Sven Roggenhofer [Paessler Technical Support]
Votes:
0
- Andreas Do you run it in a XML Custom sensor? when i run it i get this error "XML: Junk after document element </prtg> -- JSON: The returned JSON does not match the expected structure (No mapping for the Unicode character exists in the target multi-byte code page). (code: PE231)"
Votes:
0
Yes - EXE/XML Sensor Of Course you have to adopt the Script to serve your needs (other Servernames eg.)
Votes:
2
Your issue likely is the first line already...
$Servers = Get-ADComputer -Filter {Name -like "rdsh*"
You need to set the filter to something that will read all your session hosts from Active Directory. In the example RDSH* is used, so any computer in Active Directory where the name starts with RDSH....
If you can't do this, you need to find another way - what I then would do is e.g. set $Servers to a list of names:
$Servers = @("Servername1", "ServerDNSname2", "DNSservername3")
You then would need to adjust the following lines:
ForEach ($Server in $Servers) { $ServerName = $Server.Name
to something like this:
ForEach ($ServerName in $Servers) {
While using the @("","") for setting $Servers you actually create an array.
This now can be processed pretty easy in the ForEach loop, I also moved $ServerName in to the foreach line, cause in this case you have the $ServerName set manually in the array already, instead of requesting it from the more complex object that you receive from Active Directory.
In other words - yes - it is about modifying the script.
Hope it helps.
Florian
Votes:
0
I have now edit the script whit the suggestions from Florian and now everything works when I launch it in PowerShell, all 115 rdsh servers show data but when I running it in prtg in a Custom EXE/Script Advanced sensor I get all gauges but no values
Votes:
0
115 servers?
You are limited to 50 channels - meaning you need to split this up in at least 3 sensors - max. 50 servers per sensor - or e.g. based on the OU the server resides etc.. what ever helps to split it up.
This might also be the cause of you not seeing values.
Votes:
0
Yes it is a big environment, 3800 users is working in it at the same time every day.
Votes:
0
Well.. there sure is a lot more to monitor then just the total amount of users, e.g. RAM "eaters" and "CPU" eaters etc..
Did you get the sensor(s) running now?
Votes:
0
Unfortunately not but like i sad it works when I execute the ps1 file from the prtg server in PowerShell. If I look at the “exe result” in the prtg folder it look like this
No session exists for * No session exists for * No session exists for * No session exists for * No session exists for * <prtg> <result> <channel>xxxxRDSH000 Total</channel> <value>0</value> </result> <result> <channel>xxxxRDSH000 Active</channel> <value>0</value> </result> <result> <channel>xxxxRDSH001 Total</channel> <value>0</value> </result> <result> <channel>xxxxRDSH001 Active</channel> <value>0</value> </result> <result> <channel>xxxxRDSH002 Total</channel>
And when I look in PowerShell
PS H:\> C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\EXEXML\RDS_Active_Users_Monitor.ps1 <prtg> <result> <channel>xxxxRDSH000 Total</channel> <value>28</value> </result> <result> <channel>xxxxRDSH000 Active</channel> <value>31</value> </result> <result> <channel>xxxxRDSH001 Total</channel> <value>27</value> </result> <result> <channel>xxxxRDSH001 Active</channel> <value>30</value> </result> <result> <channel>xxxxRDSH002 Total</channel>
Created on Jan 14, 2019 11:49:33 AM
Last change on Jan 14, 2019 11:53:05 AM by
Sven Roggenhofer [Paessler Technical Support]
Votes:
0
In both cases you have a common issue - the XML code is incomplete.
There is a bit more to it.
QWINSTA is in theory doing nothing else then a WMI query - while it is a simple tool to accomplish this. The total amount of sessions is -3 in the original script, similar to what PRTG shows you. There are open session handlers. I am not certain about the -3 but it could be the original was written for a Citrix server, where I think there is one session on the ICA protocol while the two sessions on the RDP remain as well - listeners so to say.
You still have the other issue that you have a limitation of 50 channels, the script would create a channel per server and a sum-channel - a total of 116 in your case - what would cause another issue.
Having said this - do you only want to see the total amount of sessions in this one sensor - and let's say the total of found and the total of checked servers (might be you have 115, but if one server e.g. is not reachable, you might wanna know as well).
Are you doing a LDAP query to get the servers or did you inject them via the manual name-list as I mentioned?
As soon as I understand your goals better - I might be able to provide you an adjusted script that might work better in your case (PS: the original script is great - not that I say something wrong here - it just doesn't work in such a huge environment due to limitations of PRTG and etc.).
Florian
Votes:
0
Hi Florian Total amount of sessions in one sensor and total of found and the total of checked servers is good enough. I only want do display the information to the support and have the ability to see trend curves over time. I use $Servers = Get-Content "C:\Temp\servers.txt" as input
Created on Jan 14, 2019 3:14:55 PM
Last change on Jan 14, 2019 3:42:40 PM by
Torsten Lindner [Paessler Support]
Votes:
3
Okay - here you go:
param( $ServerNameSource, #Numeric script config parameter: 1 = Parameter ServerNames list / 2 = Parameter InputFile / 3 = LDAP query with name-prefix / 4 = LDAP query with filter on OU / 5 = LDAP query with name-prefix and OU filter $ServerNames, #depending on ServerNameSource (1) - a simple, coma separated list of server names e.g. @("server1", "server2", "server3") $ServerNamesInputFile, #depending on ServerNameSource (2) - path and name of a file that holds the server names, one servername per line - e.g.: "C:\servernames.txt" $ServerNamesLDAPnameFilter, #depending on ServerNameSource (3 and/or 5) - name search string - can hold wildcards (*) e.g.: "RDS*" $ServerNamesLDAPOUFilter, #depending on ServerNameSource (4 and/or 5) - e.g.: "OU=RDS hosts,DC=domain,DC=local" $SubstractSessionsPerHost = 2, #substract this amount of sessions per host - due to e.g. two RDS listeners (would make 2 sessions) or additional e.g. 1x ICA listener (would make 3 sessions) etc... $UseWMI = $false #use WMI - if not set it will use QWINSTA by default: enable with: $true ) #function to convert a string to proper XML and write it as output/screen Function WriteXmlToScreen ([xml]$xml) #just to make it clean XML code... { $StringWriter = New-Object System.IO.StringWriter; $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter; $XmlWriter.Formatting = "indented"; $xml.WriteTo($XmlWriter); $XmlWriter.Flush(); $StringWriter.Flush(); Write-Output $StringWriter.ToString(); } $ServerNameList; If ($ServerNameSource -gt 0 -and $ServerNameSource -lt 6) { Switch ($ServerNameSource) { 1 { $ServerNameList = $ServerNames; } 2 { $ServerNameList = Get-Content $ServerNamesInputFile; } 3 { $ServerNameList = (Get-ADComputer -LDAPFilter "(name=$ServerNamesLDAPnameFilter)" | Select Name).Name; } 4 { $ServerNameList = (Get-ADComputer -Filter "*" -SearchBase $ServerNamesLDAPOUFilter | Select Name).Name; } 5 { $ServerNameList = (Get-ADComputer -LDAPFilter "(name=$ServerNamesLDAPnameFilter)" -SearchBase $ServerNamesLDAPOUFilter | Select Name).Name; } } } Else { Write-Output "Missing Parameter or wrong value for: ServerNameSource"; Exit; } #if the array holds entries If ($ServerNameList.Count -gt 0) { $SessionsTotal = 0; $SessionsActive = 0; $ServersNotReached = 0; #go through the list ForEach ($ServerName in $ServerNameList) { #try to connect - on error jump to catch Try { $FoundTotal = 0; $FoundActive = 0; If ($UseWMI -eq $false) { #less control in theory - due to reformatting and searching through the results #single execution of the query / command $QWinstaResults = qwinsta /server $ServerName | ForEach-Object { $_.Trim() -replace "\s+","," } | ConvertFrom-Csv -Header "SessionName","UserName","ID","State","Type","Device"; #we add manual headers here, since this otherwise can cause issues with the filter later on $FoundTotal = $QWinstaResults.Count-1; #if we use QWINSTA we need to substract one more session from the results $FoundActive = ($QWinstaResults | ? { $_.State -eq "Active" }).Count; } else { #more control - but might run slower #in theory you can invoke all server-names at once, but then you need to run through the table and you lose some control over what was reached and what wasn't - first run might always be slowest depending on amount of servers $FoundTotal = (Get-WmiObject -Query "SELECT TotalSessions FROM Win32_TerminalService" -ComputerName $ServerName | Select TotalSessions).TotalSessions; $FoundActive = (Get-WmiObject -Query "SELECT ActiveSessions FROM Win32_PerfFormattedData_LocalSessionManager_TerminalServices" -ComputerName $ServerName | Select ActiveSessions).ActiveSessions; } #SessionsTotal might need a substract depending on the parameter input $FoundTotal = $FoundTotal - $SubstractSessionsPerHost; #we need to avoid that the substractions is negative, to avoid false substraction If ($FoundTotal -le 0){ $FoundTotal = 0; } #we now add the results to the current counters $SessionsTotal += $FoundTotal; $SessionsActive += $FoundActive; } Catch { $ServersNotReached += 1; } Finally { #nothing to do here } } #Lets put together the results $PRTGstring="<prtg> <result> <channel>Total Servers checked</channel> <value>" + $ServerNameList.Count + "</value> </result> <result> <channel>Total Servers responded</channel> <value>" + ($ServerNameList.Count - $ServersNotReached) + "</value> </result> <result> <channel>Total Servers not reached</channel> <value>$ServersNotReached</value> </result> <result> <channel>Total Sessions found</channel> <value>$SessionsTotal</value> </result> <result> <channel>Total Sessions active</channel> <value>$SessionsActive</value> </result> </prtg>" #we call the function WriteXmlToScreen to transform the $PRTGstring to XML and output it WriteXmlToScreen "$PRTGstring" } Else { Write-Output "Parameter Error or List did not contain any names / no server names found" Exit; }
Re-Wrote the whole script, it is now multi-variable - you can input ServerNames, a CSV file with server-names, LDAP name filter or LDAP OU filter or those two combined.
You can decide if you want to use WMI or QWinSta - WMI in my tests took to long to authenticate in the first run, QWINSTA is quicker - you might want to test what works best for you. The results will be the same.
I had issues with the QWINSTA and the later filtering - so I re-wrote the whole code and made sure it performs better by just executing it once instead of twice per server.
The results are now once response, 5x channels will be created and auto-reformatted as XML.
A detection of not query-able servers has been included - again - compare WMI against QWinSta to find what works best for you - the amount of servers you query is pretty intense.
You should be able to use your external file as server-name source, but I hope you can actually improve it with the combination of a name and OU filter per LDAP - cause a text-file is static and the amount of RDS systems in your case might be subject to change..
Let me know how it goes - I tried to test it thoroughly and will blog it out as well on my own blog.. this sure is a keeper. (https://www.it-admins.com/monitor-the-total-amount-of-sessions-on-your-rds-farm/). I mentioned there as well that I took the original script and created a total new version of it. Don't want to claim all the fame here!
Regards
Florian Rossmark
Votes:
0
Thanks now we are back in business
Votes:
0
Hi Florian, thanks for the script, I have a somewhat similar issue as described above. Manually executing the PS1 script returns the expected results.
But in PRTG after setting up the EXE/Script Advanced sensor it only returns a value on Servers Checked and Servers Responded.
Any idea why I am not seeing Sessions?
Votes:
0
Hi Mark,
Can you enable Write-Sensor-Results to disk and check the output?
I know you mailed me directly, if possible I would like to have at least the eventually solution posted here :-)
Regards Florian
Votes:
0
Was able to find the solution by simply adjusting the EXE Advanced sensor to 'Use Windows credentials of parent device'
Thanks for the help!
Votes:
0
Hi Florian,
first of all: Thank you so much for this script. This is exactly what I was looking for!
I have one problem:
In the first step I've tested the script with one RDS-server by using $ServerNames but the results are not as expected: It sais that there are no "Total Sessions active" but this is not correct. There was at least one active session. I have checked with the cmd.
Do you have any idea what could be the reason for this?
Many thanks!
<prtg> <result> <channel>Total Servers checked</channel> <value>1</value> </result> <result> <channel>Total Servers responded</channel> <value>1</value> </result> <result> <channel>Total Servers not reached</channel> <value>0</value> </result> <result> <channel>Total Sessions found</channel> <value>18</value> </result> <result> <channel>Total Sessions active</channel> <value>0</value> </result> </prtg>
Created on Jun 17, 2019 7:24:29 PM
Last change on Jun 18, 2019 4:16:46 AM by
Sven Roggenhofer [Paessler Technical Support]
Votes:
0
Hi Cortg,
Did you try the WMI switch?
Regards Florian
Votes:
0
thank for this script
I am zero in powershell and I would like to have a script that gives me the number of RDS connections for a server with the server as an argument
Votes:
0
Hi Flo,
Why don't you just use the default PRTG RDS connections sensor for this as mentioned further up in this article? This is doing that for a single server already (count -2 due to the listeners, though).
Besides that - I am not able to teach you PowerShell, I help out here on my free will and using my spare time, what is becoming an increasing issue lately for me due to some role-changes in my job - but that's another story..
How ever - there is a For-Each loop in the script - it runs through a list of servers and uses $ServerName as single server-instance... theoretically you could remove the surrounding loop and area where the script gathers the list from Active Directory and adjust the input parameters to accept a single $ServerName and done..
This is a good exercise to build some skills in PowerShell - all you need is already there - you stuff have to remove the overhead from the script.. As an IT manager I would and actually do encourage my team-members to do exactly that.. The knowledge gained from that helps them tremendously and encourages em to do even more with PowerShell (e.g. - of course I don't limit that to PowerShell).
Regards
Florian Rossmark
Votes:
0
Thanks for your help I use the default sensor in PRTG that I debugged
Add comment