PowerShell Desired State Configuration Class Based Resource for Configuring Remote Desktop

Prior to PowerShell version 5 being released, I had written a PowerShell version 4 compatible DSC (Desired State Configuration) resource named cMrRDP for configuring Remote Desktop. It can be found in my DSC respository on GitHub. The recommendation at that point was to use the letter "c" as the prefix for community created DSC resources. The current recommendation is to no longer use the "c" prefix for DSC resources. Steven Murawski wrote a blog article titled DSC People - Let's Stop Using 'c' Now that I recommend taking a look at.

The cMrRDP DSC resource only had the three required functions, Get-TargetResource, Set-TargetResource, and Test-TargetResource and lots of the code was duplicated between the functions. A better approach is to create private functions within the resource that the required functions call. This design eliminates code duplication, makes troubleshooting easier, and simplifies both the code and the functions themselves. Since the functions are simpler, writing unit test for them is also simpler.

I've rewritten that resource as a class based DSC resource and renamed it as MrRemoteDesktop. This resource can also be found in my DSC repository on GitHub. Class based DSC resources require PowerShell version 5 on the system used for authoring the resource and on the system that the configuration is going to be applied to. With class based resources, Get(), Set(), and Test() methods take the place of the previously referenced required functions. One huge benefit to class based resources is it doesn't require a MOF file for the resource itself. This makes writing the resource simpler and modifications no longer require major rework or starting from scratch.

The following code example has been saved as MrRemoteDesktop.psm1 in the $env:ProgramFiles\WindowsPowerShell\Modules\MrRemoteDesktop\ folder.

  1enum Ensure {
  2    Absent
  3    Present
  4}
  5enum UserAuthenication {
  6    NonSecure
  7    Secure
  8}
  9
 10[DscResource()]
 11class RemoteDesktop {
 12
 13    [DscProperty(Key)]
 14    [UserAuthenication]$UserAuthenication
 15
 16    [DscProperty(Mandatory)]
 17    [Ensure]$Ensure
 18
 19    [RemoteDesktop]Get() {
 20
 21        $this.UserAuthenication = $this.GetAuthSetting()
 22        $this.Ensure = $this.GetTSSetting()
 23
 24        Return $this
 25
 26    }
 27
 28    [void]Set(){
 29
 30        if ($this.TestAuthSetting() -eq $false) {
 31            $this.SetAuthSetting($this.UserAuthenication)
 32        }
 33
 34        if ($this.TestTSSetting() -eq $false) {
 35            $this.SetTSSetting($this.Ensure)
 36        }
 37
 38    }
 39
 40    [bool]Test(){
 41
 42        if ($this.TestAuthSetting() -and $this.TestTSSetting() -eq $true) {
 43            Return $true
 44        }
 45        else {
 46            Return $false
 47        }
 48
 49    }
 50
 51    [string]GetAuthSetting(){
 52
 53        $AuthCurrentSetting = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' |
 54                              Select-Object -ExpandProperty UserAuthentication
 55
 56        $AuthSetting = switch ($AuthCurrentSetting) {
 57            0 {'NonSecure'; Break}
 58            1 {'Secure'; Break}
 59        }
 60
 61        Return $AuthSetting
 62
 63    }
 64
 65    [string]GetTSSetting(){
 66
 67        $TSCurrentSetting = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name 'fDenyTSConnections' |
 68                            Select-Object -ExpandProperty fDenyTSConnections
 69
 70        $TSSetting = switch ($TSCurrentSetting) {
 71            0 {'Present'; Break}
 72            1 {'Absent'; Break}
 73        }
 74
 75        Return $TSSetting
 76
 77    }
 78
 79    [void]SetAuthSetting([UserAuthenication]$UserAuthenication){
 80
 81        switch ($this.UserAuthenication) {
 82            'NonSecure' {$Script:AuthDesiredSetting = 0; Break}
 83            'Secure' {$Script:AuthDesiredSetting = 1; Break}
 84        }
 85
 86        Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value $Script:AuthDesiredSetting
 87
 88    }
 89
 90    [void]SetTSSetting([Ensure]$Ensure){
 91
 92        switch ($this.Ensure) {
 93            'Present' {$Script:TSDesiredSetting = 0; Break}
 94            'Absent' {$Script:TSDesiredSetting = 1; Break}
 95        }
 96
 97        Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name 'fDenyTSConnections' -Value $Script:TSDesiredSetting
 98
 99    }
100
101    [bool]TestAuthSetting(){
102
103        if ($this.UserAuthenication -eq $this.GetAuthSetting()){
104            Return $true
105        }
106        else {
107            Return $false
108        }
109
110    }
111
112    [bool]TestTSSetting(){
113
114        if ($this.Ensure -eq $this.GetTSSetting()){
115            Return $true
116        }
117        else {
118            Return $false
119        }
120
121    }
122
123}

You'll also need a module manifest file. The module manifest shown in the following example has been saved as MrRemoteDesktop.psd1 in the same folder as the script module file.

 1@{
 2
 3# Script module or binary module file associated with this manifest.
 4RootModule = 'MrRemoteDesktop.psm1'
 5
 6DscResourcesToExport = 'RemoteDesktop'
 7
 8# Version number of this module.
 9ModuleVersion = '1.0'
10
11# ID used to uniquely identify this module
12GUID = '30fa4e0e-5233-4eae-ac2d-853e7b9fd8dc'
13
14# Author of this module
15Author = 'Mike F Robbins'
16
17# Company or vendor of this module
18CompanyName = 'mikefrobbins.com'
19
20# Copyright statement for this module
21Copyright = '(c) 2016 Mike F Robbins. All rights reserved.'
22
23# Description of the functionality provided by this module
24Description = 'Module with DSC Resource for enabling RDP access to a Computer'
25
26# Minimum version of the Windows PowerShell engine required by this module
27PowerShellVersion = '5.0'
28
29}

Remote Desktop is currently enabled on the server named SQL01:

dsc-remote-desktop1a.png

This server has the server core (no-GUI) installation of Windows Server 2012 R2 and I don't want admins using remote desktop to connect to it. They should either use PowerShell to remotely administer it or install the GUI tools on their workstation or a server that's dedicated for management of other systems (what I call a jump box).

A configuration to disable RDP is written, the MOF file for SQL01 is generated, and it's applied using pull mode:

 1Configuration RDP {
 2    Import-DSCResource -ModuleName MrRemoteDesktop
 3
 4    Node SQL01 {
 5
 6        RemoteDesktop RDP {
 7            UserAuthenication = 'Secure'
 8            Ensure = 'Absent'
 9        }
10
11    }
12}
13
14RDP
15Start-DscConfiguration -Path .\RDP -ComputerName SQL01 -Wait -Verbose -Force

dsc-remote-desktop2a.png

Now that the DSC configuration has been applied to SQL01, remote desktop is disabled on it:

dsc-remote-desktop3a.png

Found a bug or a better way of accomplishing something shown in the code used in this blog article? Feel free to contribute by forking the repository and submitting a pull request.

Update: Be sure to read the follow-up to this blog article: Simplifying my PowerShell version 5 Class Based DSC Resource for Configuring Remote Desktop.

µ