Hello collegues! In this article we will deploy virtual enviroment for different testing tasks with Vagrant and Powershell DSC. In this part we will start from minimal requirement of any Windows domain infrastructure – Active directory domain services. In our case sandbox will consist of 2 nodes: domain controller and domain member host.

Why is it needed?

This is a main question that need to answer. I think that everybody has his own opinion about this question. However, in general, this method allow to reduce the time needed to configure virtual labaratory.

Once we prepared the automation scenario, it can be reused on demand later. The benefit of this method is no need to do many actions by hand. We can save our time and also exclude different mistakes and wrong actions we can do while configuration.

In other words – if we have a task to simulate the work of your infrastructure components, we will face with first question:

How to do it?

It is OK if we need to test any components or software in our enviroment. But what if we need to check more complex scenario, e.g., difficult Active Directory topology?

When we realizing how much actions need to do by hand we will face with second question:

How to automate it?

This article is devoted to the answer to this question. Lets go further and we will know more about this.

Stack of used technologies and boring terminology

At a beginning we need to find our more about software and terminology that are required for further work.

Provider – in Vagrant’s terminology, the underlying virtualization solutions (VirtualBox, VmWare, Hyper-V) are called providers. It is a layer of virtualization vagrant based on.

VirtualBox – one of provider examples. We can create and configure virtual machines by this component. In this article we are using VirtualBox as default vagrant provider. Depending on the provider Vagrant configuration can differ.

Vagrant – this component automates virtual machines deploy configuration. This layer provides one style configuration virtual machines deployment.

Powershell DSC (Desired State Configuration) – task automation tool, which is based on a declarative paradigm (declarative approach). In other words, we describe the state that we want to receive, but do not describe the obtaining process of this state. We will see some magic during DSC configuration. 🙂

DSC resources – special modules that allow us to configure the system. This modules uses during dsc configuration.

Installing required software on Windows host

Automatic installation

There is a great possibility to automate this process by installation via Chocolatey package manager.

  • Windows 7+ / Windows server 2003+
  • Powershell v2+
  • .NET Framework 4+

If all dependencies are resolved, open cmd.exe as Administrator and paste the command below.

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" && choco install -y vagrant & choco install -y virtualbox

Manual installation

Of course we can use manual installation. To do it we need to:

It is not too hard to install this packages so we will skip this section and go further.

Sandbox deployment

A little notice. First, we will do some actions without much investigation, and only then we will figure out what we got there and why it works or does not.

Lets create our test laboratory project and form the files structure specified below:

  • LAB – root project folder.
  • Vagrantfile – file with instructions to deploy virtual machines.
  • provision – folder where DSC configurations are located (will use to configure virtual machine).
  • Files with .ps1 extension – DSC configurations for setup the system.

Now we need to define purpose each DSC configuration.

  • ConfigureRebootOnNode.ps1 – configures host reboot if needed.
  • DSCInstallDHCP.ps1 – install DHCP role, RSAT-DHCP component, authorize DHCP server in AD after installation and configure scope.
  • DSCInstallDomain.ps1 – install AD DS role, RSAT-ADDS component, configure PARTY.HARD domain.
  • DSCJoinDomain.ps1 – add host to Active Directory domain.
  • PreDSCInstall.ps1 – install required for work DSC modules.

Here is the project result structure we will get:

LAB
 │   Vagrantfile
 │
 └───provision
         ConfigureRebootOnNode.ps1
         DSCInstallDHCP.ps1
         DSCInstallDomain.ps1
         DSCJoinDomain.ps1
         PreDSCInstall.ps1

Copy Vagrantfile in project folder.

Vagrantfile

