1 #!powershell
2
3 # Copyright: (c) 2020 VMware, Inc. All Rights Reserved.
4 # SPDX-License-Identifier: GPL-3.0-only
5 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7 #AnsibleRequires -CSharpUtil Ansible.Basic
8
9 $spec = @{
10 options = @{
11 type = @{ type = "str"; choices = "reservation", "lease"; default = "reservation" }
12 ip = @{ type = "str" }
13 scope_id = @{ type = "str" }
14 mac = @{ type = "str" }
15 duration = @{ type = "int" }
16 dns_hostname = @{ type = "str"; }
17 dns_regtype = @{ type = "str"; choices = "aptr", "a", "noreg"; default = "aptr" }
18 reservation_name = @{ type = "str"; }
19 description = @{ type = "str"; }
20 state = @{ type = "str"; choices = "absent", "present"; default = "present" }
21 }
22 required_if = @(
23 @("state", "present", @("mac", "ip"), $true),
24 @("state", "absent", @("mac", "ip"), $true)
25 )
26 supports_check_mode = $true
27 }
28
29 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
30 $check_mode = $module.CheckMode
31
32 $type = $module.Params.type
33 $ip = $module.Params.ip
34 $scope_id = $module.Params.scope_id
35 $mac = $module.Params.mac
36 $duration = $module.Params.duration
37 $dns_hostname = $module.Params.dns_hostname
38 $dns_regtype = $module.Params.dns_regtype
39 $reservation_name = $module.Params.reservation_name
40 $description = $module.Params.description
41 $state = $module.Params.state
42
Convert-MacAddressnull43 Function Convert-MacAddress {
44 Param(
45 [string]$mac
46 )
47
48 # Evaluate Length
49 if ($mac.Length -eq 12) {
50 # Insert Dashes
51 $mac = $mac.Insert(2, "-").Insert(5, "-").Insert(8, "-").Insert(11, "-").Insert(14, "-")
52 return $mac
53 }
54 elseif ($mac.Length -eq 17) {
55 # Remove Colons
56 if($mac -like "*:*:*:*:*:*") {
57 return ($mac -replace ':')
58 }
59 # Remove Dashes
60 if ($mac -like "*-*-*-*-*-*") {
61 return ($mac -replace '-')
62 }
63 }
64 else {
65 return $false
66 }
67 }
68
Compare-DhcpLease()69 Function Compare-DhcpLease {
70 Param(
71 [PSObject]$Original,
72 [PSObject]$Updated
73 )
74
75 # Compare values that we care about
76 -not (
77 ($Original.AddressState -eq $Updated.AddressState) -and
78 ($Original.IPAddress -eq $Updated.IPAddress) -and
79 ($Original.ScopeId -eq $Updated.ScopeId) -and
80 ($Original.Name -eq $Updated.Name) -and
81 ($Original.Description -eq $Updated.Description)
82 )
83 }
84
Convert-ReturnValue()85 Function Convert-ReturnValue {
86 Param(
87 $Object
88 )
89
90 return @{
91 address_state = $Object.AddressState
92 client_id = $Object.ClientId
93 ip_address = $Object.IPAddress.IPAddressToString
94 scope_id = $Object.ScopeId.IPAddressToString
95 name = $Object.Name
96 description = $Object.Description
97 }
98 }
99
100 # Parse Regtype
101 if ($dns_regtype) {
102 Switch ($dns_regtype) {
103 "aptr" { $dns_regtype = "AandPTR"; break }
104 "a" { $dns_regtype = "A"; break }
105 "noreg" { $dns_regtype = "NoRegistration"; break }
106 default { $dns_regtype = "NoRegistration"; break }
107 }
108 }
109
110 Try {
111 # Import DHCP Server PS Module
112 Import-Module DhcpServer
113 }
114 Catch {
115 # Couldn't load the DhcpServer Module
116 $module.FailJson("The DhcpServer module failed to load properly: $($_.Exception.Message)", $_)
117 }
118
119 # Determine if there is an existing lease
120 if ($ip) {
121 $current_lease = Get-DhcpServerv4Scope | Get-DhcpServerv4Lease | Where-Object IPAddress -eq $ip
122 }
123
124 # MacAddress was specified
125 if ($mac) {
126 if ($mac -like "*-*") {
127 $mac_original = $mac
128 $mac = Convert-MacAddress -mac $mac
129 }
130
131 if ($mac -eq $false) {
132 $module.FailJson("The MAC Address is not properly formatted")
133 }
134 else {
135 $current_lease = Get-DhcpServerv4Scope | Get-DhcpServerv4Lease | Where-Object ClientId -eq $mac_original
136 }
137 }
138
139 # Did we find a lease/reservation
140 if ($current_lease) {
141 $current_lease_exists = $true
142 $original_lease = $current_lease
143 $module.Diff.before = Convert-ReturnValue -Object $original_lease
144 }
145 else {
146 $current_lease_exists = $false
147 }
148
149 # If we found a lease, is it a reservation?
150 if ($current_lease_exists -eq $true -and ($current_lease.AddressState -like "*Reservation*")) {
151 $current_lease_reservation = $true
152 }
153 else {
154 $current_lease_reservation = $false
155 }
156
157 # State: Absent
158 # Ensure the DHCP Lease/Reservation is not present
159 if ($state -eq "absent") {
160 # If the lease doesn't exist, our work here is done
161 if ($current_lease_exists -eq $false) {
162 $module.Result.msg = "The lease doesn't exist."
163 } else {
164 # If the lease exists, we need to destroy it
165 if ($current_lease_reservation -eq $true) {
166 # Try to remove reservation
167 Try {
168 $current_lease | Remove-DhcpServerv4Reservation -WhatIf:$check_mode
169 $state_absent_removed = $true
170 }
171 Catch {
172 $state_absent_removed = $false
173 $remove_err = $_
174 }
175 }
176 else {
177 # Try to remove lease
178 Try {
179 $current_lease | Remove-DhcpServerv4Lease -WhatIf:$check_mode
180 $state_absent_removed = $true
181 }
182 Catch {
183 $state_absent_removed = $false
184 $remove_err = $_
185 }
186 }
187
188 # See if we removed the lease/reservation
189 if ($state_absent_removed) {
190 $module.Result.changed = $true
191 }
192 else {
193 $module.Result.lease = Convert-ReturnValue -Object $current_lease
194 $module.FailJson("Unable to remove lease/reservation: $($remove_err.Exception.Message)", $remove_err)
195 }
196 }
197 }
198
199 # State: Present
200 # Ensure the DHCP Lease/Reservation is present, and consistent
201 if ($state -eq "present") {
202 # Current lease exists, and is not a reservation
203 if (($current_lease_reservation -eq $false) -and ($current_lease_exists -eq $true)) {
204 if ($type -eq "reservation") {
205 Try {
206 # Update parameters
207 $params = @{ }
208
209 if ($mac) {
210 $params.ClientId = $mac
211 } else {
212 $params.ClientId = $current_lease.ClientId
213 }
214
215 if ($description) {
216 $params.Description = $description
217 } else {
218 $params.Description = $current_lease.Description
219 }
220
221 if ($reservation_name) {
222 $params.Name = $reservation_name
223 } else {
224 $params.Name = "reservation-" + $params.ClientId
225 }
226
227 # Desired type is reservation
228 $current_lease | Add-DhcpServerv4Reservation -WhatIf:$check_mode
229
230 if(-not $check_mode) {
231 $current_reservation = Get-DhcpServerv4Lease -ClientId $params.ClientId -ScopeId $current_lease.ScopeId
232 }
233
234 # Update the reservation with new values
235 $current_reservation | Set-DhcpServerv4Reservation @params -WhatIf:$check_mode
236
237 if(-not $check_mode) {
238 $updated_reservation = Get-DhcpServerv4Lease -ClientId $params.ClientId -ScopeId $current_reservation.ScopeId
239 }
240
241 if(-not $check_mode) {
242 # Compare Values
243 $module.Result.changed = Compare-DhcpLease -Original $original_lease -Updated $updated_reservation
244 $module.Result.lease = Convert-ReturnValue -Object $updated_reservation
245 } else {
246 $module.Result.changed = $true
247 }
248
249 $module.ExitJson()
250 }
251 Catch {
252 $module.FailJson("Could not convert lease to a reservation",$_)
253 }
254 }
255 }
256
257 # Current lease exists, and is a reservation
258 if (($current_lease_reservation -eq $true) -and ($current_lease_exists -eq $true)) {
259 if ($type -eq "lease") {
260 Try {
261 # Desired type is a lease, remove the reservation
262 $current_lease | Remove-DhcpServerv4Reservation -WhatIf:$check_mode
263
264 # Build a new lease object with remnants of the reservation
265 $lease_params = @{
266 ClientId = $original_lease.ClientId
267 IPAddress = $original_lease.IPAddress.IPAddressToString
268 ScopeId = $original_lease.ScopeId.IPAddressToString
269 HostName = $original_lease.HostName
270 AddressState = 'Active'
271 }
272
273 # Create new lease
274 Try {
275 Add-DhcpServerv4Lease @lease_params -WhatIf:$check_mode
276 }
277 Catch {
278 $module.FailJson("Unable to convert the reservation to a lease",$_)
279 }
280
281 # Get the lease we just created
282 if(-not $check_mode) {
283 Try {
284 $new_lease = Get-DhcpServerv4Lease -ClientId $lease_params.ClientId -ScopeId $lease_params.ScopeId
285 }
286 Catch {
287 $module.FailJson("Unable to retreive the newly created lease",$_)
288 }
289 }
290
291 if(-not $check_mode) {
292 $module.Result.lease = Convert-ReturnValue -Object $new_lease
293 }
294
295 $module.Result.changed = $true
296 $module.ExitJson()
297 }
298 Catch {
299 $module.FailJson("Could not convert reservation to lease",$_)
300 }
301 }
302
303 # Already in the desired state
304 if ($type -eq "reservation") {
305
306 # Update parameters
307 $params = @{ }
308
309 if ($mac) {
310 $params.ClientId = $mac
311 } else {
312 $params.ClientId = $current_lease.ClientId
313 }
314
315 if ($description) {
316 $params.Description = $description
317 } else {
318 $params.Description = $current_lease.Description
319 }
320
321 if ($reservation_name) {
322 $params.Name = $reservation_name
323 } else {
324 # Original lease had a null name so let's generate one
325 if ($null -eq $original_lease.Name) {
326 $params.Name = "reservation-" + $original_lease.ClientId
327 } else {
328 $params.Name = $original_lease.Name
329 }
330 }
331
332 # Update the reservation with new values
333 $current_lease | Set-DhcpServerv4Reservation @params -WhatIf:$check_mode
334
335 if(-not $check_mode) {
336 $reservation = Get-DhcpServerv4Lease -ClientId $current_lease.ClientId -ScopeId $current_lease.ScopeId
337 $module.Result.changed = Compare-DhcpLease -Original $original_lease -Updated $reservation
338 $module.Result.lease = Convert-ReturnValue -Object $reservation
339 } else {
340 $module.Result.changed = $true
341 }
342
343 # Return values
344 $module.ExitJson()
345 }
346 }
347
348 # Lease Doesn't Exist - Create
349 if ($current_lease_exists -eq $false) {
350 # Required: Scope ID
351 if (-not $scope_id) {
352 $module.Result.changed = $false
353 $module.FailJson("The scope_id parameter is required for state=present when a lease or reservation doesn't already exist")
354 }
355
356 # Required Parameters
357 $lease_params = @{
358 ClientId = $mac
359 IPAddress = $ip
360 ScopeId = $scope_id
361 AddressState = 'Active'
362 Confirm = $false
363 }
364
365 if ($duration) {
366 $lease_params.LeaseExpiryTime = (Get-Date).AddDays($duration)
367 }
368
369 if ($dns_hostname) {
370 $lease_params.HostName = $dns_hostname
371 }
372
373 if ($dns_regtype) {
374 $lease_params.DnsRR = $dns_regtype
375 }
376
377 if ($description) {
378 $lease_params.Description = $description
379 }
380
381 # Create Lease
382 Try {
383 # Create lease based on parameters
384 Add-DhcpServerv4Lease @lease_params -WhatIf:$check_mode
385
386 # Retreive the lease
387 if(-not $check_mode) {
388 $new_lease = Get-DhcpServerv4Lease -ClientId $mac -ScopeId $scope_id
389 $module.Result.lease = Convert-ReturnValue -Object $new_lease
390 }
391
392 # If lease is the desired type
393 if ($type -eq "lease") {
394 $module.Result.changed = $true
395 $module.ExitJson()
396 }
397 }
398 Catch {
399 # Failed to create lease
400 $module.FailJson("Could not create DHCP lease: $($_.Exception.Message)",$_)
401 }
402
403 # Create Reservation
404 Try {
405 # If reservation is the desired type
406 if ($type -eq "reservation") {
407 if ($reservation_name) {
408 $lease_params.Name = $reservation_name
409 }
410 else {
411 $lease_params.Name = "reservation-" + $mac
412 }
413
414 Try {
415 if($check_mode) {
416 # In check mode, a lease won't exist for conversion, make one manually
417 Add-DhcpServerv4Reservation -ScopeId $scope_id -ClientId $mac_original -IPAddress $ip -WhatIf:$check_mode
418 } else {
419 # Convert to Reservation
420 $new_lease | Add-DhcpServerv4Reservation -WhatIf:$check_mode
421 }
422 }
423 Catch {
424 # Failed to create reservation
425 $module.FailJson("Could not create DHCP reservation: $($_.Exception.Message)", $_)
426 }
427
428 if(-not $check_mode) {
429 # Get DHCP reservation object
430 $new_lease = Get-DhcpServerv4Reservation -ClientId $mac_original -ScopeId $scope_id
431 $module.Result.lease = Convert-ReturnValue -Object $new_lease
432 }
433
434 $module.Result.changed = $true
435 }
436 }
437 Catch {
438 # Failed to create reservation
439 $module.FailJson("Could not create DHCP reservation: $($_.Exception.Message)", $_)
440 }
441 }
442 }
443
444 $module.ExitJson()