When I stumbled across this article on IT Pro in June this year a knowing smile crept across my face. The author had discovered an extremely handy tool for managing multiple remote desktop connections and seemingly puzzled over why it wasn’t more well known; my thoughts exactly. I found this utility about 3 years ago when searching for an easier way to manage remote desktop connections to multiple Windows servers. It made absolutely no sense to have to open multiple unique Remote Desktop / mstsc.exe windows to every server you wanted to work on. I was very relieved to find RDCMan produced by Microsoft.
The downside to this simple tool though is that you have to add servers manually, one by one. With some 200 servers (at the time) this was a potentially painstaking task, and one that I didn’t want to undertake if there was an easier alternative. The .rdg file produced by RDCMan is actually just an XML file (discovered by dropping the file into Notepad) so I figured that it’d be quite straightforward to automate the production of this using PowerShell, grabbing a list of servers from AD (or .csv, .txt or wherever). Thankfully Jan Egil Ring had done some of the work for me, but I modified it slightly and turned it into a more suitable script for our environment. Pass the function a username, an array of computer objects and an output path and you get an RDCMan file with the computers of your choice for the user of your choice. Rather than call the script multiple times from Task Scheduler (i.e defining the parameters at the script level) I just schedule it once (on Monday morning) and within the script call the function multiple times for all the users who need a file.
Here’s the script:
<# .SYNOPSIS This generates a Remote Desktop Manager file for computer objects within Active Directory. .DESCRIPTION This generates a Remote Desktop Manager file for computer objects within Active Directory. Objects are generated using Microsoft's Active Directory module. Pass values required to the function rather than the script. It is based off "New-RDCManFile.ps1" by: Jan Egil Ring. .PARAMETER debugScript Switch on Write-Debug output. Default is No. .EXAMPLE C:\PS> New-RDCManFile.ps1 .NOTES Author: Jan Egil Ring | Robin Malik #> # Leave previous two lines blank #*============================================================================= #* PARAMETER DECLARATION #*============================================================================= #*============================================================================= #* REVISION HISTORY #*============================================================================= #* Date: YYYY-MM-DD #* Author: Your Name #* Purpose: Why and how you modified the script in brief. Do not delete #* previous revision history blocks. #* #* Date: YYYY-MM-DD #* Author: Your Name #* Purpose: Why and how you modified the script in brief. Do not delete #* previous revision history blocks. #*============================================================================= #*============================================================================= #* DEFINE GLOBAL VARIABLES #*============================================================================= $startDateTime = Get-Date $EnableEmail = 1 $DebugPreference = "SilentlyContinue" if($debugScript -eq "Yes"){ $DebugPreference = "Continue" # Write-Debug commands. $EnableEmail = Read-Host("Enable email, 0 = No, 1 = Yes [0/1]: ") } #*============================================================================= #* IMPORT SNAPINS AND MODULES #*============================================================================= try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Error $($Error[0].Exception.Message) # Send email or whatever... exit } #*============================================================================= #* IMPORT LIBRARIES #*============================================================================= #*============================================================================= #* EXCEPTION HANDLER #*============================================================================= #*============================================================================= #* FUNCTION LISTINGS #*============================================================================= #*============================================================================= #* Function: New-LURDCMFile #* ============================================================================ function New-RDCManFile { <# .SYNOPSIS This generates a Remote Desktop Manager file for computer objects within Active Directory. .DESCRIPTION This generates a Remote Desktop Manager file for computer objects within Active Directory. .PARAMETER username This username that you wish to be present in the RDG file by default. .PARAMETER outputPath The output path for the file (e.g. D:\). .PARAMETER computerArray Array of computer objects from Active Directory. .EXAMPLE Verb-LUServiceNoun -param1 "foo" -param2 "bar" .NOTES Author: Your Name #> # Leave previous two lines blank param( [Parameter(Mandatory=$true,HelpMessage="Admin account.")] [String] $username, [Parameter(Mandatory=$true,HelpMessage="Output Path for file.")] [String] $outputPath, [Parameter(Mandatory=$true,HelpMessage="Array of computers.")] [Array] $computerArray ) # Create a template XML. This needs to be indented to the margin so that the output XML file has no indent. $template = @' <?xml version="1.0" encoding="utf-8"?> <RDCMan schemaVersion="1"> <version>2.2</version> <file> <properties> <name></name> <expanded>True</expanded> <comment /> <logonCredentials inherit="FromParent" /> <connectionSettings inherit="FromParent" /> <gatewaySettings inherit="FromParent" /> <remoteDesktop inherit="FromParent" /> <localResources inherit="FromParent" /> <securitySettings inherit="FromParent" /> <displaySettings inherit="FromParent" /> </properties> <group> <properties> <name></name> <expanded>True</expanded> <comment /> <logonCredentials inherit="None"> <userName></userName> <domain></domain> <password storeAsClearText="False"></password> </logonCredentials> <connectionSettings inherit="FromParent" /> <gatewaySettings inherit="None"> <userName></userName> <domain></domain> <password storeAsClearText="False" /> <enabled>False</enabled> <hostName /> <logonMethod>4</logonMethod> <localBypass>False</localBypass> <credSharing>False</credSharing> </gatewaySettings> <remoteDesktop inherit="FromParent" /> <localResources inherit="FromParent" /> <securitySettings inherit="FromParent" /> <displaySettings inherit="FromParent" /> </properties> <server> <name></name> <displayName></displayName> <comment /> <logonCredentials inherit="FromParent" /> <connectionSettings inherit="FromParent" /> <gatewaySettings inherit="FromParent" /> <remoteDesktop inherit="FromParent" /> <localResources inherit="FromParent" /> <securitySettings inherit="FromParent" /> <displaySettings inherit="FromParent" /> </server> </group> </file> </RDCMan> '@ $outputFile = $outputPath + "-$username" + ".rdg" # Output $template to a temporary XML file: $template | Out-File $home\RDCMan-template.xml -encoding UTF8 # Load the XML template into XML object: $xml = New-Object xml $xml.Load("$home\RDCMan-template.xml") # Set the file properties: $file = (@($xml.RDCMan.file.properties)[0]).Clone() $file.name = $domain $xml.RDCMan.file.properties | Where-Object { $_.Name -eq "" } | ForEach-Object { [void]$xml.RDCMan.file.ReplaceChild($file,$_) } # Set the group properties $group = (@($xml.RDCMan.file.group.properties)[0]).Clone() $group.name = $env:userdomain $group.logonCredentials.Username = "$username" $group.logonCredentials.Domain = $domain $xml.RDCMan.file.group.properties | Where-Object { $_.Name -eq "" } | ForEach-Object { [void]$xml.RDCMan.file.group.ReplaceChild($group,$_) } # Use template to add servers from Active Directory to the XML $server = (@($xml.RDCMan.file.group.server)[0]).Clone() $computerArray | ForEach-Object { $server = $server.clone() [string]$server.DisplayName = $_.Name [string]$server.Name = $_.DNSHostName $xml.RDCMan.file.group.AppendChild($server) > $null} # Remove template server $xml.RDCMan.file.group.server | Where-Object { $_.Name -eq "" } | ForEach-Object { [void]$xml.RDCMan.file.group.RemoveChild($_) } # Save the XML object to a file $xml.Save($outputFile) # Remove the temporary XML file: Remove-Item $home\RDCMan-template.xml -Force } #*============================================================================= #* END OF FUNCTION LISTINGS #*============================================================================= #*============================================================================= #* SCRIPT BODY #*============================================================================= $domain = $Env:USERDOMAIN # Base output path: $outputPath = "C:\RDCMan" # Example to get a list of MemberServers and Domain Controllers: $computerObjects1 = Get-ADComputer -SearchBase "OU=MemberServers,DC=lunet,DC=lboro,DC=ac,DC=uk" -LDAPFilter "(operatingsystem=*Windows server*)" | Select-Object -property name,dnshostname $computerObjects2 = Get-ADComputer -SearchBase "OU=Domain Controllers,DC=lunet,DC=lboro,DC=ac,DC=uk" -LDAPFilter "(operatingsystem=*Windows server*)" | Select-Object -property name,dnshostname $allComputers = $computerObjects1 + $computerObjects2 | Sort-Object # Call the function to generate the file: $filePrefix = "allservers" New-RDCManFile -username "useraccount-admin" -outputPath "$outputPath\$filePrefix" -computerArray $allComputers New-RDCManFile -username "useraccount2-admin" -outputPath "$outputPath\$filePrefix" -computerArray $allComputers # Example to output a list of all SQL servers (from an AD security group): $filePrefix = "sqlservers" $sqlservers = Get-ADGroupMember -Identity "sql-servers" | Get-ADComputer | Select-Object -property name,dnshostname | Sort-Object -Property name # Call the function to generate the file: New-RDCManFile -username "useraccount-admin" -outputPath "$outputPath\$filePrefix" -computerArray $sqlservers # Optional block to output script execution time: #$endDateTime = Get-Date #$scriptExecutionMin = ($endDateTime.Subtract($startDateTime).Minutes) #$scriptExecutionSec = ($endDateTime.Subtract($startDateTime).Seconds) #Write-Output "Script execution time: $scriptExecutionMin min $scriptExecutionSec sec." #*============================================================================= #* END SCRIPT BODY #*============================================================================= #*============================================================================= #* END OF SCRIPT #*=============================================================================