# -*- mode: ruby -*-
# # vi: set ft=ruby :
Vagrant.configure("2") do |config|  
  config.vagrant.plugins = "vagrant-reload"
  config.vm.define "lab-dc1" do |subconfig|
    subconfig.vm.box = "gusztavvargadr/windows-server"
    subconfig.vm.hostname = "lab-dc1"
    subconfig.vm.provider :virtualbox do |vb|
      vb.gui = false
    end
    subconfig.vm.network "private_network", ip: "192.168.11.2", 
      virtualbox__intnet: true
    subconfig.winrm.username = "vagrant"
    subconfig.winrm.password = "vagrant"
    subconfig.winrm.transport = :plaintext
    subconfig.winrm.basic_auth_only = true 
    #Install DSC Modules
    subconfig.vm.provision "shell",
      path: "provision\\PreDSCInstall.ps1"
    #https://github.com/dsccommunity/ActiveDirectoryDsc
    subconfig.vm.provision "shell",
      path: "provision\\DSCInstallDomain.ps1"
    #Restart VM to finish Active Directory Domain Services installation
    subconfig.vm.provision :reload
    #https://github.com/dsccommunity/xDhcpServer
    subconfig.vm.provision "shell",
      path: "provision\\DSCInstallDHCP.ps1"
  end
    
  config.vm.define "lab-test" do |subconfig|
    subconfig.vm.box = "gusztavvargadr/windows-server"
    subconfig.vm.hostname = "lab-test"
    subconfig.vm.provider :virtualbox do |vb|
      vb.gui = false
    end
    subconfig.vm.network "private_network", ip: "127.0.0.2", 
      auto_config: false,
      virtualbox__intnet: true
    subconfig.winrm.username = "vagrant"
    subconfig.winrm.password = "vagrant"
    subconfig.winrm.transport = :plaintext
    subconfig.winrm.basic_auth_only = true
    subconfig.vm.provision "shell",
      path: "provision\\ConfigureRebootOnNode.ps1"    
    subconfig.vm.provision "shell",
      path: "provision\\PreDSCInstall.ps1"
    subconfig.vm.provision "shell",
      path: "provision\\DSCJoinDomain.ps1"
  end
end

Copy PreDSCInstall.ps1 file in provision folder.

Install-Module -Name ActiveDirectoryDsc -Force
Install-Module -Name PSDscResources -Force
Install-Module -Name ComputerManagementDsc -Force
Install-Module -Name xDhcpServer -Force

Copy DSCInstallDomain.ps1 file in provision folder.

DSCInstallDomain.ps1

Configuration ADDomain_NewForest_Config
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        $SafeModePassword
    )

    Import-DscResource -ModuleName PSDscResources
    Import-DscResource -ModuleName ActiveDirectoryDsc
    Import-DscResource -ModuleName ComputerManagementDsc

    Node 'localhost'
    {
        WindowsFeature 'Active Directory Domain Services'
        {
            Name   = 'AD-Domain-Services'
            Ensure = 'Present'
        }

        WindowsFeature 'RSAT-ADDS'
        {
            Name   = 'RSAT-ADDS'
            Ensure = 'Present'
        }
        
        ADDomain 'party.hard'
        {
            DomainName                    = 'party.hard'
            Credential                    = $Credential
            SafemodeAdministratorPassword = $SafeModePassword
            ForestMode                    = 'WinThreshold'
        }
    }
}

#Next block is using to allow password as plain text
$cd = @{
    AllNodes = @(
        @{
            NodeName = 'localhost'
            PSDscAllowPlainTextPassword = $true
         }
    )
}

#Define user and password for ADDomain deployment (also used for restore).
$password = ConvertTo-SecureString "RestorePassword123$" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('vagrant',$password)

#Create MOF
ADDomain_NewForest_Config -Credential $cred -SafeModePassword $cred -ConfigurationData $cd

#Execute MOF
Start-DscConfiguration -Path .\ADDomain_NewForest_Config -Force -Wait -Verbose

Copy DSCInstallDHCP.ps1 file in provision folder.

DSCInstallDHCP.ps1

Configuration xDhcpsServerScope_NewScope
{
    Import-DscResource -ModuleName xDHCpServer
    #Define NIC IP
    $IP = Get-NetIPAddress -InterfaceAlias "Ethernet 2" | Where-Object {$_.IPAddress -notlike "*:*" } | select -ExpandProperty IPAddress

    Node 'localhost'
    {
        WindowsFeature 'RSAT-DHCP'
        {
            Name   = 'RSAT-DHCP'
            Ensure = 'Present'
        }

        WindowsFeature 'DHCP'
        {
            Name   = 'DHCP'
            Ensure = 'Present'
        }
                
        xDhcpServerAuthorization RemoteServerActivation
        {
            Ensure = 'Present'
            DnsName = $env:COMPUTERNAME + '.party.hard'
            IPAddress = $IP
        }       
                  
        
        xDhcpServerScope Scope
        {
            ScopeId = '192.168.11.0'
            Ensure = 'Present'
            IPEndRange = '192.168.11.254'
            IPStartRange = '192.168.11.10'
            Name = '11.0/24'
            SubnetMask = '255.255.255.0'
            LeaseDuration = ((New-TimeSpan -Hours 8 ).ToString())
            State = 'Active'
            AddressFamily = 'IPv4'
        }     
        
        xDhcpServerOption Option
        {
            Ensure = 'Present'
            ScopeID = '192.168.11.0'
            DnsDomain = 'party.hard'
            DnsServerIPAddress = '192.168.11.2'
            AddressFamily = 'IPv4'
            Router = '192.168.11.1'
        }
    }
}

