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-Prereqsnull12Function 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