ACTUALIZACIÓN 12/09/2020: Charlotte Bishop de Comparitech me ha escrito amablemente indicándome que tras descubrirse y publicarse una vulnerabilidad de seguridad en Microsoft Remote Desktop Connection Manager, concretamente la CVE-2020-0765, Microsoft ha decidido descontinuar el producto y eliminar el enlace de descarga. Así que lamentablemente lo que leas en este artículo tiene poco uso a efectos de RDCMan, pero te puede seguir resultado interesante como ejemplo de manipulación de archivos XML con PowerShell.

Comparitech ha publicado una comparativa de clientes de escritorio remoto alternativo, que si bien es estupenda, servidor echa en falta mRemoteNG, Remote Desktop Manager y RoyalTS. Es hora de despedirse de RDCMan y darle las gracias por la cantidad de horas que hemos echado con él, haciendo más fácil nuestro trabajo.

Dicho sea de paso, servidor no tiene ninguna relación profesional ni personal con Comparitech, los cuales simplemente me han avisado de la situación, acción que les agradezco.

Si eres un profesional de sistemas del mundo Microsoft la probabilidad de que conozcas el Remote Desktop Connection Manager es bastante alta, un software que nos permite abrir múltiples conexiones de Escritorio Remoto desde un mismo programa y gestionarlas con mucha facilidad.

Cuando trabajamos en Azure esta herramienta es de gran utilidad para agrupar todos los accesos RDP a nuestras máquinas virtuales y conectarnos rápidamente a ellas. Sin embargo, configurarlo es en ocasiones tedioso debido a que debemos ir recopilando las IPs o DNS de las máquinas a las que nos vamos a conectar. ¿Os imagináis ir agregando una a una 50 máquinas virtuales?

Investigando un poco RDCMan he podido ver que sus archivos de configuración están hechos en XML, bastante bien formado y fáciles de entender... ¡y PowerShell se maneja realmente bien con este formato! No todo iba a ser JSON en la vida...

Así pues decidí que no sería muy complicado hacer un pequeño script de PowerShell que, una vez conectado a una suscripción de Azure Resource Manager consulte automáticamente las máquinas virtuales del grupo de recursos que decidamos y nos genere el archivo correspondiente. ¡Esto nos ahorraría mucho tiempo a la hora de empezar a trabajar!

El script se puede descargar desde aquí y tiene el siguiente aspecto:

<#
.SYNOPSIS
    Generates a RDCMan connection file with IP addresses from Azure Virtual Machines.
.DESCRIPTION
    This PowerShell script will use your active Azure Resource Manager subscription and get all the virtual machines IP addresses from a resource group. After that it will build a RDCMan compatible file with the retrieved information, easying the connection to the resources.
    
    You can download RDCMan from https://www.microsoft.com/en-us/download/details.aspx?id=44989
.PARAMETER ResourceGroupName
    The resource group name where we are going to search for Virtual Machines. If you use * in this parameter it will retrieve the virtual machines from every single resource group in the subscription.
.PARAMETER Name
    The name our RDCMan tree will have. If empty it will take the ResourceGroupName value.
    Provide a PARAMETER section for each parameter that your script or function accepts.
.PARAMETER IncludeLinux
    Usually, you won't connect through RDP to Linux virtual machines, so they won't be of any use to RDCMan, but if you want to include them, turn on this switch.
.PARAMETER PreferPublicIP
    If the script finds a virtual machines with both, public and private IP addresses, prefer the public one.
.PARAMETER OutputXmlFile
    The file name used to save the generated RDCMan config file. Path will be current script execution.
.EXAMPLE
    Generate-AzureRmRdcFile.ps1 -ResourceGroupName * -Name "HOME Machines" -PreferPublicIP -OutputXmlFile home.rdg
.LINK
    https://calnus.com
.LINK
    RDCMan download: https://www.microsoft.com/en-us/download/details.aspx?id=44989
.NOTES
    Carlos Milán Figueredo
    https://www.calnus.com

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
    INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
#>

param(
    [Parameter(Mandatory=$true)]
    [String]$ResourceGroupName,
    [Parameter(Mandatory=$true)]
    [String]$Name=$ResourceGroupName,
    [Switch]$IncludeLinux,
    [Switch]$PreferPublicIP,
    [Parameter(Mandatory=$true)]
    [String]$OutputXmlFile
    )

$rdgXmlFile = [XML]'<?xml version="1.0" encoding="utf-8"?>
<RDCMan programVersion="2.7" schemaVersion="3">
  <file>
    <properties>
      <expanded>True</expanded>
      <name></name>
    </properties>
  </file>
  <connected />
  <favorites />
  <recentlyUsed />
</RDCMan>'

