Skip to content

RDS 2012 Session Deployment PowerShell Scripts

Technical Article

Automate an RDS 2012 session-based deployment end to end. Two PowerShell scripts: one for a single-server deployment that hosts RDCB, RDWA and RDSH on the same box, and one for a multi-server deployment with separate broker, web access, and session host. Each script ends by creating a session collection and publishing notepad.exe as a smoke test.

Categories
Rds 2012MicrosoftAutomation
Tags
Access ServerDeployment OfficeMember ServerMicrosoftMicrosoft WindowsOperating SystemPowershellPowershell ScriptsRds2012Remote DesktopRemote Desktop ServicesScriptsServer DeploymentServersSession HostTarget ServerWeb AccessWindows PowershellNew RdsessiondeploymentNew Rdsessioncollection
RDS 2012 Session Deployment PowerShell Scripts

These two scripts are for administrators who want to stand up a Remote Desktop Services 2012 / 2012 R2 session-based deployment without clicking through Server Manager. The first script targets a single all-in-one server (RDCB, RDWA and RDSH on the same FQDN); the second separates each role onto its own server. Both end with a session collection and a published RemoteApp as a smoke test that the broker is healthy.

What the scripts do

  • Run New-RDSessionDeployment to install the three core RDS roles (RD Connection Broker, RD Web Access, RD Session Host).
  • For the multi-server variant, Add-RDServer adds any further session hosts to the deployment after the initial install.
  • Create a session collection with New-RDSessionCollection so users have something to connect to.
  • Publish notepad.exe as a RemoteApp via New-RDRemoteApp to confirm the broker is reachable and the collection is functional.
  • Must be run from a domain member that is not one of the target servers, because the RDCB and RDSH installations reboot the target servers mid-run.

Run the deployment script from a separate management host. If you run it on the target server, the role install will queue a reboot and the script will fail mid-flight.

Prerequisites

  • All target servers are domain joined, fully patched, and accessible by FQDN from the host running the script.
  • The account running the script is a member of the local Administrators group on every target server.
  • The RemoteDesktop PowerShell module is available. It ships with the RSAT-RDS-Tools feature, and is auto-loaded on a Connection Broker. On a plain management host install it with: Install-WindowsFeature -Name RSAT-RDS-Tools.
  • WinRM is reachable (TCP 5985) on each target server. Domain-joined Server 2012 has WinRM enabled by default.

Script 1: single-server deployment

The single-server script installs RDCB, RDWA and RDSH onto one FQDN. This is the lab / proof-of-concept shape: small footprint, one reboot, one server to monitor.

#requires -RunAsAdministrator
#requires -Modules RemoteDesktop, ServerManager
<#
.SYNOPSIS
    Stands up an RDS 2012 / 2012 R2 session deployment on a single server.

.PARAMETER Server
    FQDN of the server that will host RDCB, RDWA, and RDSH.

.PARAMETER CollectionName
    Friendly name for the session collection. Defaults to 'SessionCollection'.

.EXAMPLE
    Invoke-RdsSingleServerDeployment -Server 'rds01.contoso.com'