xDhcpsServerScope_NewScope
Start-DscConfiguration -Path .\xDhcpsServerScope_NewScope -Force -Wait -Verbose

Copy DSCJoinDomain.ps1 in provision folder.

DSCJoinDomain.ps1

Configuration JoinDomainConfiguration
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullorEmpty()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    Import-DscResource -Module ComputerManagementDsc

    Node 'localhost'
    {
        Computer JoinDomain
        {
            Name       = $env:COMPUTERNAME
            DomainName = 'PARTY'
            Credential = $Credential # Credential to join to domain
        }
    }
}


#Next block is using to allow password as plain text
$cd = @{
    AllNodes = @(
        @{
            NodeName = 'localhost'
            PSDscAllowPlainTextPassword = $true
         }
    )
}

#Define user and password for ADDomain deployment (also used for restore).
$password = ConvertTo-SecureString "vagrant" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('party.hard\vagrant',$password)

#Create MOF
JoinDomainConfiguration -Credential $cred -ConfigurationData $cd

#Execute MOF
Start-DscConfiguration -Path .\JoinDomainConfiguration -Force -Wait -Verbose

Copy ConfigureRebootOnNode.ps1 file in provision folder.

ConfigureRebootOnNode.ps1

Configuration ConfigureRebootOnNode
{
    Node 'localhost'
    {
        LocalConfigurationManager
        {
            RebootNodeIfNeeded = $true
        }
    }
}

#Create MOF
ConfigureRebootOnNode
Set-DscLocalConfigurationManager .\ConfigureRebootOnNode -Verbose
Get-DscLocalConfigurationManager

Project structure is ready. What is next?

We have completed the most difficult part, congratulations. Now to deploy our test laboratory it is enough to run the command in cmd:

vagrant up

That’s all. Let’s go drink some tea. Depending on the performance of your machine and the speed of the Internet, you may have to drink more than one mug 🙂

When you return, you will find a small virtual environment that consists of two nodes. We can verify this by running the command:

vagrant status

We will see list of running virtual machines:

Current machine states:
lab-dc1                   running (virtualbox)
lab-test                  running (virtualbox)
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

Or we can open VirtualBox VM Manager. We will see the same virtual machines and same status.

What happened under the hood?

Vagrant

Let’s take a step-by-step look at what happened. All key logic are described in the Vagrantfile. For convenience, I added comments to the lines. Let’s go back to the Vagrantfile and consider the configuration of one of the machines.

config.vm.define "lab-dc1" do |subconfig|
    #Define OS image box for VM. Vagrant will download the box from the public repository (https://app.vagrantup.com/boxes/search)
    subconfig.vm.box = "gusztavvargadr/windows-server"
    #Define VM hostname
    subconfig.vm.hostname = "lab-dc1"
    #Define virtualization provider settings (not mandatory)
    subconfig.vm.provider :virtualbox do |vb|
        #Run VM in backgroung
        vb.gui = false
    end
    #Define network configuration: nic type, ip.
    subconfig.vm.network "private_network", ip: "192.168.11.2", 
        virtualbox__intnet: true
    #Define login and password for VM winrm connection and provision.
    subconfig.winrm.username = "vagrant"
    subconfig.winrm.password = "vagrant"
    #Define connection parameters.
    subconfig.winrm.transport = :plaintext
    subconfig.winrm.basic_auth_only = true 
    #Run Powershell script that will install requirement DSC modules
    subconfig.vm.provision "shell",
        path: "provision\\PreDSCInstall.ps1"
    #Run Powershell DSC configuration that will deploy AD DS
    #https://github.com/dsccommunity/ActiveDirectoryDsc
    subconfig.vm.provision "shell",
        path: "provision\\DSCInstallDomain.ps1"
    #Reboot VM after AD DS installation
    subconfig.vm.provision :reload
    #Run Powershell DSC configuration that will install and configure DHCP service
    #https://github.com/dsccommunity/xDhcpServer
    subconfig.vm.provision "shell",
        path: "provision\\DSCInstallDHCP.ps1"
end

Unfortunately this article does not fully cover the Vagrant topic. If you want to know more about it I can recomend to read official documentation.

Additional materials

https://www.vagrantup.com/docs/

The DSC configurations used in the deployment process should also be considered. Let’s go back to them to understand the working principles.

Powershell DSC

