Yes, it’s time to wow and amaze you with my coding skills! Prepare to be amazed! Or, more appropriately, roll your eyes with my lame ability to hack other peoples code into what I need to get done, quickly. Writing code for me is painful. Oh, like high school physics I can follow what’s going on pretty well, but sitting down and writing? Yea, that’s a challenge.
I needed a Windows Domain Controller for what I’m working on in my work lab. Figuring that I’ll probably need more Domain Controllers for future labs, I thought it would be a good time to automate it. I looked online for a simple PowerShell script that would do what I want and to my amazement, I couldn’t find anything other than pointers to a lot of commands. And, in many cases, those commands were either wrong or broken.
Lots of Steps
Because I work at VMware and this Domain Controller will be running as a VM, I’ll be using VMware VM Templates to create the virtual machines. I built a Windows 2012 template using some simple best practices steps documented here. I wanted to be able to right-click on a template and simply deploy a Domain Controller using GuestOS customization.
Simple, right? Actually, not that difficult. It’s mainly about getting all the steps done in the right order and meeting the requirements of each step.
- First, you have deploy the VM with the guest OS customization.
- Then add the AD bits to your Windows 2012 system so that you can enable the appropriate roles going forward.
- You’ll probably want to rename the system.
- Then install the AD Forest
- Then add the roles and configure networking/AD/DNS/etc..
How do you automate all that?
RunOnce to the Rescue
RunOnce is a Windows registry setting that automatically sets up Windows to run certain tasks when a system starts up or a user logs in. vSphere Guest OS Customization uses this so that when you deploy from a template, you can set the Windows system to run further setup scripts to modify the OS. In this example, you see how I’ve set up my DC-Builder.PS1 script to run via RunOnce.
The command I’m running is:
%SystemRoot%\system32\WindowsPowershell\v.10\Powershell.exe -executionpolicy bypass -file “C:\dc-builder.ps1”
Let’s run that down so everyone gets what’s happening. After deploying from Template and choosing this Guest OS Customization Specification, Windows 2012 will do a sysprep. After that is completed, Windows will reboot and run the RunOnce command above. That sets the ball in motion.
In the command, we’re running the DC-Builder.PS1 script using PowerShell.exe and specifying that it be run with the execution policy of bypass. That’s because the DC-Builder.PS1 script is not signed. Otherwise, it wouldn’t run.
You’ll notice that I’m running DC-Builder.PS1 from C:\. That’s because I built my template with the file copied to that location. Why? Because I didn’t want to have to deal with floppy images or network locations. Oh, sure, you can and then you can just change the location, but for me, I found this was easier. You want to do it differently? By all means, knock yourself out. :)
Now you’re probably asking “Ok, how do you automate all the other reboots?” Well, I iteratively use RunOnce! I just nest the hell out of it!
DC-Builder creates a couple of scripts and sets up the first one to run after it reboots the system the first time. Then that script calls the next one to run. DC-Builder creates the files using the PowerShell “@” operator.
Here’s an abbreviated example. Don’t just cut and paste this.
$InstallForestbat = @"
REM Creating a .BAT file to change to bypass, run the commands, then change
REM back to remote
Powershell.exe set-executionpolicy bypass -force
IF EXIST %FileToDelete% del /F %FileToDelete%
powershell -noprofile -file c:\InstallForest.ps1 > c:\InstallForest-transcript.txt
REM Powershell.exe set-executionpolicy RemoteSigned
$InstallForestBat |Out-File -FilePath c:\InstallForest.bat -Force -encoding ASCII
$AdminKey = "HKLM:"
$RunOnceKey = $AdminKey + "\Software\Microsoft\Windows\CurrentVersion\RunOnce"
Set-ItemProperty -Path $RunOncekey -Name Foo -Value "C:\InstallForest.bat"
See how I’m using the “-encoding” qualifier? PS1 files don’t need it but .BAT files most definitely do.
DC-Builder creates four files to be run.
- InstallForest.BAT – This is set up to run via RunOnce after DC-Builder completes and the system reboots. It runs InstallForest.PS1.
- InstallForest.PS1 – This installs the AD Forest, sets up the running of Post-DCCleanup.Bat via RunOnce and reboots the system.
- Post-DCCleanup.BAT – It runs Post-DCCleanup.PS1.
- Post-DCCleanup.PS1 – This finishes off the rest of the steps that need to be run after installing the AD Forest and reboots the system.
DC-Builder.PS1, InstallForest.PS1 and Post-DCCleanupPS1 also create transaction files in C:\ so that you can check for errors.
Note: You’ll notice that DC-Builder.PS1 sets up the Administrator account to log in automatically. You need this, at least initially, to get all the scripts to run. They need to run under a user context. Because this is primarily for lab environments, I set up my VM’s to log in automatically just to save steps. Yea, the “security guy” said that. Whatever. :)
How do I get started?
- Edit DC-Builder.PS1 to set up your IP address, system name and any other custom setting you’d like.
- Create your Windows 2012 Template VM.
- Copy DC-Builder.PS1 to C:\
- Shutdown the Windows 2012 Template VM.
- Attach the Windows 2012 ISO image to the VM <This is key!
- Select “Convert to Template”
- Create a Windows Customization Specification as called out above (or Import the XML)
- Deploy a new VM from template using the Customization Specification
- The VM will reboot 4 times and you’ll be left with a Windows Domain Controller, ready to roll.
Download the script and guest OS customization XML
These files have now been moved to GitHub. You can get them here:
Give it a try and let me know what you think and what it’s missing. It could use some more documenting but if you’ve played around with PowerShell before, it should be all pretty straightforward. I’ve also credited where appropriate where I got some of the inspiration for the script and where I’ve blatantly copied code. Many thanks to some helpful hints from Brian @vTagion Graf, especially when dealing with variables embedded when using the “@” operator.