How to Build the Perfect Hyper-V Test Environment

One task that seems to come up on a rather frequent basis is the request from project X to build them a test environment. They have a new application to deploy and they need to perform some testing. This is a prime candidate for automation. If your organization has adopted DevOps processes, then the ability to build and rebuild environments at the drop of a hat is a task that you’re expected to perform – except this time you’re doing it in production.

In this series of posts, I’m going to show you how you can use PowerShell scripts and PowerShell Direct to create the optimal hyper-v test environment. To begin, I will assume that you have a method to create VMs. Whether VM creation is performed from scratch, via templates or an automated build process isn’t relevant for these posts – whatever works in your organization is fine. The series will comprise 4 parts:

Part 1 – Creating an Active Directory [this post] – how to create a new AD forest and add a second Domain Controller to that forest (you do have 2 DCs in your environments don’t you?). I will assume you’re familiar with performing standard AD administrative tasks (if not, I recommend you read Learn Active Directory Management in a Month of Lunches)

Part 2 – populating your Active Directory with some OUs, accounts & groups. We’ll look at object creation singly and in bulk

Part 3 – how to add a member server then install and configure IIS. The second half of the post deals with adding a second member server and configuring it as a file server

Part 4 – installing a simple application then configure IIS to pull & display files from the file server. We’ll make people log in to the web server so that the accounts we created are used.

Building the Perfect Hyper-V Test Environment – Where to Start

I stated earlier that I’m using PowerShell Direct and PowerShell scripts to perform the configuration work. What about Desired State Configuration (DSC) you might ask. DSC is available from PowerShell v4 onwards – Windows Server 2012 R2 has it installed OOB and it’s available for some earlier versions of Windows Server as part of the Windows Management Framework (WMF) download. DSC needs a fair amount of explanation if you’ve not used it before so I’ll concentrate on scripting the configuration changes for now. I may get the opportunity to present this concept using DSC at a future date to give you the ability to compare and contrast the two approaches.

The starting point for this series is a brand new shiny VM that you’ve created, performed basic configuration (IP address etc) and patched. My previous posts Introduction to PowerShell Direct and How to Patch Hyper-V virtual machines through PowerShell Direct can help you get that job done.

Just to make the whole task more “interesting” I thought I’d only use Server Core machines so that all tasks have to be performed through code.

Create First Domain Controller

The first job to tackle is creating the first domain controller in our Active Directory (AD) and therefore creating the domain. I’m only going to create an AD forest with a single domain though these techniques can be easily adapted to create a multi-domain forest if you need one. Creating the first domain controller in a new domain requires you to:

Install the Active Directory Domain Services and DNS Windows roles

Promote the server to be a domain controller and create the domain

Install roles

Let’s start by installing the required roles. You’ll be running a number of commands against the VM so a remoting session is the most efficient way to connect:

$vm = 'TestDC01' $cred = Get-Credential -Credential "$vm\Administrator" $s = New-PSSession -VMName $vm -Credential $cred 1 2 3 $vm = 'TestDC01' $cred = Get-Credential -Credential "$vm\Administrator" $s = New-PSSession -VMName $vm -Credential $cred

What do you actually need to install?

$sb = {Get-WindowsFeature -Name AD*, DNS } Invoke-Command -Session $s -ScriptBlock $sb | select DisplayName, Name, InstallState 1 2 3 $sb = { Get-WindowsFeature -Name AD * , DNS } Invoke-Command -Session $s -ScriptBlock $sb | select DisplayName , Name , InstallState