At the beginning of the article, it was already mentioned that, unlike the usual PowerShell scripts, DSC configurations uses a declarative approach. Please note that we do not indicate exactly how we will bring the system to the desired state. In fact, we only need to declare (describe) the desired state.

Also at the beginning of the article, the concept was mentioned – Powershell resources. For PowerShell DSC technology, the concept of resources is the key. DSC resources performs configuration described in DSC configurations. There are both standard resources (included in the system) and user resources (imported separately).

You may have noticed that resources are imported in some configurations. These are examples of user (extensible) resources that have been downloaded from the github repository of the dsccommunity community.

For example we will review DSCInstallDHCP.ps1 configuration that install and configure DHCP. I added comments to the lines.

#Declare configuration name
Configuration xDhcpsServerScope_NewScope
{
    #Import requirement resources
    Import-DscResource -ModuleName xDHCpServer
    #Declare hosts that needs to configure
    Node 'localhost'
    {
        #Declare RSAT-DHCP component installation by WindowsFeature resource (standart resource example)
        WindowsFeature 'RSAT-DHCP'
        {
            #Declare requirement resource filels (component name and status).
            Name   = 'RSAT-DHCP'
            Ensure = 'Present'
        }
        #Declare DHCP component installation by WindowsFeature resource (standart resource example)
        WindowsFeature 'DHCP'
        {
            #Declare requirement resource fields (component name and status).
            Name   = 'DHCP'
            Ensure = 'Present'
        }
        #Declare DHCP Server authorization by xDhcpServerAuthorization resource (user resourse example)    
        xDhcpServerAuthorization LocalServerActivation
        {
            Ensure = 'Present'
        }     
        #Declare DHCP scope creation by xDhcpServerScope 
 resource (user resourse example).
        xDhcpServerScope Scope
        {
            #Declare score settings
            ScopeId = '192.168.11.0'
            Ensure = 'Present'
            IPEndRange = '192.168.11.254'
            IPStartRange = '192.168.11.10'
            Name = '11.0/24'
            SubnetMask = '255.255.255.0'
            LeaseDuration = ((New-TimeSpan -Hours 8 ).ToString())
            State = 'Active'
            AddressFamily = 'IPv4'
        }     
        #Declare DHCP patameters creation by xDhcpServerOption resource (user resourse example).
        xDhcpServerOption Option
        {
            Ensure = 'Present'
            ScopeID = '192.168.11.0'
            DnsDomain = 'party.hard'
            DnsServerIPAddress = '192.168.11.2'
            AddressFamily = 'IPv4'
            Router = '192.168.11.1'
        }
    }
}
#Create MOF file to execute configuration
xDhcpsServerScope_NewScope
#Execute configuration
Start-DscConfiguration -Path .\xDhcpsServerScope_NewScope -Force -Wait -Verbose

Unfortunately this article does not fully cover the Powershell DSC topic. If you want to know more about it I can recomend to read official documentation.

Additional materials

https://docs.microsoft.com/en-us/powershell/scripting/dsc/overview/overview?view=powershell-7

Is it work?

It is a reasonable question. 🙂
I suggest to make sure that all services have been deployed and correctly configured in accordance with the described requirements.

What functionality do we expect from the our sandbox?
Globally, we expect two hosts on the local network 192.168.11.0/24. One is the domain controller PARTY.HARD, the other is a member of this domain.

LAB-DC1 — 192.168.11.2

  • Active Directory Domain Services
  • AD RSAT
  • PARTY.HARD domain configuration.
  • DHCP
  • DHCP RSAT
  • DHCP authorize
  • DHCP scope configuration

LAB-TEST — 192.168.11.10 (IP was assigned by DHCP)

  • PARTY.HARD domain member

After deploying the Active Directory, the Administrator domain account with the standard vagrant password became available. I will try to use this account and log in using VirtualBox Manager.

I went into the server manager on the domain controller. As we can see the services were installed.
I also went into the ADUC snap-in. A domain exists and member hosts are located in the corresponding Organizational Units.
It seems that there were no problems with DHCP either. We can see that the area has been added.
In the list of leased addresses we can see our second VM.

Summary

It looks like we were able to deploy a virtual environment. There are many different scenarios, it all depends on your tasks, wishlist, RAM size and disk subsystem speed 🙂

In the next part we will try to increase the size of the sandbox and expand scenarios with DFSR component.
See you!

One thought on “Deploying an automating Windows sandbox using Vagrant and Powershell DSC. Part 1. AD.

Leave a Reply to Vladilen Karassev Cancel reply

Your email address will not be published. Required fields are marked *