En la anterior publicación os estuve hablando sobre la automatización completa de la creación de un nuevo bosque de Active Directory en alta disponibilidad con Azure Resource Manager.

En esta publicación quiero comentar una peculiaridad con la que mi compañero y servidor nos encontramos en el contexto del proyecto que estamos llevando a cabo: tras crear el bosque de Active Directory, nuestro proyecto ARM sigue creando máquinas en Azure que deben unirse al dominio que hemos creado usando JsonAdDomainJoinExtension; pero queremos se unan en unas OUs concretas que queremos crear automáticamente.

Efectivamente, vamos a hablar de automatizar también la generación de Organizational Units (unidades organizativas).

xADOrganizationalUnit, el recurso DSC de gestión de OU

Este recurso DSC forma parte del módulo xActiveDirectory y nos permite crear OUs de forma declarativa con una sintaxis muy simple:

xADOrganizationalUnit myOuCreateDSC
{
   Name = "DynamicsAX"
   Path = "DC=calnus,DC=com"
   ProtectedFromAccidentalDeletion = $true
   Description = "Servidores de Microsoft Dynamics AX"
   Ensure = "Present"
   DependsOn = ""
}

Este DSC no entraña más misterio pero... Ahora nos enfrentamos a los siguientes retos:

  • ¿Y si queremos crear un gran número de OUs? Por ejemplo, 25 o 50.
  • ¿Y si a priori no sabemos cuantas OUs vamos a crear y el DSC debe adaptarse dinámicamente a lo que pidamos?

Descomponiendo el problema

Si lo pensamos detenidamente, tenemos dos subproblemas a resolver para llegar a la solución final:

  • Hacer llegar las OUs y sus rutas al DSC desde ARM.
  • Hacer que el DSC pueda procesar lo que le mandemos sin conocerlo a priori.

¡Vamos a ello!

Paso de parámetros de ARM a DSC

Lo primero que tenemos que hacer es recordar que esta operación se llevaba a cabo desde la Virtual Machine Extension de DSC desde ARM y que en el caso que nos ocupaba la publicación anterior era así:

{
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "name": "[concat(parameters('vmName-DC1'),'/CreateADForest')]",
      "apiVersion": "[parameters('apiVersion')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName-DC1'))]",
        "[concat('Microsoft.Compute/virtualMachines/', parameters('vmName-DC1'),'/extensions','/BGInfo')]"
      ],
      "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.19",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "ModulesUrl": "[concat(parameters('_artifactsLocation'), '/', 'dsc', '/', parameters('_addsPdcDscFileName'), parameters('_artifactsLocationSasToken'))]",
          "ConfigurationFunction": "[parameters('adPdcConfigurationFunction')]",
          "Properties": {
            "DomainName": "[parameters('addsRootDomainName')]",
            "AdminCreds": {
              "UserName": "[parameters('adminUsername')]",
              "Password": "PrivateSettingsRef:AdminPassword"
            }
          }
        },
        "protectedSettings": {
          "Items": {
            "AdminPassword": "[parameters('adminPassword')]"
          }
        }
    }
}

Como se puede ver de la línea 18 a la 23, la propiedad Properties es la que realiza el paso de parámetros al DSC. ¿Y si definimos dentro alguna estructura de datos para nuestras OUs? En este caso un hashtable me ha parecido ideal, ¿por qué?

  • Al DSC le tenemos que pasar dos valores: nombre de la OU y la ruta donde se va a crear.
  • El hashtable es un tipo nativo tanto en JSON como el PowerShell.

Así pues se me ha ocurrido generar el siguiente:

"createOU": {
    "DynamicsAX": "DC=calnus,DC=com",
    "SQLServer": "DC=calnus,DC=com",
    "Linux": "DC=calnus,DC=com"
}

Las claves son los nombres de las OUs, mientras que los valores son las rutas. Sin embargo, hardcodear estos valores en el JSON no me parece la mejor práctica. ¿Recordáis que el FQDN del dominio lo cogíamos como parámetro de ARM? ¿Y si lo utilizamos para generar a partir de el la ruta base en formato LDAP? Las funciones de manejo de cadenas de ARM nos pueden ayudar a ello, de forma que quedaría así:

"createOU": {
  "DynamicsAX": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]",
  "SQLServer": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]",
  "Linux": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]"
}

Empezamos escribiendo DC= y después cogemos el FQDN (calnus.com) y cambiamos el punto por otra vez DC= y ya lo tenemos.

El resultado final de Properties sería este:

"Properties": {
  "DomainName": "[parameters('addsRootDomainName')]",
  "AdminCreds": {
     "UserName": "[parameters('adminUsername')]",
     "Password": "PrivateSettingsRef:AdminPassword"
   },
   "createOU": {
      "DynamicsAX": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]",
      "SQLServer": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]",
      "Linux": "[concat('DC=',replace(parameters('addsRootDomainName'),'.',',DC='))]"
    }
}

¡Ya tenemos paso de parámetros al DSC!

Aplicando el hashtable en DSC

Vamos a trabajar en el archivo declarativo de nuestro DSC, siguiendo el ejemplo de la publicación anterior, el CreateADPDC.ps1 y en la declaración de parámetros insertamos la nueva variable que debe llamarse igual que el hashtable de ARM (lo llamamos createOU).

configuration CreateADPDC 
{ 
   param 
   ( 
        [Parameter(Mandatory)]
        [String]$DomainName,

        [Parameter(Mandatory)]
        [System.Management.Automation.PSCredential]$Admincreds,
        
        [Parameter(Mandatory)]
        [hashtable]$createOU, 

        [Int]$RetryCount=20,
        [Int]$RetryIntervalSec=30
    ) 

En la línea 12 tenemos nuestra declaración, que es realmente trivial. Sin embargo llegamos a la parte crucial de la operación. ¿Podemos hacer un bucle en PowerShell DSC y aplicar una configuración múltiples veces? La respuesta es sí, y es que PowerShell DSC lleva PowerShell delante por algo.

La declaración sería la siguiente:

foreach($ou in $createOU.GetEnumerator())
{
   $createOuName="CreateOU_$($ou.Name)"
   xADOrganizationalUnit $createOuName
   {
      Name = $ou.Name
      Path = $ou.Value
      ProtectedFromAccidentalDeletion = $true
      Description = ""
      Ensure = "Present"
      DependsOn = "[xWaitForADDomain]DscForestWait"
   }
}

Justo encima de la declaración del recurso DSC he definido un bucle foreach que recorrerá el hashtable basado en sus claves. A partir de aquí el resto es historia:

  • $createOuName="CreateOU_$($ou.Name)" nos permite dar un nombre distinto a cada declaración generada por el bucle, ya que en caso contrario nos daría error.
  • Name se corresponde con la clave de nuestra tabla hash, mientras que Path es el valor.
  • En este caso en DependsOn indico que esta operación no se llevará a cabo hasta que la máquina haya promocionado a controlador de dominio, además de reiniciado.

Espero que os haya resultado ilustrativo y os ayuda a automatizar aún más distintos procesos con ARM y DSC.

Happy DSCing!