DisplayName Name InstallState ----------- ---- ------------ Active Directory Certificate Services AD-Certificate Available Certification Authority ADCS-Cert-Authority Available Certificate Enrollment Policy Web Service ADCS-Enroll-Web-Pol Available Certificate Enrollment Web Service ADCS-Enroll-Web-Svc Available Certification Authority Web Enrollment ADCS-Web-Enrollment Available Network Device Enrollment Service ADCS-Device-Enrollment Available Online Responder ADCS-Online-Cert Available Active Directory Domain Services AD-Domain-Services Available Active Directory Federation Services ADFS-Federation Available Active Directory Lightweight Directory Services ADLDS Available Active Directory Rights Management Services ADRMS Available Active Directory Rights Management Server ADRMS-Server Available Identity Federation Support ADRMS-Identity Available DNS Server DNS Available 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DisplayName Name InstallState ----------- ---- ------------ Active Directory Certificate Services AD-Certificate Available Certification Authority ADCS-Cert -Authority Available Certificate Enrollment Policy Web Service ADCS-Enroll -Web -Pol Available Certificate Enrollment Web Service ADCS-Enroll -Web -Svc Available Certification Authority Web Enrollment ADCS-Web -Enrollment Available Network Device Enrollment Service ADCS-Device -Enrollment Available Online Responder ADCS-Online -Cert Available Active Directory Domain Services AD-Domain -Services Available Active Directory Federation Services ADFS-Federation Available Active Directory Lightweight Directory Services ADLDS Available Active Directory Rights Management Services ADRMS Available Active Directory Rights Management Server ADRMS-Server Available Identity Federation Support ADRMS-Identity Available DNS Server DNS Available

I’ve cheated a little with this code as I have a good idea of the names of the features to install. We need to install AD-Domain-Services and DNS.

Run this code:

$sb = {Install-WindowsFeature -Name AD-Domain-Services, DNS -IncludeAllSubFeature -IncludeManagementTools -Restart} Invoke-Command -Session $s -ScriptBlock $sb 1 2 $sb = { Install-WindowsFeature -Name AD-Domain -Services , DNS -IncludeAllSubFeature -IncludeManagementTools -Restart } Invoke-Command -Session $s -ScriptBlock $sb

You need to use the -IncludeAllSubFeatures to ensure that you get any additional sub-features the roles require. -IncludeManagementTools ensures you get the tools to manage the role installed on your server – in this case the PowerShell cmdlets to manage AD and DNS. The -Restart parameter will restart your VM after the installation has finished but only if required. As a side note, you may see code that uses Add-WindowsFeature instead of Install-WindowsFeature. Add-WindowsFeature was the cmdlet name when originally introduced with Windows Server 2008. It was later changed to Install-WindowsFeature but Add-WindowsFeature is still available as an alias so using that command will also work.

You should see results like this:

PSComputerName : TestDC01 RunspaceId : e5c81a53-765f-4923-8dbd-a0e3d066b56d Success : True RestartNeeded : No FeatureResult : {Active Directory Domain Services, DNS Server, Group Policy Management, Remote Server Administration Tools...} ExitCode : Success 1 2 3 4 5 6 7 PSComputerName : TestDC01 RunspaceId : e5c81a53 -765f -4923 -8dbd -a0e3d066b56d Success : True RestartNeeded : No FeatureResult : { Active Directory Domain Services , DNS Server , Group Policy Management , Remote Server Administration Tools . . . } ExitCode : Success

If you rerun the Get-WindowsFeature code from earlier you’ll see that the InstallState has changed to Installed.

Promote domain controller

Now that you’ve got the features installed how do you promote the server to be a domain controller and create the domain? In the past, you’d have to use DCPROMO but that option isn’t available anymore – now you must use the members of the ADDSDeployment module which is installed when you install the AD Domain Services role.

PS C:\Scripts> Get-Command -Module ADDSDeployment CommandType Name Version Source ----------- ---- ------- ------ Cmdlet Add-ADDSReadOnlyDomainControllerAccount 1.0.0.0 ADDSDeployment Cmdlet Install-ADDSDomain 1.0.0.0 ADDSDeployment Cmdlet Install-ADDSDomainController 1.0.0.0 ADDSDeployment Cmdlet Install-ADDSForest 1.0.0.0 ADDSDeployment Cmdlet Test-ADDSDomainControllerInstallation 1.0.0.0 ADDSDeployment Cmdlet Test-ADDSDomainControllerUninstallation 1.0.0.0 ADDSDeployment Cmdlet Test-ADDSDomainInstallation 1.0.0.0 ADDSDeployment Cmdlet Test-ADDSForestInstallation 1.0.0.0 ADDSDeployment Cmdlet Test-ADDSReadOnlyDomainControllerAccountCreation 1.0.0.0 ADDSDeployment Cmdlet Uninstall-ADDSDomainController 1.0.0.0 ADDSDeployment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 PS C : \ Scripts > Get-Command -Module ADDSDeployment CommandType Name Version Source ----------- ---- ------- ------ Cmdlet Add-ADDSReadOnlyDomainControllerAccount 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Install-ADDSDomain 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Install-ADDSDomainController 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Install-ADDSForest 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Test-ADDSDomainControllerInstallation 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Test-ADDSDomainControllerUninstallation 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Test-ADDSDomainInstallation 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Test-ADDSForestInstallation 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Test-ADDSReadOnlyDomainControllerAccountCreation 1 . 0 . 0 . 0 ADDSDeployment Cmdlet Uninstall-ADDSDomainController 1 . 0 . 0 . 0 ADDSDeployment

The three important cmdlets, at this stage, are:

Install-ADDSForest – create new AD forest

Install-ADDSDomain – create new domain in existing forest

Install-ADDSDomainController – add a domain controller to an existing domain

You’ll use Install-ADDSForest to create the new forest:

$sb = { Install-ADDSForest -ForestMode 7 -DomainMode 7 ` -DomainName 'myorg.test' -DomainNetbiosName 'myorg' ` -InstallDns:$true -CreateDnsDelegation:$false ` -DatabasePath 'C:\Windows\NTDS' -LogPath 'C:\Windows\NTDS' ` -SysvolPath 'C:\Windows\SYSVOL' ` -Force:$true } Invoke-Command -Session $s -ScriptBlock $sb 1 2 3 4 5 6 7 8 9 $sb = { Install-ADDSForest -ForestMode 7 -DomainMode 7 ` -DomainName 'myorg.test' -DomainNetbiosName 'myorg' ` -InstallDns : $true -CreateDnsDelegation : $false ` -DatabasePath 'C:\Windows\NTDS' -LogPath 'C:\Windows\NTDS' ` -SysvolPath 'C:\Windows\SYSVOL' ` -Force : $true } Invoke-Command -Session $s -ScriptBlock $sb

You’ll be prompted for the Safe Mode Administrator password – and again for its confirmation.

The parameters are reasonably self-explanatory. The database, log, and SYSVOL paths are the defaults but I like to include them to make sure I think about their placement. The -InstallDns parameter will configure DNS, creating the appropriate zones and records. A DNS delegation won’t be created.

The forest and domain modes need some explanation. A value of 7 means Windows Server 2016 forest and domain level. For legacy operating systems you could use Win2008, Win2008R2, Win2012 or Win2012R2 as appropriate. It seems that a value for Windows Server 2016 wasn’t added to the cmdlet! The values for forest mode and therefore the operating systems you can use as domain controllers in your forest are listed here. This link will also show the equivalent at the domain level.

As the script runs, you’ll see a warning about the script asking for passwords, possibly issues with network adapters if any are configured with DHCP, DNS delegations even though you told it not to create one and Windows Server 2016 security settings. If you run the code in the PowerShell ISE you’ll see progress messages.

When the script has finished you’ll see something like this:

PSComputerName : TestDC01 RunspaceId : e5c81a53-765f-4923-8dbd-a0e3d066b56d Message : Operation completed successfully Context : DCPromo.General.1 RebootRequired : False Status : Success 1 2 3 4 5 6 PSComputerName : TestDC01 RunspaceId : e5c81a53 -765f -4923 -8dbd -a0e3d066b56d Message : Operation completed successfully Context : DCPromo . General . 1 RebootRequired : False Status : Success

Notice the RebootRequired returns false – even though you told it to reboot.

At this stage, you’ve got your first domain controller built and the AD forest is created. You now need a VM to use as the second DC.

Your remoting session will have been broken because the VM rebooted to finalize the domain controller creation. If you need to re-establish the session use the domain\Administrator form for the credential:

$cred = Get-Credential -Credential 'myorg\Administrator' $s = New-PSSession -VMName TestDC01 -Credential $cred 1 2 $cred = Get-Credential -Credential 'myorg\Administrator' $s = New-PSSession -VMName TestDC01 -Credential $cred

Create Second Domain Controller

Creating the second domain controller is a similar process to creating the first except you use Install-ADDSDomainController. First create, configure and patch your VM.

Install AD Domain services and DNS:

$vm = 'TestDC02' $cred = Get-Credential -Credential "$vm\Administrator" $s = New-PSSession -VMName $vm -Credential $cred $sb = {Install-WindowsFeature -Name AD-Domain-Services, DNS -IncludeAllSubFeature -IncludeManagementTools -Restart} Invoke-Command -Session $s -ScriptBlock $sb 1 2 3 4 5 $vm = 'TestDC02' $cred = Get-Credential -Credential "$vm\Administrator" $s = New-PSSession -VMName $vm -Credential $cred $sb = { Install-WindowsFeature -Name AD-Domain -Services , DNS -IncludeAllSubFeature -IncludeManagementTools -Restart } Invoke-Command -Session $s -ScriptBlock $sb

Then run this code:

$sb = { $cred = Get-Credential 'myorg\Administrator' Install-ADDSDomainController ` -Credential $cred ` -NoGlobalCatalog:$false ` -CreateDnsDelegation:$false ` -CriticalReplicationOnly:$false ` -DatabasePath 'C:\Windows\NTDS' ` -DomainName 'myorg.test' ` -InstallDns:$true ` -LogPath 'C:\Windows\NTDS' ` -NoRebootOnCompletion:$false ` -SysvolPath 'C:\Windows\SYSVOL' ` -Force:$true } Invoke-Command -Session $s -ScriptBlock $sb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $sb = { $cred = Get-Credential 'myorg\Administrator' Install-ADDSDomainController ` -Credential $cred ` -NoGlobalCatalog : $false ` -CreateDnsDelegation : $false ` -CriticalReplicationOnly : $false ` -DatabasePath 'C:\Windows\NTDS' ` -DomainName 'myorg.test' ` -InstallDns : $true ` -LogPath 'C:\Windows\NTDS' ` -NoRebootOnCompletion : $false ` -SysvolPath 'C:\Windows\SYSVOL' ` -Force : $true } Invoke-Command -Session $s -ScriptBlock $sb

You should get results like this returned:

PSComputerName : TestDC02 RunspaceId : 9bdcd659-4c5c-4764-9050-1e1d07e0fa13 Message : Operation completed successfully Context : DCPromo.General.1 RebootRequired : False Status : Success 1 2 3 4 5 6 PSComputerName : TestDC02 RunspaceId : 9bdcd659 -4c5c -4764 -9050 -1e1d07e0fa13 Message : Operation completed successfully Context : DCPromo . General . 1 RebootRequired : False Status : Success

By default, the new domain controller will be configured as a global catalog and won’t use critical replication. The parameters for these options in the code are for completeness so that you have an explicit statement of what has been performed. In a new AD, the domain controller will be added to the default site. If you want to add the domain controller to a specific site then you use the -SiteName parameter:

-SiteName ‘Site1’ 1 -SiteName ‘Site1’

You’re already well on your way to creating the perfect Hyper-V test environment! Now that you’ve got your domain controllers built it’s time to create some user accounts which we’ll get to in the next post in this series. In the meantime, why not ensure you have the perfect system set up for your test environment by implementing 6 Hardware Tweaks that will Skyrocket your Hyper-V Performance.