#>
function Invoke-RdsSingleServerDeployment {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Server,

        [string]$CollectionName = 'SessionCollection',

        [string]$CollectionDescription = 'Default session collection created by Invoke-RdsSingleServerDeployment.'
    )

    Import-Module RemoteDesktop -ErrorAction Stop

    # 1. Has the deployment already been created? If yes, skip the install step.
    $existing = Get-RDServer -ConnectionBroker $Server -ErrorAction SilentlyContinue
    if (-not $existing) {
        Write-Host "Creating session deployment on $Server ..." -ForegroundColor Cyan
        New-RDSessionDeployment `
            -ConnectionBroker $Server `
            -WebAccessServer  $Server `
            -SessionHost      $Server `
            -ErrorAction Stop
    }
    else {
        Write-Host "Deployment already exists on $Server. Skipping New-RDSessionDeployment." -ForegroundColor Yellow
    }

    # 2. Create the session collection if it does not already exist.
    $collection = Get-RDSessionCollection -ConnectionBroker $Server -ErrorAction SilentlyContinue |
        Where-Object { $_.CollectionName -eq $CollectionName }

    if (-not $collection) {
        Write-Host "Creating session collection '$CollectionName' ..." -ForegroundColor Cyan
        New-RDSessionCollection `
            -CollectionName        $CollectionName `
            -SessionHost           $Server `
            -CollectionDescription $CollectionDescription `
            -ConnectionBroker      $Server `
            -ErrorAction Stop
    }
    else {
        Write-Host "Collection '$CollectionName' already exists. Skipping New-RDSessionCollection." -ForegroundColor Yellow
    }

    # 3. Publish notepad.exe as a smoke test that the broker is reachable.
    $remoteApp = Get-RDRemoteApp -CollectionName $CollectionName -ConnectionBroker $Server -ErrorAction SilentlyContinue |
        Where-Object { $_.Alias -eq 'notepad' }

    if (-not $remoteApp) {
        Write-Host "Publishing notepad.exe as a RemoteApp smoke test ..." -ForegroundColor Cyan
        New-RDRemoteApp `
            -CollectionName    $CollectionName `
            -DisplayName       'Notepad' `
            -FilePath          'C:\Windows\System32\notepad.exe' `
            -Alias             'notepad' `
            -ConnectionBroker  $Server `
            -ErrorAction Stop
    }
    else {
        Write-Host "Notepad RemoteApp already published. Skipping." -ForegroundColor Yellow
    }

    Write-Host "`nDeployment complete. Verify with:" -ForegroundColor Green
    Write-Host "  Get-RDServer            -ConnectionBroker $Server"
    Write-Host "  Get-RDSessionCollection -ConnectionBroker $Server"
    Write-Host "  Get-RDRemoteApp         -ConnectionBroker $Server -CollectionName $CollectionName"
}

Script 2: multi-server deployment

The multi-server script splits the roles across separate servers: one for the Connection Broker, one for Web Access, and one (or more) for Session Host. This is the production shape: each role can be scaled independently, and a session host outage no longer takes the broker down with it.

#requires -RunAsAdministrator
#requires -Modules RemoteDesktop, ServerManager
<#
.SYNOPSIS
    Stands up an RDS 2012 / 2012 R2 session deployment across separate
    Connection Broker, Web Access, and Session Host servers.

.PARAMETER ConnectionBroker
    FQDN of the server that will host the RD Connection Broker role.

.PARAMETER WebAccessServer
    FQDN of the server that will host the RD Web Access role.

.PARAMETER SessionHost
    One or more FQDNs of the servers that will host the RD Session Host role.
    The first entry is used in New-RDSessionDeployment; any further entries
    are added with Add-RDServer.

.PARAMETER CollectionName
    Friendly name for the session collection. Defaults to 'SessionCollection'.

