- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
One of the most common components of any test environment is Active Directory (AD). AD is a critical part of most environments and is one we need to replicate as closely as possible to production in a test environment. Before technologies like PowerShell Desired State Configuration (DSC) came along, we had to copy the AD database over manually, bring up entirely new domain controllers from scratch, and a lot of other hacks. Nowadays, we can invoke a DSC configuration script that can bring up an entirely new AD domain in no time!
Prerequisites
To automate creating a test domain with DSC, we'll need to ensure we've got a few prerequisites in place. First, we'll need an existing server (physical or virtual) in a workgroup. Next, we'll need to make sure this server has at least PowerShell v4 installed and preferably v5.
In the demonstration, we'll be creating the DSC configuration on another machine and sending it to the server that will be our domain controller. Although it's not necessary if performing the work locally, this computer will need to be able to communicate with the soon-to-be domain controller via Server Message Block (SMB).
Finally, you'll need to have the xActiveDirectory DSC module installed on the server. You can download this by running the following:
Install-Module -Name xActiveDirectory
Project scope
Creating an AD domain can mean a lot to many different people since it's such a large topic. So let's scope our DSC configuration script down a bit. For this article, we'll be:
- Promoting a member server to a domain controller
- Creating multiple groups
- Creating multiple users
- Creating multiple organizational units
This isn't, by far, everything that's possible. But with the framework down, you can add additional objects to your DSC configuration script more easily later.
Defining Configuration Data
A best practice to use when creating any DSC configuration script is to separate the actual configuration itself from the configuration data. Configuration data can consist of any "static" values the code needs to reference when running. In our case, it includes the domain name, group names, organizational unit paths, and so on. The first task to tackle is defining all of these values in a PSD1 file that contains a hash table with all the data we need.
To expedite the process of creating this file, I've created an example you can download from the TestDomainCreator GitHub repository.
@{ AllNodes = @( @{ NodeName = '*' PsDscAllowDomainUser = $true PsDscAllowPlainTextPassword = $true }, @{ NodeName = 'LABDC' Purpose = 'Domain Controller' WindowsFeatures = 'AD-Domain-Services' } ) NonNodeData = @{ DomainName = 'mytestlab.local' AdGroups = 'Accounting','Information Systems','Executive Office','Janitorial Services' OrganizationalUnits = 'Accounting','Information Systems','Executive Office','Janitorial Services' AdUsers = @( @{ FirstName = 'Katie' LastName = 'Green' Department = 'Accounting' Title = 'Manager of Accounting' } @{ FirstName = 'Joe' LastName = 'Blow' Department = 'Information Systems' Title = 'System Administrator' } @{ FirstName = 'Joe' LastName = 'Schmoe' Department = 'Information Systems' Title = 'Software Developer' } @{ FirstName = 'Barack' LastName = 'Obama' Department = 'Executive Office' Title = 'CEO' } @{ FirstName = 'Donald' LastName = 'Trump' Department = 'Janitorial Services' Title = 'Custodian' } ) } }
This file holds all the configuration item values we need to create a fully functional AD domain.
Creating the DSC configuration script
Next, we need to create the DSC configuration. We can break down this configuration into the four topics I described above. Since we've already created a separate configuration data file, we'll need to reference this inside the DSC configuration script. We'll use the $ConfigurationData variable automatically available in all DSC configuration scripts (if using the ConfigurationData parameter when invoking Start-DscConfiguration).
You can download an example script from GitHub. You'll see from the example that the DSC configuration script is calling each of the AD objects we're creating (groups, organizational units, and users). It then references the appropriate DSC resource within the xActiveDirectory DSC module we downloaded earlier.
The only section of the DSC configuration script that does not fit this mold is when we're installing the appropriate Windows features and promoting the server to a domain controller.
Domain DSC configurationconfiguration NewTestEnvironment { Import-DscResource -ModuleName xActiveDirectory ## Authenticate to Azure Login-AzureRmAccount ## This will be invoked by Azure Automation so grab the Azure Automation DSC credential asset and use it. $credParams = @{ ResourceGroupName = 'Group' AutomationAccountName = 'adamautomation' } $defaultAdUserCred = Get-AutomationPSCredential -Name 'Default AD User Password' $domainSafeModeCred = Get-AutomationPSCredential -Name 'Domain safe mode' Node $AllNodes.where({ $_.Purpose -eq 'Domain Controller' }).NodeName { @($ConfigurationData.NonNodeData.ADGroups).foreach( { xADGroup $_ { Ensure = 'Present' GroupName = $_ DependsOn = '[xADDomain]ADDomain' } }) @($ConfigurationData.NonNodeData.OrganizationalUnits).foreach( { xADOrganizationalUnit $_ { Ensure = 'Present' Name = ($_ -replace '-') Path = ('DC={0},DC={1}' -f ($ConfigurationData.NonNodeData.DomainName -split '\.')[0], ($ConfigurationData.NonNodeData.DomainName -split '\.')[1]) DependsOn = '[xADDomain]ADDomain' } }) @($ConfigurationData.NonNodeData.ADUsers).foreach( { xADUser "$($_.FirstName) $($_.LastName)" { Ensure = 'Present' DomainName = $ConfigurationData.NonNodeData.DomainName GivenName = $_.FirstName SurName = $_.LastName UserName = ('{0}{1}' -f $_.FirstName.SubString(0, 1), $_.LastName) Department = $_.Department Path = ("OU={0},DC={1},DC={2}" -f $_.Department, ($ConfigurationData.NonNodeData.DomainName -split '\.')[0], ($ConfigurationData.NonNodeData.DomainName -split '\.')[1]) JobTitle = $_.Title Password = $defaultAdUserCred DependsOn = '[xADDomain]ADDomain' } }) ($Node.WindowsFeatures).foreach( { WindowsFeature $_ { Ensure = 'Present' Name = $_ } }) xADDomain ADDomain { DomainName = $ConfigurationData.NonNodeData.DomainName DomainAdministratorCredential = $domainSafeModeCred SafemodeAdministratorPassword = $domainSafeModeCred DependsOn = '[WindowsFeature]AD-Domain-Services' } } }
Once you've downloaded the DSC configuration script and the configuration data, and you've tweaked them to your liking, you can then create the MOF file. After this, we can then invoke the file on the server.
. NewTestEnvironment.ps1 NewTestEnvironment -ConfigurationData C:\ConfigurationData.psd1
WARNING: The configuration 'NewTestEnvironment' is loading one or more built-in resources without explicitly importing associated modules. Add Import-DscResource –ModuleName 'PSDesiredStateConfiguration' to your configuration to avoid this message.
Directory: C:\NewTestEnvironment Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 5/21/2017 4:53 PM 16258 LABDC.mof
Above I'm creating the MOF file for the server I have called LABDC.
Once I've created the MOF file, I can then invoke the DSC configuration on my remote server by running Start-DscConfiguration.
Start-DscConfiguration -Wait -Force -Path .\NewTestEnvironment -Credential (Get-Credential) -Verbose
Notice that I'm using a credential. This is necessary because the server you'll be running the DSC configuration script on will be in a workgroup. Thus, you must provide an administrative username and password to make that initial connection.
Subscribe to 4sysops newsletter!
After finishing this process, you will then have a brand new AD domain running on your server!
Hi
thanks for your post
is it possible to customise this configuration in order to create :
– a real OU structure ( OU / sub OU / sub Ou …)
– define in wich OU groups and users should be created?
thanks
Sorry, just saw the « path » function, could you just please explain how the path function works in order to define the path?
(-f ?)
Thanks
Didn't find your test users amusing.
I did find your test users amusing
This is definitely not the place to be advertising your politics.
David F.