As you read in Part 15 of this blog Series we are able to create some great Gold Images with Big Demo.

How do we go about building the actual application Servers from the Gold Images?

 

Building your Servers with Big_Demo

 

Whenever you are working with logic and automation it is critical to control the steps that happen.

 

For example, you can’t join a domain without a functional domain controller?

To work around this there is a wonderful PowerShell Function that Dave grabbed from Ben Armstrong @VirtualPCGUy.

It’s called Wait-PSDirect and it uses PowerShell Direct to determine if a VM is ready to proceed by checking credentials.

As the script progresses you will notice that there are a few different switches Wait-PSDirect uses.

 

One is using $LocalCred which is the Local Administrator Account and one is $DomainCred which uses the Domain Admin Account.

 

This is important to understand because as the lab is building there are times when the machine hasn’t joined the domain yet and we are waiting on a pending reboot.

We want to let the machine finish the reboot before the script continues.

 

Yes it is a glorified Wait function but the logic behind it is brilliant because it just works. Kind of like Veeam Software

 

You will see this Function called a lot in the script:

function Wait-PSDirect
{
     param
     (
         [string]
         $VMName,

         [Object]
         $cred
     )

    Write-Log $VMName "Waiting for PowerShell Direct (using $($cred.username))"
    while ((Invoke-Command -VMName $VMName -Credential $cred {
                'Test'
    } -ea SilentlyContinue) -ne 'Test') 
    {
        Start-Sleep -Seconds 1
    }
}

 

 

Next, we have Restart-VM which helps us be simply automating a reboot of the VM’s as they are servicing.

function Restart-DemoVM
{
     param
     (
         [string]
         $VMName
     )

    Write-Log $VMName 'Rebooting'
    stop-vm $VMName
    start-vm $VMName
}

 

So, what actually builds the HyperV VM’s for us? Well it is the next few functions that do all the heavy lifting for us.

 

Invoke-DemoPrepVM is the function that will build all of the base VM’s in the Lab for us. We use it to build AD,DHCP,MGMT and other core VMS.

Remember that these are all built from the Gold VHDx file that we learned about in Part 15 of this blog post.

Each of these new VM’s are created using differencing disks, configured with a set amount of RAM, vCPUS, etc.

function Invoke-DemoVMPrep 
{
    param
    (
        [string] $VMName, 
        [string] $GuestOSName, 
        [switch] $FullServer
    ) 

    Write-Log $VMName 'Removing old VM'
    get-vm $VMName -ErrorAction SilentlyContinue |
    stop-vm -TurnOff -Force -Passthru |
    remove-vm -Force
    Clear-File "$($VMPath)\$($GuestOSName).vhdx"
   
    Write-Log $VMName 'Creating new differencing disk'
    if ($FullServer) 
    {
        $null = New-VHD -Path "$($VMPath)\$($GuestOSName).vhdx" -ParentPath "$($BaseVHDPath)\VMServerBase.vhdx" -Differencing
    }

    else 
    {
        $null = New-VHD -Path "$($VMPath)\$($GuestOSName).vhdx" -ParentPath "$($BaseVHDPath)\VMServerBaseCore.vhdx" -Differencing
    }

    Write-Log $VMName 'Creating virtual machine'
    new-vm -Name $VMName -MemoryStartupBytes 16GB -SwitchName $virtualSwitchName `
    -Generation 2 -Path "$($VMPath)\" | Set-VM -ProcessorCount 2 

    Set-VMFirmware -VMName $VMName -SecureBootTemplate MicrosoftUEFICertificateAuthority
    Set-VMFirmware -Vmname $VMName -EnableSecureBoot off
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName).vhdx" -ControllerType SCSI
    Write-Log $VMName 'Starting virtual machine'
    Enable-VMIntegrationService -Name 'Guest Service Interface' -VMName $VMName
    start-vm $VMName
}

 

The build process for these VM’s takes about 2 minutes as they are just spinning up from a Sysprepped Master Gold VHDx file.

 

After the core VM’s have been provisioned we use Create-DemoVM to name the VM, Set the IP Address, and then use the Wait-PSDirect function to give it time to finish before continuing.

function Create-DemoVM 
{
    param
    (
        [string] $VMName, 
        [string] $GuestOSName, 
        [string] $IPNumber = '0'
    ) 
  
    Wait-PSDirect $VMName -cred $localCred

    Invoke-Command -VMName $VMName -Credential $localCred {
        param($IPNumber, $GuestOSName,  $VMName, $domainName, $Subnet)
        if ($IPNumber -ne '0') 
        {
            Write-Output -InputObject "[$($VMName)]:: Setting IP Address to $($Subnet)$($IPNumber)"
            $null = New-NetIPAddress -IPAddress "$($Subnet)$($IPNumber)" -InterfaceAlias 'Ethernet' -PrefixLength 24
            Write-Output -InputObject "[$($VMName)]:: Setting DNS Address"
            Get-DnsClientServerAddress | ForEach-Object -Process {
                Set-DnsClientServerAddress -InterfaceIndex $_.InterfaceIndex -ServerAddresses "$($Subnet)1"
            }
        }
        Write-Output -InputObject "[$($VMName)]:: Renaming OS to `"$($GuestOSName)`""
        Rename-Computer -NewName $GuestOSName
        Write-Output -InputObject "[$($VMName)]:: Configuring WSMAN Trusted hosts"
        Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value "*.$($domainName)" -Force
        Set-Item WSMan:\localhost\client\trustedhosts "$($Subnet)*" -Force -concatenate
        Enable-WSManCredSSP -Role Client -DelegateComputer "*.$($domainName)" -Force
    } -ArgumentList $IPNumber, $GuestOSName, $VMName, $domainName, $Subnet

    Restart-DemoVM $VMName
    
    Wait-PSDirect $VMName -cred $localCred
}

 

Now because our Storage Nodes for Storage Spaces Direct require a bit of a customized configuration like: Additional VHDx files that serve as our JBOD disks, Core Networking, etc.

A customized version of the Create-DemoVM function has been built for this purpose. It is called Invoke-NodeStorageBuild.

