'Create Azure IaaS Domain Controller using Terraform

I'm attempting to add a domain controller to an existing domain using terraform (in Azure). I declared some local values in the main terraform file for my servers, like so:

locals {
  username_command = "$username = ${var.domainAdminUsername}"
  password_command = "$password = ConvertTo-SecureString ${var.domainAdminPassword} -AsPlainText -Force"
  credentials_command  = "$credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username,$password)"
  install_ad_command   = "Add-WindowsFeature -name ad-domain-services -IncludeManagementTools"
  configure_ad_command = "Install-ADDSDomainController -DomainName ${var.domainName} -InstallDns -Credential $credentials -SafeModeAdministratorPassword $password -Force:$true"
  shutdown_command     = "shutdown -r -t 10"
  exit_code_hack       = "exit 0"
  powershell_command   = "${local.username_command}; ${local.password_command}; ${local.credentials_command}; ${local.install_ad_command}; ${local.configure_ad_command}; ${local.shutdown_command}; ${local.exit_code_hack}"


}

I'm then applying a custom script extension resource on the VM, that run the powershell cmdlets built by the local values:

resource "azurerm_virtual_machine_extension" "promote-to-domain-controller" {
  count = 2
  depends_on = [
    azurerm_windows_virtual_machine.vm
  ]

  name                 = "promote-to-domain-controller"
  virtual_machine_id   = azurerm_windows_virtual_machine.vm[count.index].id
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.9"

  settings = <<SETTINGS
    {
        "commandToExecute": "powershell.exe -Command \"${local.powershell_command}\""
    }
SETTINGS
}

Everything seems to work, but the VM extension. [ { "code": "ComponentStatus/StdOut/succeeded", "level": "Info", "displayStatus": "Provisioning succeeded", "message": "" }, { "code": "ComponentStatus/StdErr/succeeded", "level": "Info", "displayStatus": "Provisioning succeeded", "message": "azadmin : The term 'azadmin' is not recognized as the name of a cmdlet, function, script file, or operable program. \r\nCheck the spelling of the name, or if a path was included, verify that the path is correct and try again.\r\nAt line:1 char:13\r\n+ $username = azadmin; $password = ConvertTo-SecureString <redacted>...\r\n+ ~~~~~~~\r\n + CategoryInfo : ObjectNotFound: (azadmin:String) [], CommandNotFoundException\r\n + FullyQualifiedErrorId : CommandNotFoundException\r\n \r\nNew-Object : Exception calling \".ctor\" with \"2\" argument(s): \"Cannot process argument because the value of argument \r\n\"userName\" is not valid. Change the value of the \"userName\" argument and run the operation again.\"\r\nAt line:1 char:118\r\n+ ... edentials = New-Object -TypeName System.Management.Automation.PSCrede ...\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException\r\n + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand\r\n \r\n" } ]

var.domainAdminUsername is set to 'azadmin'. In the first screenshot, on line 11, you can see that I'm passing this value ($username) to the PSCredential constructor. This constructor takes two overloads: a string and a securestring. It's like the value that's getting passed to the constructor isn't a string, though it should be.



Solution 1:[1]

You can use the below to promote your VM as a domain controller for a exisitng forest .

Main.tf file:

   provider "azurerm" {
  features {}
}
locals {
  password_command = "$password = ConvertTo-SecureString ${var.admin_password} -AsPlainText -Force"

  credentials_command  = "$credentials = Get-Credential ${var.domainAdminUsername}"
  install_ad_command   = "Add-WindowsFeature -name ad-domain-services -IncludeManagementTools"
  configure_ad_command = "Install-ADDSDomainController -DomainName ${var.active_directory_domain} -InstallDns -Credential $credentials -SafeModeAdministratorPassword $password -Force:$true"
  shutdown_command     = "shutdown -r -t 10"
  exit_code_hack       = "exit 0"
  powershell_command   = " ${local.password_command};${local.credentials_command}; ${local.install_ad_command}; ${local.configure_ad_command}; ${local.shutdown_command}; ${local.exit_code_hack}"

}

data "azurerm_virtual_machine" "example" {
 name = "${var.vmname}"
 resource_group_name = "${var.resource_group_name}"
}

resource "azurerm_virtual_machine_extension" "promote-to-domain-controller" {
  name                 = "promote-to-domain-controller"
   virtual_machine_id = data.azurerm_virtual_machine.example.id
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.9"

  settings = <<SETTINGS
    {
        "commandToExecute": "powershell.exe -Command \"${local.powershell_command}\""
    }
SETTINGS
}

variable.tf file:

variable resource_group_name {
  description = "The name of the Resource Group where the VM is"
  default = "resourcegroup"
}
variable location {
  description = "The Azure Region in which the Resource Group exists"
  default = "resourcegrouplocation"
}

# Active Directory & Domain Controller
variable vmname {
  description = "The Virtual Machine name that you wish to join to the domain"
  default = "vmname"
}

variable "active_directory_domain" {
  description = "The name of the Active Directory domain, for example `consoto.local`"
  default = "domainname"
}
variable "domainAdminUsername" {
    description = "The local administrator account on the Domain"
    default = "Domain\admin or [email protected]"
}

variable "admin_password" {
  description = "The password associated with the local administrator account on the virtual machine"
  default = "password"
}

Output: (Terraform Plan)

enter image description here

Adding VM to existing domain:

Main.tf

resource "azurerm_virtual_machine_extension" "join-domain" {
  name                 = "join-domain"
  location             = "${var.location}"
  resource_group_name  = "${var.resource_group_name}"
  virtual_machine_name = "${var.vmname}"
  publisher            = "Microsoft.Compute"
  type                 = "JsonADDomainExtension"
  type_handler_version = "1.3"
  

  
  settings = <<SETTINGS
    {
        "Name": "${var.active_directory_domain}",
        "OUPath": "",
        "User": "${var.active_directory_domain}\\${var.active_directory_username}",
        "Restart": "true",
        "Options": "3"
    }
SETTINGS

  protected_settings = <<SETTINGS
    {
        "Password": "${var.active_directory_password}"
    }
SETTINGS
}

Variable.tf

variable resource_group_name {
  description = "The name of the Resource Group where the VM is"
}
variable location {
  description = "The Azure Region in which the Resource Group exists"
}

# Active Directory & Domain Controller
variable vmname {
  description = "The Virtual Machine name that you wish to join to the domain"
}

variable "active_directory_domain" {
  description = "The name of the Active Directory domain, for example `consoto.local`"
}

variable "active_directory_username" {
  description = "The username of an account with permissions to bind machines to the Active Directory Domain"
}

variable "active_directory_password" {
  description = "The password of the account with permissions to bind machines to the Active Directory Domain"
}

Note: Please ensure to use username as domain\adminusername for the code to run as its expecting username to be domain's username.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1