Tag Archives: Remote Desktop Services

ExportImportRdsDeployment module has been updated and it has Backup functionalities now


After releasing my Powershell Module ExportImportRdsDeployment here, which helps you to migrate your RDS deployment to a newer version, I got some great feedback from users.
So it was time to implement some feature requests and improve some things from the first version.
The ExportImportRdsDeployment module has been updated and it has Backup functionalities now…

Updating the Module

The module is again available in the Powershell Gallery, so you can easily install and use it on any Windows Server.
Make sure you download and use version 2.0.

To check your current version:

Check current version

If you already have version 1.0 or 1.1, I would suggest you uninstall these versions, and install the latest.
To do this, you simply execute this commands:

Install newest version

Now you can import the module again and start using the updated cmdlets

Import the module

The changes

Get-Help improvement

I improved the instructions and help you get when using the Get-Help cmdlet.
You get more information and updated examples for all 4 cmdlets in the module.


Get Help improvements

Optional removal of collections & servers from deployment

In the previous version, when you executed the Export-cmdlets, the collections & the servers were removed from the deployment.

In the new version, you can export the collections or export the servers using the Export-cmdlets, without the removal functionality.
This way,

  • You can test the export and validate the migration before performing the actual migration.
  • You can use the module to create daily backups of your deployment/collections and quickly restore in case of issues or wrong manipulations on your deployment.

This is how you do it:

The Export-cmdlets are in Export-Only mode by default.
So if you run the cmdlet as you did before, no removal will be performed at the end of the export.

You can specifically declare this too, to make sure no removal is executed, using the RemoveCollections & RemoveServers switch-parameters:

If you want to perform the migration, you must specify the -RemoveCollections or -RemoveServers parameter and confirm the export with removal.
With this safety feature, you cannot mistakenly remove anything.

Confirm export

Reboot Pending check when importing

When running the Import-RDDeploymentToConnectionBroker cmdlet, the module will check if there is a Reboot pending on the target machines (in the export XML file).
If there are reboots pending, you can let the cmdlet try to reboot the VMs, or do it manually.
You cannot continue with the import until the servers are rebooted.

Reboot pending overview

File access test for XML file

Before performing an export, the cmdlet will test if you have permissions and access to the XMLFile location you have specified (or to the default location).
If you do not have access, the cmdlet will stop the export.

Export before removing

When performing an export with -RemoveCollections or -RemoveServers parameter, the cmdlet will first perform the export to the XMLfile before removing the collection/deployment.
The writing to the XMLfile must succeed first before continuing.

[Microsoft.RemoteDesktopServices.Management.RDSessionCollectionType] on a Windows 2012 machine

On a Windows 2012 machine, the cmdlet will correctly check for the [Microsoft.RemoteDesktopServices.Management.RDSessionCollectionType] when exporting/importing.


With this update to the module, you should be able to migrate your entire deployment more easily than before, again on any platform (like Azure) and faster than performing an in-place upgrade.

If you have any questions or feature requests, do not hesitate to contact me using the comments, or via Twitter/LinkedIn.


Add Sessionhosts to your existing RDS deployment using ARM templates and Invoke-AzureRmVMRunCommand

Add Sessionhosts to your existing RDS deployment using ARM templates and Invoke-AzureRmVMRunCommand


If you are using ARM-templates & Desired State Configuration (DSC) for your RDS deployments, you will be able to cover almost all your needs.
But you could also use different options or technologies for your needs.

A great example is the combination of an ARM template with the CustomScriptExtension (for installing the RDS Sessionhost role on the VMs), the JsonADDomainExtension (to join the VM to a domain) and the new Invoke-AzureRmVMRunCommand cmdlet.

In this blog, I will show you how you can use ARM templates & the new Invoke-AzureRmVMRunCommand cmdlet to add new sessionhosts to your already existing RDS deployment, and more!

The Invoke-AzureRmVMRunCommand cmdlet

The new cmdlet allows you, as an admin of the Azure subscription, to “Invoke a run command on the VM“.
Run Command uses the VM agent to run PowerShell scripts within an Azure Windows VM. This means that the script will be executed as the Local System account (important to remember).
There are some restrictions to the cmdlet, which you can find here.

Most important thing to remember: the Powershell script you provide to the cmdlet is copied to the VM, and then executed under the Local System account.

The goal

The goal of this blog is to do these tasks:

  1. Create a new VM using an ARM Template
  2. Install the RDS Sessionhost role on the new VM using the CustomScriptExtension
  3. Join the new VM to a domain using the JsonADDomainExtension
  4. Add the new VM to an existing RDS deployment, create a new SessionCollection with the new VM, and set some basic SessionCollection settings using the Invoke-AzureRmVMRunCommand.

Because of all the great blogposts and examples on ARM Templates & RDS deployments (a great example: part 1 & part 2 up to part 7 from Freek Berson), I will not go deep dive into this.
I will touch the most important sections and parts of the template to complete tasks 1-3.
Task 4 will be the main topic of this blog.


Before you can start with any of this, you will need your basic setup.
You will need to have a VNET, subnet(s), an Active Directory setup and a full RDS deployment with RDS Connection Broker, Gateway & WebAccess in place.

Task 1-3: Create VM, install RDS Role & join to the domain.

Create VM using ARM Template

To create a VM on Azure, you have a lot of possibilities: using the Azure Portal, using Powershell, using Azure CLI, etc etc.
Another great option is using an ARM Template.
As I said before, there are so many great blogposts and examples, so I’m not going into detail about this.
Below, you can find a screenshot from my ARM Template which will be used for the next parts as well.
ARM Template Create VM

Install RDS Role using CustomScriptExtension

In the ARM template, you can already start customizing your VM to your needs.
This can be achieved using the CustomScriptExtension.

You first create a Powershell script.
For this blog, I created a script with the following content:

This will install the RDS-RD-Server role required to add to our RDS Deployment.

Next, you will need to store this script on an Azure Storage Account so it can be used in the ARM Template.
PS script On Storage Account

To link a CustomScriptExtension to a VM, you define it in the Resources section of the VM.
You add a resource from the type “Microsoft.Compute/virtualMachines/extensions”. (line 192)
You give the resource the name from your VM and add a name for the action. In this case I named it Fix-RDS (line 193).
The script information is located in the “properties” section.
First, you set the extension type to “CustomScriptExtension” (line 203).
Next, you specify the script Uri (line 208): this is the Uri from the Azure Storage Account, the blob container and filename (see previous screenshot).
Next, you will enter the command that needs to executed (line 212)
Last part is the security information to download the Powershell script. In the example, I’m using the StorageAccountName & Key (line 213 & 214).


Join to the domain using JsonADDomainExtension

Once the VM is created in Azure, you want to be able to access the VM directly. This can be made easier if the VM is directly joined in your existing Active Directory.
This can be done using the JsonADDomainExtension.

To link a JsonADDomainExtension to a VM, you use the “Microsoft.Compute/virtualMachines/extensions” resource again, but this is not linked under your VM section. It’s a separate section, not linked or under the VirtualMachine section as you can see in the screenshot below.
You give the resource the name from your VM and add a name for the action. In this case I named it “joindomain” (line 229).
First, you set the extension type to “JsonADDomainExtension” (line 233).
Next, you enter the Active Directory name & the FQDN from the user to perform the join. This user needs to have permissions to perform the join in your AD.
Last part is the password from that user (line 244), but this is entered in the protectedSettings part for security purposes.

ARM Template JsonADDomainExtension

Now you have an ARM Template that completes Task 1 till 3.

Task 4: Add the new VM to your existing RDS Deployment.

All previous steps were all done using an ARM Template.
This template deployment can be started from the Azure Portal, Visual Studio (Code) or using Azure CLI.
But it can also be started from a Powershell script using New-AzureRmResourceGroupDeployment.
Powershell New-AzureRmResourceGroupDeployment

And once you have started your deployment, the next step is easy.
You wait until the deployment is finished using the Job ID.

As soon as the deployment is finished, you can further customize your VM, and finish the RDS setup.

The command

Invoke-AzureRmVMRunCommand has a few required parameters:

  • ResourceGroupName: the resource group where the VM is located in.
  • CommandId: The type of command you want to execute. In my example, I’m going to use “RunPowerShellScript
  • VM / VMName / ResourceID: the VM where the command needs to be executed on.

