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.