function Invoke-NodeStorageBuild 
{
    param($VMName, $GuestOSName)

    Create-DemoVM $VMName $GuestOSName
    Clear-File "$($VMPath)\$($GuestOSName) - Data 1.vhdx"
    Clear-File "$($VMPath)\$($GuestOSName) - Data 2.vhdx"
    Clear-File "$($VMPath)\$($GuestOSName) - Data 3.vhdx"
    Clear-File "$($VMPath)\$($GuestOSName) - Data 4.vhdx"
    Clear-File "$($VMPath)\$($GuestOSName) - Data 5.vhdx"
    Clear-File "$($VMPath)\$($GuestOSName) - Data 6.vhdx"
    Get-VM $VMName | Stop-VM 
    Add-VMNetworkAdapter -VMName $VMName -SwitchName $virtualSwitchName
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 1.vhdx" -Dynamic -SizeBytes 200GB 
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 1.vhdx" -ControllerType SCSI
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 2.vhdx" -Dynamic -SizeBytes 200GB
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 2.vhdx" -ControllerType SCSI
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 3.vhdx" -Dynamic -SizeBytes 200GB
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 3.vhdx" -ControllerType SCSI
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 4.vhdx" -Dynamic -SizeBytes 200GB
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 4.vhdx" -ControllerType SCSI
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 5.vhdx" -Dynamic -SizeBytes 200GB
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 5.vhdx" -ControllerType SCSI
    new-vhd -Path "$($VMPath)\$($GuestOSName) - Data 6.vhdx" -Dynamic -SizeBytes 200GB
    Add-VMHardDiskDrive -VMName $VMName -Path "$($VMPath)\$($GuestOSName) - Data 6.vhdx" -ControllerType SCSI
    Set-VMProcessor -VMName $VMName -Count 2 -ExposeVirtualizationExtensions $true
    Add-VMNetworkAdapter -VMName $VMName -SwitchName $virtualSwitchName
    Add-VMNetworkAdapter -VMName $VMName -SwitchName $virtualSwitchName
    Add-VMNetworkAdapter -VMName $VMName -SwitchName $virtualSwitchName
    Get-VMNetworkAdapter -VMName $VMName | Set-VMNetworkAdapter -AllowTeaming On
    Get-VMNetworkAdapter -VMName $VMName | Set-VMNetworkAdapter -MacAddressSpoofing on
    Start-VM $VMName
    Wait-PSDirect $VMName -cred $localCred

    Invoke-Command -VMName $VMName -Credential $localCred {
        param($VMName, $domainCred, $domainName)
        Write-Output -InputObject "[$($VMName)]:: Installing Clustering"
        $null = Install-WindowsFeature -Name File-Services, Failover-Clustering, Hyper-V -IncludeManagementTools
        Write-Output -InputObject "[$($VMName)]:: Joining domain as `"$($env:computername)`""
        while (!(Test-Connection -ComputerName $domainName -BufferSize 16 -Count 1 -Quiet -ea SilentlyContinue)) 
        {
            Start-Sleep -Seconds 1
        }
        do 
        {
            Add-Computer -DomainName $domainName -Credential $domainCred -ea SilentlyContinue
        }
        until ($?)
    } -ArgumentList $VMName, $domainCred, $domainName

    Wait-PSDirect $VMName -cred $domainCred

    Invoke-Command -VMName $VMName -Credential $domainCred {
        Rename-NetAdapter -Name 'Ethernet' -NewName 'LOM-P0'
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        Rename-NetAdapter -Name 'Ethernet 2' -NewName 'LOM-P1'
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        Rename-NetAdapter -Name 'Ethernet 3' -NewName 'Riser-P0'
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred{
        Get-NetAdapter -Name 'Ethernet 5' | Rename-NetAdapter -NewName 'Riser-P1'
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        New-NetLbfoTeam -Name HyperVTeam -TeamMembers 'LOM-P0' -verbose -confirm:$false
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        Add-NetLbfoTeamMember 'LOM-P1' -team HyperVTeam -confirm:$false
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        New-NetLbfoTeam -Name StorageTeam -TeamMembers 'Riser-P0' -verbose -confirm:$false
    }
    Invoke-Command -VMName $VMName -Credential $DomainCred {
        Add-NetLbfoTeamMember 'Riser-P1' -team StorageTeam -confirm:$false
    }

    Restart-DemoVM $VMName
    Wait-PSDirect $VMName -cred $domainCred

    ping localhost -n 20

    Invoke-Command -VMName $VMName -Credential $domainCred {
        New-VMSwitch -Name 'VSW01' -NetAdapterName 'HyperVTeam' -AllowManagementOS $false
    }
    Invoke-Command -VMName $VMName -Credential $domainCred {
        Add-VMNetworkAdapter -ManagementOS -Name ClusterCSV-VLAN204 -Switchname VSW01 -verbose
    }
    Invoke-Command -VMName $VMName -Credential $domainCred {
        Add-VMNetworkAdapter -ManagementOS -Name LM-VLAN203 -Switchname VSW01 -verbose
    }
    Invoke-Command -VMName $VMName -Credential $domainCred {
        Add-VMNetworkAdapter -ManagementOS -Name Servers-VLAN201 -Switchname VSW01 -verbose
    }
    Invoke-Command -VMName $VMName -Credential $domainCred {
        Add-VMNetworkAdapter -ManagementOS -Name MGMT-VLAN200 -Switchname VSW01 -verbose
    }

    # Restart-DemoVM $VMName
}

 

 

Here is an example of the Script in action:

First, we run the Invoke-DemoPrepVM which builds the VM, then the Create-DemoVM to give it a name and IP Address.

The last step is to configure something inside the VM. In this case we will install Active Directory using PowerShell Direct for the Lab.

Invoke-DemoVMPrep 'DC1-RDS' 'DC1-RDS' -FullServer


$VMName = 'DC1-RDS'
$GuestOSName = 'DC1-RDS'
$IPNumber = '1'

Create-DemoVM $VMName $GuestOSName $IPNumber

Invoke-Command -VMName $VMName -Credential $localCred {
    param($VMName, $domainName, $domainAdminPassword)

    Write-Output -InputObject "[$($VMName)]:: Installing AD"
    $null = Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
    Write-Output -InputObject "[$($VMName)]:: Enabling Active Directory and promoting to domain controller"
    Install-ADDSForest -DomainName $domainName -InstallDNS -NoDNSonNetwork -NoRebootOnCompletion `
    -SafeModeAdministratorPassword (ConvertTo-SecureString -String $domainAdminPassword -AsPlainText -Force) -confirm:$false
} -ArgumentList $VMName, $domainName, $domainAdminPassword

Restart-DemoVM $VMName 


 

Remember the base code for this can be downloaded from http://www.github.com/dkawula

 

Let’s have a look at the Script executing to build my lab which includes my nested Storage Spaces Direct Farm.

Write-Log 'Host' 'Getting started...'

Confirm-Path $BaseVHDPath
Confirm-Path $VMPath
Write-Log 'Host' 'Building Base Images'

if (!(Test-Path -Path "$($BaseVHDPath)\VMServerBase.vhdx")) 
{
    . Initialize-BaseImage
}

if ((Get-VMSwitch | Where-Object -Property name -EQ -Value $virtualSwitchName) -eq $null)
{
    New-VMSwitch -Name $virtualSwitchName -SwitchType Private
}


Invoke-DemoVMPrep 'DHCP1-RDS' 'DHCP1-RDS' -FullServer
Invoke-DemoVMPrep 'MGMT1-RDS' 'MDT01-RDS' -FullServer
Invoke-DemoVMPrep 'DC1-RDS' 'DC1-RDS' -FullServer


$VMName = 'DC1-RDS'
$GuestOSName = 'DC1-RDS'
$IPNumber = '1'

Create-DemoVM $VMName $GuestOSName $IPNumber

Invoke-Command -VMName $VMName -Credential $localCred {
    param($VMName, $domainName, $domainAdminPassword)

    Write-Output -InputObject "[$($VMName)]:: Installing AD"
    $null = Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
    Write-Output -InputObject "[$($VMName)]:: Enabling Active Directory and promoting to domain controller"
    Install-ADDSForest -DomainName $domainName -InstallDNS -NoDNSonNetwork -NoRebootOnCompletion `
    -SafeModeAdministratorPassword (ConvertTo-SecureString -String $domainAdminPassword -AsPlainText -Force) -confirm:$false
} -ArgumentList $VMName, $domainName, $domainAdminPassword

Restart-DemoVM $VMName 


$VMName = 'DHCP1-RDS'
$GuestOSName = 'DHCP1-RDS'
$IPNumber = '3'

Create-DemoVM $VMName $GuestOSName $IPNumber

Invoke-Command -VMName $VMName -Credential $localCred {
    param($VMName, $domainCred, $domainName)
    Write-Output -InputObject "[$($VMName)]:: Installing DHCP"
    $null = Install-WindowsFeature DHCP -IncludeManagementTools
    Write-Output -InputObject "[$($VMName)]:: Joining domain as `"$($env:computername)`""
    while (!(Test-Connection -ComputerName $domainName -BufferSize 16 -Count 1 -Quiet -ea SilentlyContinue)) 
    {
        Start-Sleep -Seconds 1
    }
    do 
    {
        Add-Computer -DomainName $domainName -Credential $domainCred -ea SilentlyContinue
    }
    until ($?)
} -ArgumentList $VMName, $domainCred, $domainName

Restart-DemoVM $VMName
Wait-PSDirect $VMName -cred $domainCred

Invoke-Command -VMName $VMName -Credential $domainCred {
    param($VMName, $domainName, $Subnet, $IPNumber)

    Write-Output -InputObject "[$($VMName)]:: Waiting for name resolution"

    while ((Test-NetConnection -ComputerName $domainName).PingSucceeded -eq $false) 
    {
        Start-Sleep -Seconds 1
    }

    Write-Output -InputObject "[$($VMName)]:: Configuring DHCP Server"    
    Set-DhcpServerv4Binding -BindingState $true -InterfaceAlias Ethernet
    Add-DhcpServerv4Scope -Name 'IPv4 Network' -StartRange "$($Subnet)10" -EndRange "$($Subnet)200" -SubnetMask 255.255.255.0
    Set-DhcpServerv4OptionValue -OptionId 6 -value "$($Subnet)1"
    Add-DhcpServerInDC -DnsName "$($env:computername).$($domainName)"
    foreach($i in 1..99) 
    {
        $mac = '00-b5-5d-fe-f6-' + ($i % 100).ToString('00')
        $ip = $Subnet + '1' + ($i % 100).ToString('00')
        $desc = 'Container ' + $i.ToString()
        $scopeID = $Subnet + '0'
        Add-DhcpServerv4Reservation -IPAddress $ip -ClientId $mac -Description $desc -ScopeId $scopeID
    }
} -ArgumentList $VMName, $domainName, $Subnet, $IPNumber

Restart-DemoVM $VMName


$VMName = 'DC1-RDS'
$GuestOSName = 'DC1-RDS'
$IPNumber = '1'

Wait-PSDirect $VMName -cred $domainCred

Invoke-Command -VMName $VMName -Credential $domainCred {
    param($VMName, $password)

    Write-Output -InputObject "[$($VMName)]:: Creating user account for Dave"
    do 
    {
        Start-Sleep -Seconds 5
        New-ADUser `
        -Name 'Dave' `
        -SamAccountName  'Dave' `
        -DisplayName 'Dave' `
        -AccountPassword (ConvertTo-SecureString -String $password -AsPlainText -Force) `
        -ChangePasswordAtLogon $false  `
        -Enabled $true -ea 0
    }
    until ($?)
    Add-ADGroupMember -Identity 'Domain Admins' -Members 'Dave'
} -ArgumentList $VMName, $domainAdminPassword

$VMName = 'MGMT1-RDS'
$GuestOSName = 'MGMT1-RDS'

Create-DemoVM $VMName $GuestOSName

Invoke-Command -VMName $VMName -Credential $localCred {
    param($VMName, $domainCred, $domainName)
    Write-Output -InputObject "[$($VMName)]:: Management tools"
    $null = Install-WindowsFeature RSAT-Clustering, RSAT-Hyper-V-Tools
    Write-Output -InputObject "[$($VMName)]:: Joining domain as `"$($env:computername)`""
    while (!(Test-Connection -ComputerName $domainName -BufferSize 16 -Count 1 -Quiet -ea SilentlyContinue)) 
    {
        Start-Sleep -Seconds 1
    }
    do 
    {
        Add-Computer -DomainName $domainName -Credential $domainCred -ea SilentlyContinue
    }
    until ($?)
} -ArgumentList $VMName, $domainCred, $domainName

Restart-DemoVM $VMName


Invoke-DemoVMPrep 'S2D1' 'S2D1' #-FullServer 
Invoke-DemoVMPrep 'S2D2' 'S2D2' #-FullServer 
Invoke-DemoVMPrep 'S2D3' 'S2D3' #-FullServer 
Invoke-DemoVMPrep 'S2D4' 'S2D4' #-FullServer 
Invoke-DemoVMPrep 'S2D5' 'S2D5' #-FullServer 


Wait-PSDirect 'S2D5' -cred $localCred

$VMName = 'S2D1'
$GuestOSName = 'S2D1'

Invoke-NodeStorageBuild 'S2D1' 'S2D1' 
Invoke-NodeStorageBuild 'S2D2' 'S2D2' 
Invoke-NodeStorageBuild 'S2D3' 'S2D3' 
Invoke-NodeStorageBuild 'S2D4' 'S2D4' 
Invoke-NodeStorageBuild 'S2D5' 'S2D5' 

Wait-PSDirect 'S2D5' -cred $domainCred

Invoke-Command -VMName 'MGMT1' -Credential $domainCred {
    param ($domainName)
    do 
    {
        New-Cluster -Name S2DCluster -Node S2D1, S2D2, S2D3, S2D4,S2D5 -NoStorage
    }
    until ($?)
    while (!(Test-Connection -ComputerName "S2DCluster.$($domainName)" -BufferSize 16 -Count 1 -Quiet -ea SilentlyContinue)) 
    {
        ipconfig.exe /flushdns
        Start-Sleep -Seconds 1
    }
    {
    (Get-Cluster).S2DBusTypes=4294967295
    }
    #Enable-ClusterStorageSpacesDirect -Cluster "S2DCluster.$($domainName)"
    #Add-ClusterScaleOutFileServerRole -name S2DFileServer -cluster "S2DCluster.$($domainName)"
} -ArgumentList $domainName

Invoke-Command -VMName 'S2D1' -Credential $domainCred {
    param ($domainName)
    #New-StoragePool -StorageSubSystemName "S2DCluster.$($domainName)" -FriendlyName S2DPool -WriteCacheSizeDefault 0 -ProvisioningTypeDefault Fixed -ResiliencySettingNameDefault Mirror -PhysicalDisk (Get-StorageSubSystem  -Name "S2DCluster.$($domainName)" | Get-PhysicalDisk)
    #New-Volume -StoragePoolFriendlyName S2DPool -FriendlyName S2DDisk -PhysicalDiskRedundancy 2 -FileSystem CSVFS_REFS -Size 500GB
    #updated from MSFT TP5 notes

   # The Cmdlet changed for RTM - Enable-ClusterS2D -CacheMode Disabled -AutoConfig:0 -SkipEligibilityChecks -confirm:$false
   #This step can take a little while espeically if you are adding a lot of nodes and disks
     
    (Get-Cluster).S2DBusTypes=4294967295
    
    Enable-ClusterStorageSpacesDirect -PoolFriendlyName S2DPool -confirm:$False

    #Create storage pool and set media type to HDD
    #New-StoragePool -StorageSubSystemFriendlyName *Cluster* -FriendlyName S2D -ProvisioningTypeDefault Fixed -PhysicalDisk (Get-PhysicalDisk | Where-Object -Property CanPool -EQ -Value $true)

    #Create a volume
    #This will match the configuration that was done in the book

    New-Volume -StoragePoolFriendlyName S2DPool -FriendlyName Mirror-2Way -FileSystem CSVFS_REFS -Size 200GB -PhysicalDiskRedundancy 1 
    New-Volume -StoragePoolFriendlyName S2DPool -FriendlyName Mirror-3Way -FileSystem CSVFS_REFS -Size 200GB -PhysicalDiskRedundancy 2 

} -ArgumentList $domainName


Write-Log 'Done' 'Done!'

 

 

 

 

Hope you enjoyed this post and have fun learning more about HyperV, Storage Spaces Direct, and PowerShell.

 

Happy learning….

 

Thanks,

 

Cristal

 

Cristal Kawula

Cristal Kawula is the co-founder of MVPDays Community Roadshow and #MVPHour live Twitter Chat. She was also a member of the Gridstore Technical Advisory board and is the President of TriCon Elite Consulting. Cristal is also only the 2nd woman in the world to receive the prestigious Veeam Vanguard award.

 

BLOG: http://www.checkyourlogs.net

Twitter: @supercristal1 / @mvpdays / #mvphour

Check out www.mvpdays.com to see where the MVPDays Roadshow will be next. Maybe it will be in a city near you.