1 #!powershell
2 
3 # Copyright: (c) 2018, Kevin Subileau (@ksubileau)
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 #Requires -Module Ansible.ModuleUtils.Legacy
7 #Requires -Module Ansible.ModuleUtils.SID
8 
9 $ErrorActionPreference = "Stop"
10 
11 # List of authentication methods as string. Used for parameter validation and conversion to integer flag, so order is important!
12 $auth_methods_set = @("none", "password", "smartcard", "both")
13 # List of session timeout actions as string. Used for parameter validation and conversion to integer flag, so order is important!
14 $session_timeout_actions_set = @("disconnect", "reauth")
15 
16 $params = Parse-Args -arguments $args -supports_check_mode $true
17 $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
18 $diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false
19 
20 $name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
21 $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present","enabled","disabled"
22 $auth_method = Get-AnsibleParam -obj $params -name "auth_method" -type "str" -validateset $auth_methods_set
23 $order = Get-AnsibleParam -obj $params -name "order" -type "int"
24 $session_timeout = Get-AnsibleParam -obj $params -name "session_timeout" -type "int"
25 $session_timeout_action = Get-AnsibleParam -obj $params -name "session_timeout_action" -type "str" -default "disconnect" -validateset $session_timeout_actions_set
26 $idle_timeout = Get-AnsibleParam -obj $params -name "idle_timeout" -type "int"
27 $allow_only_sdrts_servers = Get-AnsibleParam -obj $params -name "allow_only_sdrts_servers" -type "bool"
28 $user_groups = Get-AnsibleParam -obj $params -name "user_groups" -type "list"
29 $computer_groups = Get-AnsibleParam -obj $params -name "computer_groups" -type "list"
30 
31 # Device redirections
32 $redirect_clipboard = Get-AnsibleParam -obj $params -name "redirect_clipboard" -type "bool"
33 $redirect_drives = Get-AnsibleParam -obj $params -name "redirect_drives" -type "bool"
34 $redirect_printers = Get-AnsibleParam -obj $params -name "redirect_printers" -type "bool"
35 $redirect_serial = Get-AnsibleParam -obj $params -name "redirect_serial" -type "bool"
36 $redirect_pnp = Get-AnsibleParam -obj $params -name "redirect_pnp" -type "bool"
37 
38 
Get-CAP([string] $name)39 function Get-CAP([string] $name) {
40     $cap_path = "RDS:\GatewayServer\CAP\$name"
41     $cap = @{
42         Name = $name
43     }
44 
45     # Fetch CAP properties
46     Get-ChildItem -LiteralPath $cap_path | ForEach-Object { $cap.Add($_.Name,$_.CurrentValue) }
47     # Convert boolean values
48     $cap.Enabled = $cap.Status -eq 1
49     $cap.Remove("Status")
50     $cap.AllowOnlySDRTSServers = $cap.AllowOnlySDRTSServers -eq 1
51 
52     # Convert multiple choices values
53     $cap.AuthMethod = $auth_methods_set[$cap.AuthMethod]
54     $cap.SessionTimeoutAction = $session_timeout_actions_set[$cap.SessionTimeoutAction]
55 
56     # Fetch CAP device redirection settings
57     $cap.DeviceRedirection = @{}
58     Get-ChildItem -LiteralPath "$cap_path\DeviceRedirection" | ForEach-Object { $cap.DeviceRedirection.Add($_.Name, ($_.CurrentValue -eq 1)) }
59 
60     # Fetch CAP user and computer groups in Down-Level Logon format
61     $cap.UserGroups = @(
62         Get-ChildItem -LiteralPath "$cap_path\UserGroups" |
63             Select-Object -ExpandProperty Name |
64             ForEach-Object { Convert-FromSID -sid (Convert-ToSID -account_name $_) }
65     )
66     $cap.ComputerGroups = @(
67         Get-ChildItem -LiteralPath "$cap_path\ComputerGroups" |
68             Select-Object -ExpandProperty Name |
69             ForEach-Object { Convert-FromSID -sid (Convert-ToSID -account_name $_) }
70     )
71 
72     return $cap
73 }
74 
Set-CAPPropertyValuenull75 function Set-CAPPropertyValue {
76     [CmdletBinding(SupportsShouldProcess=$true)]
77     param (
78         [Parameter(Mandatory=$true)]
79         [string] $name,
80         [Parameter(Mandatory=$true)]
81         [string] $property,
82         [Parameter(Mandatory=$true)]
83         $value,
84         [Parameter()]
85         $resultobj = @{}
86     )
87 
88     $cap_path = "RDS:\GatewayServer\CAP\$name"
89 
90     try {
91         Set-Item -LiteralPath "$cap_path\$property" -Value $value -ErrorAction Stop
92     } catch {
93         Fail-Json -obj $resultobj -message "Failed to set property $property of CAP ${name}: $($_.Exception.Message)"
94     }
95 }
96 
97 $result = @{
98   changed = $false
99 }
100 $diff_text = $null
101 
102 # Validate CAP name
103 if ($name -match "[*/\\;:?`"<>|\t]+") {
104     Fail-Json -obj $result -message "Invalid character in CAP name."
105 }
106 
107 # Validate user groups
108 if ($null -ne $user_groups) {
109     if ($user_groups.Count -lt 1) {
110         Fail-Json -obj $result -message "Parameter 'user_groups' cannot be an empty list."
111     }
112 
113     $user_groups = $user_groups | ForEach-Object {
114         $group = $_
115         # Test that the group is resolvable on the local machine
116         $sid = Convert-ToSID -account_name $group
117         if (!$sid) {
118             Fail-Json -obj $result -message "$group is not a valid user group on the host machine or domain"
119         }
120 
121         # Return the normalized group name in Down-Level Logon format
122         Convert-FromSID -sid $sid
123     }
124     $user_groups = @($user_groups)
125 }
126 
127 # Validate computer groups
128 if ($null -ne $computer_groups) {
129     $computer_groups = $computer_groups | ForEach-Object {
130         $group = $_
131         # Test that the group is resolvable on the local machine
132         $sid = Convert-ToSID -account_name $group
133         if (!$sid) {
134             Fail-Json -obj $result -message "$group is not a valid computer group on the host machine or domain"
135         }
136 
137         # Return the normalized group name in Down-Level Logon format
138         Convert-FromSID -sid $sid
139     }
140     $computer_groups = @($computer_groups)
141 }
142 
143 # Validate order parameter
144 if ($null -ne $order -and $order -lt 1) {
145     Fail-Json -obj $result -message "Parameter 'order' must be a strictly positive integer."
146 }
147 
148 # Ensure RemoteDesktopServices module is loaded
149 if ($null -eq (Get-Module -Name RemoteDesktopServices -ErrorAction SilentlyContinue)) {
150     Import-Module -Name RemoteDesktopServices
151 }
152 
153 # Check if a CAP with the given name already exists
154 $cap_exist = Test-Path -LiteralPath "RDS:\GatewayServer\CAP\$name"
155 
156 if ($state -eq 'absent') {
157     if ($cap_exist) {
158         Remove-Item -LiteralPath "RDS:\GatewayServer\CAP\$name" -Recurse -WhatIf:$check_mode
159         $diff_text += "-[$name]"
160         $result.changed = $true
161     }
162 } else {
163     $diff_text_added_prefix = ''
164     if (-not $cap_exist) {
165         if ($null -eq $user_groups) {
166             Fail-Json -obj $result -message "User groups must be defined to create a new CAP."
167         }
168 
169         # Auth method is required when creating a new CAP. Set it to password by default.
170         if ($null -eq $auth_method) {
171             $auth_method = "password"
172         }
173 
174         # Create a new CAP
175         if (-not $check_mode) {
176             $CapArgs = @{
177                 Name = $name
178                 UserGroupNames = $user_groups -join ';'
179             }
180             $return = Invoke-CimMethod -Namespace Root\CIMV2\TerminalServices -ClassName Win32_TSGatewayConnectionAuthorizationPolicy -MethodName Create -Arguments $CapArgs
181             if ($return.ReturnValue -ne 0) {
182                 Fail-Json -obj $result -message "Failed to create CAP $name (code: $($return.ReturnValue))"
183             }
184         }
185 
186         $cap_exist = -not $check_mode
187 
188         $diff_text_added_prefix = '+'
189         $result.changed = $true
190     }
191 
192     $diff_text += "$diff_text_added_prefix[$name]`n"
193 
194     # We cannot configure a CAP that was created above in check mode as it won't actually exist
195     if($cap_exist) {
196         $cap = Get-CAP -Name $name
197         $wmi_cap = Get-CimInstance -ClassName Win32_TSGatewayConnectionAuthorizationPolicy -Namespace Root\CIMv2\TerminalServices -Filter "name='$($name)'"
198 
199         if ($state -in @('disabled', 'enabled')) {
200             $cap_enabled = $state -ne 'disabled'
201             if ($cap.Enabled -ne $cap_enabled) {
202                 $diff_text += "-State = $(@('disabled', 'enabled')[[int]$cap.Enabled])`n+State = $state`n"
203                 Set-CAPPropertyValue -Name $name -Property Status -Value ([int]$cap_enabled) -ResultObj $result -WhatIf:$check_mode
204                 $result.changed = $true
205             }
206         }
207 
208         if ($null -ne $auth_method -and $auth_method -ne $cap.AuthMethod) {
209             $diff_text += "-AuthMethod = $($cap.AuthMethod)`n+AuthMethod = $auth_method`n"
210             Set-CAPPropertyValue -Name $name -Property AuthMethod -Value ([array]::IndexOf($auth_methods_set, $auth_method)) -ResultObj $result -WhatIf:$check_mode
211             $result.changed = $true
212         }
213 
214         if ($null -ne $order -and $order -ne $cap.EvaluationOrder) {
215             # Order cannot be greater than the total number of existing CAPs (InvalidArgument exception)
216             $cap_count =  (Get-ChildItem -LiteralPath "RDS:\GatewayServer\CAP").Count
217             if($order -gt $cap_count) {
218                 Add-Warning -obj $result -message "Given value '$order' for parameter 'order' is greater than the number of existing CAPs. The actual order will be capped to '$cap_count'."
219                 $order = $cap_count
220             }
221 
222             $diff_text += "-Order = $($cap.EvaluationOrder)`n+Order = $order`n"
223             Set-CAPPropertyValue -Name $name -Property EvaluationOrder -Value $order -ResultObj $result -WhatIf:$check_mode
224             $result.changed = $true
225         }
226 
227         if ($null -ne $session_timeout -and ($session_timeout -ne $cap.SessionTimeout -or $session_timeout_action -ne $cap.SessionTimeoutAction)) {
228             try {
229                 Set-Item -Path "RDS:\GatewayServer\CAP\$name\SessionTimeout" `
230                     -Value $session_timeout `
231                     -SessionTimeoutAction ([array]::IndexOf($session_timeout_actions_set, $session_timeout_action)) `
232                     -ErrorAction Stop `
233                     -WhatIf:$check_mode
234             } catch {
235                 Fail-Json -obj $result -message "Failed to set property ComputerGroupType of RAP ${name}: $($_.Exception.Message)"
236             }
237 
238             $diff_text += "-SessionTimeoutAction = $($cap.SessionTimeoutAction)`n+SessionTimeoutAction = $session_timeout_action`n"
239             $diff_text += "-SessionTimeout = $($cap.SessionTimeout)`n+SessionTimeout = $session_timeout`n"
240             $result.changed = $true
241         }
242 
243         if ($null -ne $idle_timeout -and $idle_timeout -ne $cap.IdleTimeout) {
244             $diff_text += "-IdleTimeout = $($cap.IdleTimeout)`n+IdleTimeout = $idle_timeout`n"
245             Set-CAPPropertyValue -Name $name -Property IdleTimeout -Value $idle_timeout -ResultObj $result -WhatIf:$check_mode
246             $result.changed = $true
247         }
248 
249         if ($null -ne $allow_only_sdrts_servers -and $allow_only_sdrts_servers -ne $cap.AllowOnlySDRTSServers) {
250             $diff_text += "-AllowOnlySDRTSServers = $($cap.AllowOnlySDRTSServers)`n+AllowOnlySDRTSServers = $allow_only_sdrts_servers`n"
251             Set-CAPPropertyValue -Name $name -Property AllowOnlySDRTSServers -Value ([int]$allow_only_sdrts_servers) -ResultObj $result -WhatIf:$check_mode
252             $result.changed = $true
253         }
254 
255         if ($null -ne $redirect_clipboard -and $redirect_clipboard -ne $cap.DeviceRedirection.Clipboard) {
256             $diff_text += "-RedirectClipboard = $($cap.DeviceRedirection.Clipboard)`n+RedirectClipboard = $redirect_clipboard`n"
257             Set-CAPPropertyValue -Name $name -Property "DeviceRedirection\Clipboard" -Value ([int]$redirect_clipboard) -ResultObj $result -WhatIf:$check_mode
258             $result.changed = $true
259         }
260 
261         if ($null -ne $redirect_drives -and $redirect_drives -ne $cap.DeviceRedirection.DiskDrives) {
262             $diff_text += "-RedirectDrives = $($cap.DeviceRedirection.DiskDrives)`n+RedirectDrives = $redirect_drives`n"
263             Set-CAPPropertyValue -Name $name -Property "DeviceRedirection\DiskDrives" -Value ([int]$redirect_drives) -ResultObj $result -WhatIf:$check_mode
264             $result.changed = $true
265         }
266 
267         if ($null -ne $redirect_printers -and $redirect_printers -ne $cap.DeviceRedirection.Printers) {
268             $diff_text += "-RedirectPrinters = $($cap.DeviceRedirection.Printers)`n+RedirectPrinters = $redirect_printers`n"
269             Set-CAPPropertyValue -Name $name -Property "DeviceRedirection\Printers" -Value ([int]$redirect_printers) -ResultObj $result -WhatIf:$check_mode
270             $result.changed = $true
271         }
272 
273         if ($null -ne $redirect_serial -and $redirect_serial -ne $cap.DeviceRedirection.SerialPorts) {
274             $diff_text += "-RedirectSerial = $($cap.DeviceRedirection.SerialPorts)`n+RedirectSerial = $redirect_serial`n"
275             Set-CAPPropertyValue -Name $name -Property "DeviceRedirection\SerialPorts" -Value ([int]$redirect_serial) -ResultObj $result -WhatIf:$check_mode
276             $result.changed = $true
277         }
278 
279         if ($null -ne $redirect_pnp -and $redirect_pnp -ne $cap.DeviceRedirection.PlugAndPlayDevices) {
280             $diff_text += "-RedirectPnP = $($cap.DeviceRedirection.PlugAndPlayDevices)`n+RedirectPnP = $redirect_pnp`n"
281             Set-CAPPropertyValue -Name $name -Property "DeviceRedirection\PlugAndPlayDevices" -Value ([int]$redirect_pnp) -ResultObj $result -WhatIf:$check_mode
282             $result.changed = $true
283         }
284 
285         if ($null -ne $user_groups) {
286             $groups_to_remove = @($cap.UserGroups | Where-Object { $user_groups -notcontains $_ })
287             $groups_to_add = @($user_groups | Where-Object { $cap.UserGroups -notcontains $_ })
288 
289             $user_groups_diff = $null
290             foreach($group in $groups_to_add) {
291                 if (-not $check_mode) {
292                     $return = $wmi_cap | Invoke-CimMethod -MethodName AddUserGroupNames -Arguments @{ UserGroupNames = $group }
293                     if ($return.ReturnValue -ne 0) {
294                         Fail-Json -obj $result -message "Failed to add user group $($group) (code: $($return.ReturnValue))"
295                     }
296                 }
297                 $user_groups_diff += "  +$group`n"
298                 $result.changed = $true
299             }
300 
301             foreach($group in $groups_to_remove) {
302                 if (-not $check_mode) {
303                     $return = $wmi_cap | Invoke-CimMethod -MethodName RemoveUserGroupNames -Arguments @{ UserGroupNames = $group }
304                     if ($return.ReturnValue -ne 0) {
305                         Fail-Json -obj $result -message "Failed to remove user group $($group) (code: $($return.ReturnValue))"
306                     }
307                 }
308                 $user_groups_diff += "  -$group`n"
309                 $result.changed = $true
310             }
311 
312             if($user_groups_diff) {
313                 $diff_text += "~UserGroups`n$user_groups_diff"
314             }
315         }
316 
317         if ($null -ne $computer_groups) {
318             $groups_to_remove = @($cap.ComputerGroups | Where-Object { $computer_groups -notcontains $_ })
319             $groups_to_add = @($computer_groups | Where-Object { $cap.ComputerGroups -notcontains $_ })
320 
321             $computer_groups_diff = $null
322             foreach($group in $groups_to_add) {
323                 if (-not $check_mode) {
324                     $return = $wmi_cap | Invoke-CimMethod -MethodName AddComputerGroupNames -Arguments @{ ComputerGroupNames = $group }
325                     if ($return.ReturnValue -ne 0) {
326                         Fail-Json -obj $result -message "Failed to add computer group $($group) (code: $($return.ReturnValue))"
327                     }
328                 }
329                 $computer_groups_diff += "  +$group`n"
330                 $result.changed = $true
331             }
332 
333             foreach($group in $groups_to_remove) {
334                 if (-not $check_mode) {
335                     $return = $wmi_cap | Invoke-CimMethod -MethodName RemoveComputerGroupNames -Arguments @{ ComputerGroupNames = $group }
336                     if ($return.ReturnValue -ne 0) {
337                         Fail-Json -obj $result -message "Failed to remove computer group $($group) (code: $($return.ReturnValue))"
338                     }
339                 }
340                 $computer_groups_diff += "  -$group`n"
341                 $result.changed = $true
342             }
343 
344             if($computer_groups_diff) {
345                 $diff_text += "~ComputerGroups`n$computer_groups_diff"
346             }
347         }
348     }
349 }
350 
351 if ($diff_mode -and $result.changed -eq $true) {
352     $result.diff = @{
353         prepared = $diff_text
354     }
355 }
356 
357 Exit-Json $result
358