1 #!powershell
2 
3 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4 
5 #Requires -Module Ansible.ModuleUtils.Legacy
6 
7 Set-StrictMode -Version 2
8 $ErrorActionPreference = "Stop"
9 
10 # FUTURE: Consider action wrapper to manage reboots and credential changes
11 
Ensure-Prereqsnull12 Function Ensure-Prereqs {
13     $gwf = Get-WindowsFeature AD-Domain-Services
14     if ($gwf.InstallState -ne "Installed") {
15         $result.changed = $true
16 
17         # NOTE: AD-Domain-Services includes: RSAT-AD-AdminCenter, RSAT-AD-Powershell and RSAT-ADDS-Tools
18         $awf = Add-WindowsFeature AD-Domain-Services -WhatIf:$check_mode
19         $result.reboot_required = $awf.RestartNeeded
20         # FUTURE: Check if reboot necessary
21 
22         return $true
23     }
24     return $false
25 }
26 
27 $params = Parse-Args $args -supports_check_mode $true
28 $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
29 $dns_domain_name = Get-AnsibleParam -obj $params -name "dns_domain_name" -failifempty $true
30 $domain_netbios_name = Get-AnsibleParam -obj $params -name "domain_netbios_name"
31 $safe_mode_admin_password = Get-AnsibleParam -obj $params -name "safe_mode_password" -failifempty $true
32 $database_path = Get-AnsibleParam -obj $params -name "database_path" -type "path"
33 $sysvol_path = Get-AnsibleParam -obj $params -name "sysvol_path" -type "path"
34 $create_dns_delegation = Get-AnsibleParam -obj $params -name "create_dns_delegation" -type "bool"
35 $domain_mode = Get-AnsibleParam -obj $params -name "domain_mode" -type "str"
36 $forest_mode = Get-AnsibleParam -obj $params -name "forest_mode" -type "str"
37 
38 # FUTURE: Support down to Server 2012?
39 if ([System.Environment]::OSVersion.Version -lt [Version]"6.3.9600.0") {
40     Fail-Json -message "win_domain requires Windows Server 2012R2 or higher"
41 }
42 
43 # Check that domain_netbios_name is less than 15 characters
44 if ($domain_netbios_name -and $domain_netbios_name.length -gt 15) {
45     Fail-Json -message "The parameter 'domain_netbios_name' should not exceed 15 characters in length"
46 }
47 
48 $result = @{
49     changed=$false;
50     reboot_required=$false;
51 }
52 
53 # FUTURE: Any sane way to do the detection under check-mode *without* installing the feature?
54 $installed = Ensure-Prereqs
55 
56 # when in check mode and the prereq was "installed" we need to exit early as
57 # the AD cmdlets weren't really installed
58 if ($check_mode -and $installed) {
59     Exit-Json -obj $result
60 }
61 
62 # Check that we got a valid domain_mode
63 $valid_domain_modes = [Enum]::GetNames((Get-Command -Name Install-ADDSForest).Parameters.DomainMode.ParameterType)
64 if (($null -ne $domain_mode) -and -not ($domain_mode -in $valid_domain_modes)) {
65     Fail-Json -obj $result -message "The parameter 'domain_mode' does not accept '$domain_mode', please use one of: $valid_domain_modes"
66 }
67 
68 # Check that we got a valid forest_mode
69 $valid_forest_modes = [Enum]::GetNames((Get-Command -Name Install-ADDSForest).Parameters.ForestMode.ParameterType)
70 if (($null -ne $forest_mode) -and -not ($forest_mode -in $valid_forest_modes)) {
71     Fail-Json -obj $result -message "The parameter 'forest_mode' does not accept '$forest_mode', please use one of: $valid_forest_modes"
72 }
73 
74 $forest = $null
75 try {
76     # Cannot use Get-ADForest as that requires credential delegation, the below does not
77     $forest_context = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList Forest, $dns_domain_name
78     $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($forest_context)
79 } catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] {
80 } catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException] { }
81 
82 if (-not $forest) {
83     $result.changed = $true
84 
85     $sm_cred = ConvertTo-SecureString $safe_mode_admin_password -AsPlainText -Force
86 
87     $install_params = @{
88         DomainName=$dns_domain_name;
89         SafeModeAdministratorPassword=$sm_cred;
90         Confirm=$false;
91         SkipPreChecks=$true;
92         InstallDns=$true;
93         NoRebootOnCompletion=$true;
94         WhatIf=$check_mode;
95     }
96 
97     if ($database_path) {
98         $install_params.DatabasePath = $database_path
99     }
100 
101     if ($sysvol_path) {
102         $install_params.SysvolPath = $sysvol_path
103     }
104 
105     if ($domain_netbios_name) {
106         $install_params.DomainNetBiosName = $domain_netbios_name
107     }
108 
109     if ($null -ne $create_dns_delegation) {
110         $install_params.CreateDnsDelegation = $create_dns_delegation
111     }
112 
113     if ($domain_mode) {
114         $install_params.DomainMode = $domain_mode
115     }
116 
117     if ($forest_mode) {
118         $install_params.ForestMode = $forest_mode
119     }
120 
121     $iaf = $null
122     try {
123         $iaf = Install-ADDSForest @install_params
124     } catch [Microsoft.DirectoryServices.Deployment.DCPromoExecutionException] {
125         # ExitCode 15 == 'Role change is in progress or this computer needs to be restarted.'
126         # DCPromo exit codes details can be found at https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/troubleshooting-domain-controller-deployment
127         if ($_.Exception.ExitCode -in @(15, 19)) {
128             $result.reboot_required = $true
129         } else {
130             Fail-Json -obj $result -message "Failed to install ADDSForest, DCPromo exited with $($_.Exception.ExitCode): $($_.Exception.Message)"
131         }
132     }
133 
134     if ($check_mode) {
135         # the return value after -WhatIf does not have RebootRequired populated
136         # manually set to True as the domain would have been installed
137         $result.reboot_required = $true
138     } elseif ($null -ne $iaf) {
139         $result.reboot_required = $iaf.RebootRequired
140 
141         # The Netlogon service is set to auto start but is not started. This is
142         # required for Ansible to connect back to the host and reboot in a
143         # later task. Even if this fails Ansible can still connect but only
144         # with ansible_winrm_transport=basic so we just display a warning if
145         # this fails.
146         try {
147             Start-Service -Name Netlogon
148         } catch {
149             Add-Warning -obj $result -message "Failed to start the Netlogon service after promoting the host, Ansible may be unable to connect until the host is manually rebooting: $($_.Exception.Message)"
150         }
151     }
152 }
153 
154 Exit-Json $result
155