When using “RunPowerShellScript” as CommandId, you will need to specify which script needs to be executed.
Therefor, you use the ScriptPath parameter. Here, you specify the path where the script is located.
Important: the script needs to be on the computer/server executing the Invoke-AzureRmVMRunCommand, so you need to specify the local path on the local computer/server.

Last part are the optional parameters that you need to provide to your own script.
You can create a hashtable containing all your parameters for your script and provide the hashtable to the cmdlet.

In my example, I execute the command on the Connection Broker. This is the most ideal VM I think to do this, because you are sure the necessary cmdlets are installed.

There are a few important things you need to remember!

  • If you want to use blanks in your parameters, you must use double quotes and escape them, as you can see in my example below.
    So for example: you want to pass the parameter “sessionCollection” with the value “Micha Demo Collection”, then you need to add an item to the hashtable like this:
    “sessionCollection” = “"Micha Demo Collection“”
  • Your script can contain parameters, but the parameters cannot be set to manditory.
    So your parameter cannot have this property: [Parameter(Mandatory=$true)].
    Otherwise, you will end up a vague error like you can see below.
    I already posted this into the Powershell Advisor group to see if this can be fixed.
    Invoke-AzureRmVMRunCommand : Long running operation failed with status ‘Failed’. Additional Info:’VM has reported a failure when processing extension ‘RunCommandWindows’. Error message: “Finished executing command”.’
    ErrorCode: VMExtensionProvisioningError
    ErrorMessage: VM has reported a failure when processing extension ‘RunCommandWindows’. Error message: “Finished executing command”.

The script

Now comes the hardest part, because as I said in the beginning, the command started by Invoke-AzureRmVMRunCommand is executed on the VM under the Local System account!
As you may know, when you execute the Add-RDServer cmdlet, the cmdlet will check if the role you specify is installed on the new VM. If it’s not installed, the cmdlet will try to install the role.
And the Local System account of your Connection Broker does not have permissions on the new VM.

So how do we fix this?

As stated in the beginning, the script is copied to the target VM.
And the easiest way to perform and control a RunAs, is through a Scheduled Task.

So here is the solution:

  1. Create an inner script inside the outer script
  2. Output the inner script to the target VM disk
  3. Create a Scheduled task to run the inner script as a user with enough permissions
  4. Start the task and wait.

The outer script is the script you have on your local computer, containing parameters, the inner script, the scheduled task creation part and the wait job.

Sounds complicated, but really isn’t. Let’s go over it step by step.

The Parameters

The outer script starts with the parameters you want to provide. In my example, it is just these simple parameters:

The Inner script

We want to output this inner script to a ps1-file, without the outer script to execute the inner script. The easiest way to do this is by using a Here-String in Powershell.
You create a variable, and start with @”. All the text following after this, is interpreted by Powershell as text in 1 variable, no matter how many lines or tabs you enter, until you end it with “@

So for this script, I add the basic commands to add a server to an existing RDS deployment.
I also added a screenshot to show you how it looks in Powershell ISE.

Powershell Inner Script

Output the script

Easiest part: Just pipe your script variable to the Out-File cmdlet. This will write the inner script to the local disk of the server.

The scheduled task

A bit more difficult, but once you get it, it’s easy as 1-2-3.
First you create “an action“. This is what the Scheduled Task is going to execute. In my example: the Inner script in Powershell that we just created on the VM(line 2).
Next, you can specify some settings. In my example, I set the Compatibility level to the highest. So on a Server2016, this will be Server 2016 level (line 3).
Last part is registering the Scheduled Task, specifing the user credentials, the action and the settings created just before that (line 4)

Start the Task and Wait

This is easy, especially when you work with variables for your TaskName.
Now you simply execute the Task using Start-ScheduledTask.
And then you query the status from the Task until it is Ready

That’s it!


Invoke-AzureRmVMRunCommand is a great way to finish a ARM Template deployment.
Because if you can start both from Powershell, you know which servers are created, and you can easily finish installations, RDS deployments, etc.

This is not the only way to do this, but’s a great way, easily manageble and highly automatable.