Write-Output "Get-AzureRMmRdcFile.ps1 - The Azure Resource Manager RDCMan generator"
Write-Output "by Carlos Milán Figueredo - https://www.calnus.com"

$rdgXmlFile.RDCMan.file.properties.name = $Name

if($ResourceGroupName -eq '*')
{
    $VMs = Get-AzureRmVm
} else {
    $VMs = Get-AzureRmVm -ResourceGroupName $ResourceGroupName
}
Write-Output "Found $($VMs.Count) virtual machines..."
foreach($vm in $VMs)
{
    Write-Output ""
    Write-Output "========== $($vm.name) is based on $($vm.StorageProfile.OsDisk.OsType) OS"
    Write-Output "Found $($vm.NetworkInterfaceIDs.Count) network interface(s)"
    if(($vm.StorageProfile.OsDisk.OsType -eq "Windows") -or $IncludeLinux)
    {
        foreach($nicId in $vm.NetworkInterfaceIDs)
        {
            $nic = Get-AzureRmResource -ResourceId $nicId
            Write-Output "Got $($nic.Name) interface."
            Write-Output "* Private IP: $($nic.Properties.ipConfigurations.Properties.privateIPAddress)"
            Write-Output "* Private IP Allocation Method: $($nic.Properties.ipConfigurations.Properties.privateIPAllocationMethod)"
            $ip=$nic.Properties.ipConfigurations.Properties.privateIPAddress
            try
            {
                $pip = Get-AzureRmResource -ResourceId $nic.Properties.ipConfigurations.Properties.PublicIpAddress.Id
                Write-Output "* Public IP: $($pip.Properties.ipAddress)"
                Write-Output "* Public IP Allocation Method: $($pip.Properties.publicIPAllocationMethod)"
                if($PreferPublicIp)
                {
                    $ip=$pip.Properties.ipAddress
                }
            } catch {
                Write-Output "* Public IP: not found"
            }
            if($vm.NetworkInterfaceIDs.Count -le 1)
            {
                $xmlDisplayNameTextNode=$rdgXmlFile.CreateTextNode($vm.Name)
            } else {
                $xmlDisplayNameTextNode=$rdgXmlFile.CreateTextNode($nic.Name)
            }
            $xmlNameTextNode=$rdgXmlFile.CreateTextNode($ip)
            $xmlServerElement=$rdgXmlFile.CreateElement("server")
            $xmlPropElement=$rdgXmlFile.CreateElement("properties")
            $xmlNameElement=$rdgXmlFile.CreateElement("name")
            $xmlDisplayNameElement=$rdgXmlFile.CreateElement("displayName")
            [void]$xmlDisplayNameElement.AppendChild($xmlDisplayNameTextNode)
            [void]$xmlNameElement.AppendChild($xmlNameTextNode)
            [void]$xmlPropElement.AppendChild($xmlDisplayNameElement)
            [void]$xmlPropElement.AppendChild($xmlNameElement)
            [void]$xmlServerElement.AppendChild($xmlPropElement)
            [void]$rdgXmlFile.RDCMan.file.AppendChild($xmlServerElement)
        }
    }
}
Write-Output "`r`nWritting file to $PSScriptRoot\$OutputXmlFile"
$rdgXmlFile.Save("$PSScriptRoot\$OutputXmlFile")
Write-Output "Done!"

Para usarlo basta con tener una conexión de Azure abierta con Add-AzureRmAccount y ejecutarlo con los parámetros correspondientes, entre ellos:

  • ResourceGroupName. Nombre del grupo de recursos del que vamos a retirar la información de conexión de todas las máquinas virtuales. Si aquí especificamos ***** obtendremos la información de todas las máquinas virtuales existentes en la suscripción.
  • Name. El nombre que queremos darle al la raíz del archivo de configuración de RDCMan. Si no especificamos este parámetro se tomará el mismo valor que de ResourceGroupName.
  • IncludeLinux. Habitualmente no se accede a las máquinas basadas en Linux a través de RDP, sino por SSH, que queda totalmente fuera del ámbito de RDCMan. Por tanto, el comportamiento por defecto del script es obviar estas máquinas. Sin embargo, si por alguna razón quisiéramos incluirlas, no tenemos más que especificar este parámetro.
  • PreferPublicIP. Puede darse el caso de que una máquina virtual tenga IP pública. Si especificamos este parámetro el script preferirá configurar RDCMan con la IP pública de la máquina virtual, en lugar de la privada.
  • OutputXmlFile. Nombre del archivo a guardar, que debería tener extensión rdg.

El script en sí no tiene mucho de especial, pero si os surge alguna pregunta o sugerencia, no tenéis más que dejarme un comentario y lo hablamos.

Happy RDCing!