.EXAMPLE
    Invoke-RdsMultiServerDeployment `
        -ConnectionBroker 'rdcb01.contoso.com' `
        -WebAccessServer  'rdwa01.contoso.com' `
        -SessionHost      'rdsh01.contoso.com', 'rdsh02.contoso.com'
#>
function Invoke-RdsMultiServerDeployment {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ConnectionBroker,

        [Parameter(Mandatory)]
        [string]$WebAccessServer,

        [Parameter(Mandatory)]
        [string[]]$SessionHost,

        [string]$CollectionName = 'SessionCollection',

        [string]$CollectionDescription = 'Default session collection created by Invoke-RdsMultiServerDeployment.'
    )

    Import-Module RemoteDesktop -ErrorAction Stop

    $primaryHost     = $SessionHost[0]
    $additionalHosts = $SessionHost | Select-Object -Skip 1

    # 1. Create the deployment if it does not already exist.
    $existing = Get-RDServer -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue
    if (-not $existing) {
        Write-Host "Creating session deployment ..." -ForegroundColor Cyan
        Write-Host "  RDCB: $ConnectionBroker"
        Write-Host "  RDWA: $WebAccessServer"
        Write-Host "  RDSH: $primaryHost"

        New-RDSessionDeployment `
            -ConnectionBroker $ConnectionBroker `
            -WebAccessServer  $WebAccessServer `
            -SessionHost      $primaryHost `
            -ErrorAction Stop
    }
    else {
        Write-Host "Deployment already exists on $ConnectionBroker. Skipping New-RDSessionDeployment." -ForegroundColor Yellow
    }

    # 2. Add any extra session hosts that were not part of the initial deployment.
    #    Use $rdsh as the iterator variable; $host is a PowerShell automatic variable
    #    and shadowing it inside a foreach is a lint warning waiting to happen.
    foreach ($rdsh in $additionalHosts) {
        $alreadyAdded = Get-RDServer -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue |
            Where-Object { $_.Server -eq $rdsh -and $_.Roles -contains 'RDS-RD-SERVER' }

        if (-not $alreadyAdded) {
            Write-Host "Adding additional session host: $rdsh" -ForegroundColor Cyan
            Add-RDServer `
                -Server           $rdsh `
                -Role             RDS-RD-SERVER `
                -ConnectionBroker $ConnectionBroker `
                -ErrorAction Stop
        }
        else {
            Write-Host "Session host $rdsh already in deployment. Skipping." -ForegroundColor Yellow
        }
    }

    # 3. Create the collection across all session hosts if not already present.
    $collection = Get-RDSessionCollection -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue |
        Where-Object { $_.CollectionName -eq $CollectionName }

    if (-not $collection) {
        Write-Host "Creating session collection '$CollectionName' across $($SessionHost.Count) host(s) ..." -ForegroundColor Cyan
        New-RDSessionCollection `
            -CollectionName        $CollectionName `
            -SessionHost           $SessionHost `
            -CollectionDescription $CollectionDescription `
            -ConnectionBroker      $ConnectionBroker `
            -ErrorAction Stop
    }
    else {
        Write-Host "Collection '$CollectionName' already exists. Skipping New-RDSessionCollection." -ForegroundColor Yellow
    }

    # 4. Publish notepad.exe as a smoke test that the broker is reachable.
    $remoteApp = Get-RDRemoteApp -CollectionName $CollectionName -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue |
        Where-Object { $_.Alias -eq 'notepad' }

    if (-not $remoteApp) {
        Write-Host "Publishing notepad.exe as a RemoteApp smoke test ..." -ForegroundColor Cyan
        New-RDRemoteApp `
            -CollectionName   $CollectionName `
            -DisplayName      'Notepad' `
            -FilePath         'C:\Windows\System32\notepad.exe' `
            -Alias            'notepad' `
            -ConnectionBroker $ConnectionBroker `
            -ErrorAction Stop
    }
    else {
        Write-Host "Notepad RemoteApp already published. Skipping." -ForegroundColor Yellow
    }

    Write-Host "`nDeployment complete. Verify with:" -ForegroundColor Green
    Write-Host "  Get-RDServer            -ConnectionBroker $ConnectionBroker"
    Write-Host "  Get-RDSessionCollection -ConnectionBroker $ConnectionBroker"
    Write-Host "  Get-RDRemoteApp         -ConnectionBroker $ConnectionBroker -CollectionName $CollectionName"
}

How to use them

# Run from a separate domain-joined management host (NOT a target server):

# Single-server lab deployment:
. .\Invoke-RdsSingleServerDeployment.ps1
Invoke-RdsSingleServerDeployment -Server 'rds01.contoso.com' -Verbose

# Multi-server production-shaped deployment:
. .\Invoke-RdsMultiServerDeployment.ps1
Invoke-RdsMultiServerDeployment `
    -ConnectionBroker 'rdcb01.contoso.com' `
    -WebAccessServer  'rdwa01.contoso.com' `
    -SessionHost      'rdsh01.contoso.com', 'rdsh02.contoso.com' `
    -Verbose

If the execution policy blocks the script, set it for the current process only: Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned. Avoid Unrestricted at machine scope.

Security notes

  • Least privilege on the management host. Use a dedicated deployment account that is in the local Administrators group on each target server, not Domain Admin. Remove the account after the install completes.
  • Restrict WinRM at the firewall. TCP 5985 only needs to be open between the management host and the target servers. Lock it down with -RemoteAddress on the WinRM firewall rule.
  • Reboots are part of the install. RDCB, RDWA, and RDSH role installs queue reboots on the target servers. Run the script outside business hours or in a maintenance window.
  • Idempotency. Both scripts use Get-RDServer, Get-RDSessionCollection, and Get-RDRemoteApp exists-checks before each mutation, so re-running them is safe.
  • Rollback. If the deployment ends up in a half-built state, Remove-RDSessionCollection and Remove-RDServer (in reverse order of install) take it back to bare metal. Snapshot the target VMs first if you can.

Original 2013 scripts

The original 2013 post linked to two scripts hosted on OneDrive (now retired). The two functions above reproduce their behaviour using the documented RDS 2012 cmdlets (New-RDSessionDeployment, Add-RDServer, New-RDSessionCollection, New-RDRemoteApp), and add explicit idempotency checks, -ErrorAction Stop, and a Notepad smoke-test publish so you can confirm the broker is healthy without leaving the script.