Created
April 2, 2024 20:58
-
-
Save cobbr/69a7b92429fa38ea86faa88e07e2c9b2 to your computer and use it in GitHub Desktop.
win_domain_child
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!powershell | |
# Copyright: (c) 2022 Jordan Borean (@jborean93) [email protected] | |
# Copyright: (c) 2023, Ryan Cobb <[email protected]> | |
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
#AnsibleRequires -CsharpUtil Ansible.Basic | |
# win_domain_child module | |
$spec = @{ | |
options = @{ | |
dns_domain_name = @{ type = 'str' } | |
domain_admin_password = @{ type = 'str'; required = $true; no_log = $true } | |
domain_admin_username = @{ type = 'str'; required = $true } | |
domain_mode = @{ type = 'str'; choices = 'Win2008', 'Win2008R2', 'Win2012', 'Win2012R2', 'WinThreshold', 'Default' } | |
safe_mode_password = @{ type = 'str'; required = $true; no_log = $true } | |
site_name = @{ type = 'str' } | |
} | |
required_together = @( | |
, @("domain_admin_username", "domain_admin_password") | |
) | |
supports_check_mode = $true | |
} | |
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) | |
$dns_domain_name = $module.Params.dns_domain_name | |
$domain_admin_password = $module.Params.domain_admin_password | |
$domain_admin_username = $module.Params.domain_admin_username | |
$domain_mode = $module.Params.domain_mode | |
$safe_mode_password = $module.Params.safe_mode_password | |
$site_name = $module.Params.site_name | |
$module.Result.reboot_required = $false | |
$system_role = Get-CimInstance -ClassName Win32_ComputerSystem -Property Domain, DomainRole | |
if ($system_role.DomainRole -in @(4, 5)) | |
{ | |
if ($system_role.Domain -ne $dns_domain_name) | |
{ | |
$module.FailJson("Host is already a domain controller in another domain $($system_role.Domain)") | |
} | |
$module.ExitJson() | |
} | |
$new_domain_name, $parent_domain_name = $dns_domain_name.Split(".", 2) | |
$domain_admin_credential = New-Object -TypeName PSCredential -ArgumentList @( | |
$domain_admin_username, | |
(ConvertTo-SecureString -AsPlainText -Force -String $domain_admin_password) | |
) | |
$install_params = @{ | |
NewDomainName = $new_domain_name | |
ParentDomainName = $parent_domain_name | |
DomainType = 'ChildDomain' | |
NoRebootOnCompletion = $true | |
SafeModeAdministratorPassword = (ConvertTo-SecureString -AsPlainText -Force -String $safe_mode_password) | |
Force = $true | |
Credential = $domain_admin_credential | |
WhatIf = $module.CheckMode | |
ErrorAction = 'Stop' | |
} | |
if ($domain_mode) | |
{ | |
$install_params.DomainMode = $domain_mode | |
} | |
if ($site_name) | |
{ | |
$install_params.SiteName = $site_name | |
} | |
$install_domain = $null | |
try { | |
$install_domain = Install-ADDSDomain @install_params | |
} | |
catch { | |
# DCPromo exit codes details can be found at | |
# https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/troubleshooting-domain-controller-deployment | |
# ExitCode 1 == 'Exit, success' | |
# ExitCode 2 == 'Exit, success, need to reboot' | |
# ExitCode 3 == 'Exit, success, with a noncritical failure' | |
# ExitCode 4 == 'Exit, success, with a noncritical failure, need to reboot' | |
if ($_.Exception.ErrorCode -in @(1, 2, 3, 4)) { | |
$module.Result.changed = $true | |
$module.Result.reboot_required = $true | |
} | |
# ExitCode 15 == 'Role change is in progress or needs reboot' | |
# ExitCode 19 == 'Name change pending, needs reboot' | |
# ExitCode 53 == 'The promotion/demotion failed, machine must be rebooted to clean up' | |
# ExitCode 56 == 'The promotion/demotion was canceled by the user, machine must be rebooted to clean up' | |
elseif ($_.Exception.ErrorCode -in @(15, 19, 53, 56)) { | |
$module.Result.changed = $false | |
$module.Result.reboot_required = $true | |
} | |
# ExitCode 45 == 'A forest with the specified name already exists' | |
# ExitCode 47 == 'A tree with the specified name already exists' | |
elseif ($_.Exception.ErrorCode -in @(45, 47)) | |
{ | |
$module.Result.changed = $false | |
} | |
else | |
{ | |
$module.FailJson("Failed to install ADDSDomain, DCPromo exited with $($_.Exception.ExitCode)", $_) | |
} | |
} | |
if ($install_domain) | |
{ | |
$module.Result.changed = $true | |
$module.Result.reboot_required = $true | |
if (-not $module.CheckMode) | |
{ | |
# The Netlogon service is set to auto start but is not started. This is | |
# required for Ansible to connect back to the host and reboot in a | |
# later task. Even if this fails Ansible can still connect but only | |
# with ansible_winrm_transport=basic so we just display a warning if | |
# this fails. | |
try { | |
$started = Start-Service -Name Netlogon | |
} | |
catch { | |
$msg = -join @( | |
"Failed to start the Netlogon service after promoting the host, " | |
"Ansible may be unable to connect until the host is manually rebooted: $($_.Exception.Message)" | |
) | |
$module.Warn($msg) | |
} | |
} | |
} | |
$module.ExitJson() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# Copyright: (c) 2022 Jordan Borean (@jborean93) [email protected] | |
# Copyright: (c) 2023, Ryan Cobb <[email protected]> | |
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
DOCUMENTATION = r''' | |
--- | |
module: win_domain_child | |
short_description: Ensure the existence of a Windows child domain within a forest. | |
description: | |
- Ensure the existence of a Windows child domain within a forest. | |
- This module may require subsequent use of the M(ansible.windows.win_reboot) action if changes are made. | |
options: | |
dns_domain_name: | |
description: | |
- The DNS name of the domain which should exist and be reachable or reside on the target Windows host. | |
type: str | |
required: yes | |
domain_admin_username: | |
description: | |
- The username of a domain admin used to authenticate with the remote domain. | |
type: str | |
required: yes | |
domain_admin_password: | |
description: | |
- The password of the domain admin used to authenticate with the remote domain. | |
type: str | |
required: yes | |
domain_mode: | |
description: | |
- Specifies the domain functional level of the first domain in the creation of a new forest. | |
type: str | |
choices: [ Win2008, Win2008R2, Win2012, Win2012R2, WinThreshold, Default ] | |
required: no | |
safe_mode_password: | |
description: | |
- Specifies the password for the administrator account when the computer is started in Safe Mode. | |
type: str | |
required: yes | |
site_name: | |
description: | |
- Specifies the name of an existing site where you can place the new domain controller. | |
type: str | |
required: no | |
author: | |
- Ryan Cobb (@cobbr) | |
''' | |
EXAMPLES = r''' | |
- name: Create a domain child within a forest | |
win_domain_child: | |
name: child.domain.local | |
domain_admin_username: Administrator | |
domain_admin_password: Password123! | |
safe_mode_password: Password123! | |
register: child_domain | |
''' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment