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