1 #!powershell
2
3 # Copyright: (c) 2017, Red Hat, Inc.
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
8 Set-StrictMode -Version 2
9
10 $ErrorActionPreference = "Stop"
11
Write-DebugLog()12 Function Write-DebugLog {
13 Param(
14 [string]$msg
15 )
16
17 $DebugPreference = "Continue"
18 $date_str = Get-Date -Format u
19 $msg = "$date_str $msg"
20
21 Write-Debug $msg
22 $log_path = Get-Variable -Name log_path -Scope Global -ValueOnly -ErrorAction SilentlyContinue
23 if($log_path) {
24 Add-Content -LiteralPath $log_path -Value $msg
25 }
26 }
27
Get-DomainMembershipMatchnull28 Function Get-DomainMembershipMatch {
29 Param(
30 [string] $dns_domain_name
31 )
32
33 # FUTURE: add support for NetBIOS domain name?
34
35 # this requires the DC to be accessible; "DC unavailable" is indistinguishable from "not joined to the domain"...
36 Try {
37 Write-DebugLog "calling GetComputerDomain()"
38 $current_dns_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name
39
40 $domain_match = $current_dns_domain -eq $dns_domain_name
41
42 Write-DebugLog ("current domain {0} matches {1}: {2}" -f $current_dns_domain, $dns_domain_name, $domain_match)
43
44 return $domain_match
45 }
46 catch [System.Security.Authentication.AuthenticationException] {
47 Write-DebugLog "Failed to get computer domain. Attempting a different method."
48 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
49 $user_principal = [System.DirectoryServices.AccountManagement.UserPrincipal]::Current
50 If ($user_principal.ContextType -eq "Machine") {
51 $current_dns_domain = (Get-CimInstance -ClassName Win32_ComputerSystem -Property Domain).Domain
52
53 $domain_match = $current_dns_domain -eq $dns_domain_name
54
55 Write-DebugLog ("current domain {0} matches {1}: {2}" -f $current_dns_domain, $dns_domain_name, $domain_match)
56
57 return $domain_match
58 }
59 Else {
60 Fail-Json -obj $result -message "Failed to authenticate with domain controller and cannot retrieve the existing domain name: $($_.Exception.Message)"
61 }
62 }
63 Catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] {
64 Write-DebugLog "not currently joined to a reachable domain"
65 return $false
66 }
67 }
68
New-Credential()69 Function New-Credential {
70 Param(
71 [string] $cred_user,
72 [string] $cred_pass
73 )
74
75 $cred = New-Object System.Management.Automation.PSCredential($cred_user, $($cred_pass | ConvertTo-SecureString -AsPlainText -Force))
76
77 return $cred
78 }
79
Get-HostnameMatchnull80 Function Get-HostnameMatch {
81 Param(
82 [string] $hostname
83 )
84
85 # Add-Computer will validate the "shape" of the hostname- we just care if it matches...
86
87 $hostname_match = $env:COMPUTERNAME -eq $hostname
88 Write-DebugLog ("current hostname {0} matches {1}: {2}" -f $env:COMPUTERNAME, $hostname, $hostname_match)
89
90 return $hostname_match
91 }
92
Test-DomainJoined()93 Function Test-DomainJoined {
94 return (Get-CIMInstance Win32_ComputerSystem).PartOfDomain
95 }
96
Join-Domain()97 Function Join-Domain {
98 Param(
99 [string] $dns_domain_name,
100 [string] $new_hostname,
101 [string] $domain_admin_user,
102 [string] $domain_admin_password,
103 [string] $domain_ou_path
104 )
105
106 Write-DebugLog ("Creating credential for user {0}" -f $domain_admin_user)
107 $domain_cred = New-Credential $domain_admin_user $domain_admin_password
108
109 $add_args = @{
110 ComputerName="."
111 Credential=$domain_cred
112 DomainName=$dns_domain_name
113 Force=$null
114 }
115
116 Write-DebugLog "adding hostname set arg to Add-Computer args"
117 If($new_hostname) {
118 $add_args["NewName"] = $new_hostname
119 }
120
121
122 if($domain_ou_path){
123 Write-DebugLog "adding OU destination arg to Add-Computer args"
124 $add_args["OUPath"] = $domain_ou_path
125 }
126 $argstr = $add_args | Out-String
127 Write-DebugLog "calling Add-Computer with args: $argstr"
128 try {
129 $add_result = Add-Computer @add_args
130 } catch {
131 Fail-Json -obj $result -message "failed to join domain: $($_.Exception.Message)"
132 }
133
134 Write-DebugLog ("Add-Computer result was \n{0}" -f $add_result | Out-String)
135 }
136
Get-Workgroup()137 Function Get-Workgroup {
138 return (Get-CIMInstance Win32_ComputerSystem).Workgroup
139 }
140
Set-Workgroup()141 Function Set-Workgroup {
142 Param(
143 [string] $workgroup_name
144 )
145
146 Write-DebugLog ("Calling JoinDomainOrWorkgroup with workgroup {0}" -f $workgroup_name)
147 try {
148 $swg_result = Get-CimInstance Win32_ComputerSystem | Invoke-CimMethod -MethodName JoinDomainOrWorkgroup -Arguments @{Name="$workgroup_name"}
149 } catch {
150 Fail-Json -obj $result -message "failed to call Win32_ComputerSystem.JoinDomainOrWorkgroup($workgroup_name): $($_.Exception.Message)"
151 }
152
153 if ($swg_result.ReturnValue -ne 0) {
154 Fail-Json -obj $result -message "failed to set workgroup through WMI, return value: $($swg_result.ReturnValue)"
155 }
156 }
157
Join-Workgroupnull158 Function Join-Workgroup {
159 Param(
160 [string] $workgroup_name,
161 [string] $domain_admin_user,
162 [string] $domain_admin_password
163 )
164
165 If(Test-DomainJoined) { # if we're on a domain, unjoin it (which forces us to join a workgroup)
166 $domain_cred = New-Credential $domain_admin_user $domain_admin_password
167
168 # 2012+ call the Workgroup arg WorkgroupName, but seem to accept
169 try {
170 Remove-Computer -Workgroup $workgroup_name -Credential $domain_cred -Force
171 } catch {
172 Fail-Json -obj $result -message "failed to remove computer from domain: $($_.Exception.Message)"
173 }
174 }
175
176 # we're already on a workgroup- change it.
177 Else {
178 Set-Workgroup $workgroup_name
179 }
180 }
181
182
183 $result = @{
184 changed = $false
185 reboot_required = $false
186 }
187
188 $params = Parse-Args -arguments $args -supports_check_mode $true
189
190 $state = Get-AnsibleParam $params "state" -validateset @("domain","workgroup") -failifempty $result
191
192 $dns_domain_name = Get-AnsibleParam $params "dns_domain_name"
193 $hostname = Get-AnsibleParam $params "hostname"
194 $workgroup_name = Get-AnsibleParam $params "workgroup_name"
195 $domain_admin_user = Get-AnsibleParam $params "domain_admin_user" -failifempty $result
196 $domain_admin_password = Get-AnsibleParam $params "domain_admin_password" -failifempty $result
197 $domain_ou_path = Get-AnsibleParam $params "domain_ou_path"
198
199 $log_path = Get-AnsibleParam $params "log_path"
200 $_ansible_check_mode = Get-AnsibleParam $params "_ansible_check_mode" -default $false
201
202 If ($state -eq "domain") {
203 If(-not $dns_domain_name) {
204 Fail-Json @{} "dns_domain_name is required when state is 'domain'"
205 }
206 }
207 Else { # workgroup
208 If(-not $workgroup_name) {
209 Fail-Json @{} "workgroup_name is required when state is 'workgroup'"
210 }
211 }
212
213 Set-Variable -Name log_path -Scope Global -Value $log_path
214
215 Try {
216
217 $hostname_match = If($hostname) { Get-HostnameMatch $hostname } Else { $true }
218
219 $result.changed = $result.changed -or (-not $hostname_match)
220
221 Switch($state) {
222 domain {
223 $domain_match = Get-DomainMembershipMatch $dns_domain_name
224
225 $result.changed = $result.changed -or (-not $domain_match)
226
227 If($result.changed -and -not $_ansible_check_mode) {
228 If(-not $domain_match) {
229 If(Test-DomainJoined) {
230 Write-DebugLog "domain doesn't match, and we're already joined to another domain"
231 throw "switching domains is not implemented"
232 }
233
234 $join_args = @{
235 dns_domain_name = $dns_domain_name
236 domain_admin_user = $domain_admin_user
237 domain_admin_password = $domain_admin_password
238 }
239
240 Write-DebugLog "not a domain member, joining..."
241
242 If(-not $hostname_match) {
243 Write-DebugLog "adding hostname change to domain-join args"
244 $join_args.new_hostname = $hostname
245 }
246 If($null -ne $domain_ou_path){ # If OU Path is not empty
247 Write-DebugLog "adding domain_ou_path to domain-join args"
248 $join_args.domain_ou_path = $domain_ou_path
249 }
250
251 Join-Domain @join_args
252
253 # this change requires a reboot
254 $result.reboot_required = $true
255 }
256 ElseIf(-not $hostname_match) { # domain matches but hostname doesn't, just do a rename
257 Write-DebugLog ("domain matches, setting hostname to {0}" -f $hostname)
258
259 $rename_args = @{NewName=$hostname}
260
261 If (Test-DomainJoined) {
262 $domain_cred = New-Credential $domain_admin_user $domain_admin_password
263 $rename_args.DomainCredential = $domain_cred
264 }
265
266 Rename-Computer @rename_args
267
268 # this change requires a reboot
269 $result.reboot_required = $true
270 } Else {
271 # no change is needed
272 }
273
274 }
275 Else {
276 Write-DebugLog "check mode, exiting early..."
277 }
278
279 }
280
281 workgroup {
282 $workgroup_match = $(Get-Workgroup) -eq $workgroup_name
283
284 $result.changed = $result.changed -or (-not $workgroup_match)
285
286 If(-not $_ansible_check_mode) {
287 If(-not $workgroup_match) {
288 Write-DebugLog ("setting workgroup to {0}" -f $workgroup_name)
289 Join-Workgroup -workgroup_name $workgroup_name -domain_admin_user $domain_admin_user -domain_admin_password $domain_admin_password
290
291 # this change requires a reboot
292 $result.reboot_required = $true
293 }
294 If(-not $hostname_match) {
295 Write-DebugLog ("setting hostname to {0}" -f $hostname)
296 Rename-Computer -NewName $hostname
297
298 # this change requires a reboot
299 $result.reboot_required = $true
300 }
301 }
302 }
303 default { throw "invalid state $state" }
304 }
305
306 Exit-Json $result
307 }
308 Catch {
309 $excep = $_
310
311 Write-DebugLog "Exception: $($excep | out-string)"
312
313 Throw
314 }
315