# Verify Running as Admin
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
If (-not $isAdmin) {
Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1
if($PSVersionTable.PSEdition -eq "Core") {
Start-Process pwsh.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
} else {
Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
}
exit
}
#region Functions
#region Output logging
function WriteInfo($message) {
Write-Host $message
}
function WriteInfoHighlighted($message) {
Write-Host $message -ForegroundColor Cyan
}
function WriteSuccess($message) {
Write-Host $message -ForegroundColor Green
}
function WriteError($message) {
Write-Host $message -ForegroundColor Red
}
function WriteErrorAndExit($message) {
Write-Host $message -ForegroundColor Red
Write-Host "Press enter to continue ..."
Stop-Transcript
Read-Host | Out-Null
Exit
}
#endregion
#region Telemetry
Function Merge-Hashtables {
$Output = @{}
ForEach ($Hashtable in ($Input + $Args)) {
If ($Hashtable -is [Hashtable]) {
ForEach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key}
}
}
$Output
}
function Get-StringHash {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline, Mandatory = $true)]
[string]$String,
$Hash = "SHA1"
)
process {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($String)
$algorithm = [System.Security.Cryptography.HashAlgorithm]::Create($Hash)
$StringBuilder = New-Object System.Text.StringBuilder
$algorithm.ComputeHash($bytes) |
ForEach-Object {
$null = $StringBuilder.Append($_.ToString("x2"))
}
$StringBuilder.ToString()
}
}
function Get-VolumePhysicalDisk {
param(
[Parameter(Mandatory = $true)]
[string]$Volume
)
process {
if(-not $Volume.EndsWith(":")) {
$Volume += ":"
}
$physicalDisks = Get-cimInstance "win32_diskdrive"
foreach($disk in $physicalDisks) {
$partitions = Get-cimInstance -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID=`"$($disk.DeviceID.replace('\','\\'))`"} WHERE AssocClass = Win32_DiskDriveToDiskPartition"
foreach($partition in $partitions) {
$partitionVolumes = Get-cimInstance -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=`"$($partition.DeviceID)`"} WHERE AssocClass = Win32_LogicalDiskToPartition"
foreach($partitionVolume in $partitionVolumes) {
if($partitionVolume.Name -eq $Volume) {
$physicalDisk = Get-PhysicalDisk | Where-Object DeviceID -eq $disk.Index
return $physicalDisk
}
}
}
}
}
}
function Get-TelemetryLevel {
param(
[switch]$OptOut
)
process {
$acceptedTelemetryLevels = "None", "Basic", "Full"
# LabConfig value has a priority
if($LabConfig.TelemetryLevel -and $LabConfig.TelemetryLevel -in $acceptedTelemetryLevels) {
return $LabConfig.TelemetryLevel
}
# Environment variable as a fallback
if($env:WSLAB_TELEMETRY_LEVEL -and $env:WSLAB_TELEMETRY_LEVEL -in $acceptedTelemetryLevels) {
return $env:WSLAB_TELEMETRY_LEVEL
}
# If nothing is explicitely configured and OptOut flag enabled, explicitely disable telemetry
if($OptOut) {
return "None"
}
# as a last option return nothing to allow asking the user
}
}
function Get-TelemetryLevelSource {
param(
[switch]$OptOut
)
process {
$acceptedTelemetryLevels = "None", "Basic", "Full"
# Is it set interactively?
if($LabConfig.ContainsKey("TelemetryLevelSource")) {
return $LabConfig.TelemetryLevelSource
}
# LabConfig value has a priority
if($LabConfig.TelemetryLevel -and $LabConfig.TelemetryLevel -in $acceptedTelemetryLevels) {
return "LabConfig"
}
# Environment variable as a fallback
if($env:WSLAB_TELEMETRY_LEVEL -and $env:WSLAB_TELEMETRY_LEVEL -in $acceptedTelemetryLevels) {
return "Environment"
}
}
}
function Get-PcSystemType {
param(
[Parameter(Mandatory = $true)]
[int]$Id
)
process {
$type = switch($Id) {
1 { "Desktop" }
2 { "Laptop" }
3 { "Workstation" }
4 { "Server" }
7 { "Server" }
5 { "Server" }
default { $Id }
}
$type
}
}
$aiPropertyCache = @{}
function New-TelemetryEvent {
param(
[Parameter(Mandatory = $true)]
[string]$Event,
$Properties,
$Metrics,
$NickName
)
process {
if(-not $TelemetryInstrumentationKey) {
WriteInfo "Instrumentation key is required to send telemetry data."
return
}
$level = Get-TelemetryLevel
$levelSource = Get-TelemetryLevelSource
$r = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
$build = "$($r.CurrentMajorVersionNumber).$($r.CurrentMinorVersionNumber).$($r.CurrentBuildNumber).$($r.UBR)"
$osVersion = "$($r.ProductName) ($build)"
$hw = Get-CimInstance -ClassName Win32_ComputerSystem
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$machineHash = (((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography).MachineGuid) | Get-StringHash)
if(-not $NickName) {
$NickName = "?"
}
$osType = switch ($os.ProductType) {
1 { "Workstation" }
default { "Server" }
}
$extraMetrics = @{}
$extraProperties = @{
'telemetry.level' = $level
'telemetry.levelSource' = $levelSource
'telemetry.nick' = $NickName
'powershell.edition' = $PSVersionTable.PSEdition
'powershell.version' = $PSVersionTable.PSVersion.ToString()
'host.isAzure' = (Get-CimInstance win32_systemenclosure).SMBIOSAssetTag -eq "7783-7084-3265-9085-8269-3286-77"
'host.os.type' = $osType
'host.os.build' = $r.CurrentBuildNumber
'hw.type' = Get-PcSystemType -Id $hw.PCSystemType
}
if($level -eq "Full") {
# OS
$extraProperties.'device.locale' = (Get-WinsystemLocale).Name
# RAM
$extraMetrics.'memory.total' = [Math]::Round(($hw.TotalPhysicalMemory)/1024KB, 0)
# CPU
$extraMetrics.'cpu.logical.count' = $hw.NumberOfLogicalProcessors
$extraMetrics.'cpu.sockets.count' = $hw.NumberOfProcessors
if(-not $aiPropertyCache.ContainsKey("cpu.model")) {
$aiPropertyCache["cpu.model"] = (Get-CimInstance "Win32_Processor" | Select-Object -First 1).Name
}
$extraProperties.'cpu.model' = $aiPropertyCache["cpu.model"]
# Disk
$driveLetter = $ScriptRoot -Split ":" | Select-Object -First 1
$volume = Get-Volume -DriveLetter $driveLetter
$disk = Get-VolumePhysicalDisk -Volume $driveLetter
$extraMetrics.'volume.size' = [Math]::Round($volume.Size / 1024MB)
$extraProperties.'volume.fs' = $volume.FileSystemType
$extraProperties.'disk.type' = $disk.MediaType
$extraProperties.'disk.busType' = $disk.BusType
}
$payload = @{
name = "Microsoft.ApplicationInsights.Event"
time = $([System.dateTime]::UtcNow.ToString("o"))
iKey = $TelemetryInstrumentationKey
tags = @{
"ai.internal.sdkVersion" = 'wslab-telemetry:1.0.1'
"ai.application.ver" = $wslabVersion
"ai.cloud.role" = Split-Path -Path $PSCommandPath -Leaf
"ai.session.id" = $TelemetrySessionId
"ai.user.id" = $machineHash
"ai.device.id" = $machineHash
"ai.device.type" = $extraProperties["hw.type"]
"ai.device.locale" = "" # not propagated anymore
"ai.device.os" = ""
"ai.device.osVersion" = ""
"ai.device.oemName" = ""
"ai.device.model" = ""
}
data = @{
baseType = "EventData"
baseData = @{
ver = 2
name = $Event
properties = ($Properties, $extraProperties | Merge-Hashtables)
measurements = ($Metrics, $extraMetrics | Merge-Hashtables)
}
}
}
if($level -eq "Full") {
$payload.tags.'ai.device.os' = $osVersion
$payload.tags.'ai.device.osVersion' = $build
}
$payload
}
}
function Send-TelemetryObject {
param(
[Parameter(Mandatory = $true)]
[array]$Data
)
process {
$json = "{0}" -f (($Data) | ConvertTo-Json -Depth 10 -Compress)
if($LabConfig.ContainsKey('TelemetryDebugLog')) {
Add-Content -Path "$ScriptRoot\Telemetry.log" -Value ((Get-Date -Format "s") + "`n" + $json)
}
try {
$response = Invoke-RestMethod -Uri 'https://dc.services.visualstudio.com/v2/track' -Method Post -UseBasicParsing -Body $json -TimeoutSec 20
} catch {
WriteInfo "`tSending telemetry failed with an error: $($_.Exception.Message)"
$response = $_.Exception.Message
}
if($LabConfig.ContainsKey('TelemetryDebugLog')) {
Add-Content -Path "$ScriptRoot\Telemetry.log" -Value $response
Add-Content -Path "$ScriptRoot\Telemetry.log" -Value "`n------------------------------`n"
}
}
}
function Send-TelemetryEvent {
param(
[Parameter(Mandatory = $true)]
[string]$Event,
$Properties,
$Metrics,
$NickName
)
process {
$telemetryEvent = New-TelemetryEvent -Event $Event -Properties $Properties -Metrics $Metrics -NickName $NickName
Send-TelemetryObject -Data $telemetryEvent
}
}
function Send-TelemetryEvents {
param(
[Parameter(Mandatory = $true)]
[array]$Events
)
process {
Send-TelemetryObject -Data $Events
}
}
function Read-TelemetryLevel {
process {
# Ask user for consent
WriteInfoHighlighted "`nLab telemetry"
WriteInfo "By providing a telemetry information you will help us to improve WSLab scripts. There are two levels of a telemetry information and we are not collecting any personally identifiable information (PII)."
WriteInfo "Details about telemetry levels and the content of telemetry messages can be found in documentation https://aka.ms/wslab/telemetry"
WriteInfo "Available telemetry levels are:"
WriteInfo " * None -- No information will be sent"
WriteInfo " * Basic -- Information about lab will be sent (e.g. script execution time, number of VMs, guest OSes)"
WriteInfo " * Full -- Information about lab and the host machine (e.g. type of disk)"
WriteInfo "Would you be OK with providing an information about your WSLab usage?"
WriteInfo "`nTip: You can also configure telemetry settings explicitly in LabConfig.ps1 file or by setting an environmental variable and suppress this prompt."
$options = [System.Management.Automation.Host.ChoiceDescription[]] @(
<# 0 #> New-Object System.Management.Automation.Host.ChoiceDescription "&None", "No information will be sent"
<# 1 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Basic", "Lab info will be sent (e.g. script execution time, number of VMs)"
<# 2 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Full", "More details about the host machine and deployed VMs (e.g. guest OS)"
)
$response = $host.UI.PromptForChoice("WSLab telemetry level", "Please choose a telemetry level for this WSLab instance. For more details please see WSLab documentation.", $options, 1 <#default option#>)
$telemetryLevel = $null
switch($response) {
0 {
$telemetryLevel = 'None'
WriteInfo "`nNo telemetry information will be sent."
}
1 {
$telemetryLevel = 'Basic'
WriteInfo "`nTelemetry has been set to Basic level, thank you for your valuable feedback."
}
2 {
$telemetryLevel = 'Full'
WriteInfo "`nTelemetry has been set to Full level, thank you for your valuable feedback."
}
}
$telemetryLevel
}
}
# Instance values
$ScriptRoot = $PSScriptRoot
$wslabVersion = "v21.09.3"
$TelemetryEnabledLevels = "Basic", "Full"
$TelemetryInstrumentationKey = "9ebf64de-01f8-4f60-9942-079262e3f6e0"
$TelemetrySessionId = $ScriptRoot + $env:COMPUTERNAME + ((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography).MachineGuid) | Get-StringHash
#endregion
#Create Unattend for VHD
Function CreateUnattendFileVHD{
param (
[parameter(Mandatory=$true)]
[string]
$Computername,
[parameter(Mandatory=$true)]
[string]
$AdminPassword,
[parameter(Mandatory=$true)]
[string]
$Path,
[parameter(Mandatory=$true)]
[string]
$TimeZone
)
if ( Test-Path "$path\Unattend.xml" ) {
Remove-Item "$Path\Unattend.xml"
}
$unattendFile = New-Item "$Path\Unattend.xml" -type File
$fileContent = @"
1
$Computername
$oeminformation
PFE
Contoso
$AdminPassword
true
true
true
true
$TimeZone
"@
Set-Content -path $unattendFile -value $fileContent
#return the file object
Return $unattendFile
}
#endregion
#region Initialization
#Start Log
Start-Transcript -Path "$PSScriptRoot\CreateParentDisks.log"
$StartDateTime = Get-Date
WriteInfo "Script started at $StartDateTime"
WriteInfo "`nMSLab Version $mslabVersion"
#Load LabConfig....
. "$PSScriptRoot\LabConfig.ps1"
# Telemetry
if(-not (Get-TelemetryLevel)) {
$telemetryLevel = Read-TelemetryLevel
$LabConfig.TelemetryLevel = $telemetryLevel
$LabConfig.TelemetryLevelSource = "Prompt"
$promptShown = $true
}
if((Get-TelemetryLevel) -in $TelemetryEnabledLevels) {
if(-not $promptShown) {
WriteInfo "Telemetry is set to $(Get-TelemetryLevel) level from $(Get-TelemetryLevelSource)"
}
Send-TelemetryEvent -Event "CreateParentDisks.Start" -NickName $LabConfig.TelemetryNickName | Out-Null
}
#create variables if not already in LabConfig
If (!$LabConfig.DomainNetbiosName){
$LabConfig.DomainNetbiosName="Corp"
}
If (!$LabConfig.DomainName){
$LabConfig.DomainName="Corp.contoso.com"
}
If (!$LabConfig.DefaultOUName){
$LabConfig.DefaultOUName="Workshop"
}
If ($LabConfig.PullServerDC -eq $null){
$LabConfig.PullServerDC=$true
}
If (!$LabConfig.DHCPscope){
$LabConfig.DHCPscope="10.0.0.0"
}
#create some built-in variables
$DN=$null
$LabConfig.DomainName.Split(".") | ForEach-Object {
$DN+="DC=$_,"
}
$LabConfig.DN=$DN.TrimEnd(",")
$AdminPassword=$LabConfig.AdminPassword
$Switchname="DC_HydrationSwitch_$([guid]::NewGuid())"
$DCName='DC'
#Grab TimeZone
$TimeZone = (Get-TimeZone).id
#Grab Installation type
$WindowsInstallationType=Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' -Name InstallationType
#DCHP scope
$DHCPscope = $LabConfig.DHCPscope
$ReverseDNSrecord = $DHCPscope -replace '^(\d+)\.(\d+)\.\d+\.(\d+)$','$3.$2.$1.in-addr.arpa'
$DHCPscope = $DHCPscope.Substring(0,$DHCPscope.Length-1)
#endregion
#region Check prerequisites
#Check if not running in root folder
if (($PSScriptRoot).Length -eq 3) {
WriteErrorAndExit "`t MSLab canot run in root folder. Please put MSLab scripts into a folder. Exiting"
}
#check Hyper-V
WriteInfoHighlighted "Checking if Hyper-V is installed"
if ((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).state -eq "Enabled"){
WriteSuccess "`t Hyper-V is Installed"
}else{
WriteErrorAndExit "`t Hyper-V not installed. Please install hyper-v feature including Hyper-V management tools. Exiting"
}
WriteInfoHighlighted "Checking if Hyper-V Powershell module is installed"
if ((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell).state -eq "Enabled"){
WriteSuccess "`t Hyper-V is Installed"
}else{
WriteErrorAndExit "`t Hyper-V tools are not installed. Please install Hyper-V management tools. Exiting"
}
#check if VMM prereqs files are present if InstallSCVMM or SCVMM prereq is requested and tools.vhdx not present
if (-not (Get-ChildItem -Path "$PSScriptRoot\ParentDisks" -ErrorAction SilentlyContinue).name -contains "tools.vhdx"){
if ($LabConfig.InstallSCVMM -eq "Yes"){
"Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe","Temp\ToolsVHD\SCVMM\SCVMM\setup.exe","Temp\ToolsVHD\SCVMM\SQL\setup.exe","Temp\ToolsVHD\SCVMM\ADK\Installers\Windows PE x86 x64-x86_en-us.msi" | ForEach-Object {
if(!(Test-Path -Path "$PSScriptRoot\$_")){
WriteErrorAndExit "file $_ needed for SCVMM install not found. Exitting"
}
}
}
if ($LabConfig.InstallSCVMM -eq "Prereqs"){
"Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe","Temp\ToolsVHD\SCVMM\SQL\setup.exe","Temp\ToolsVHD\SCVMM\ADK\Installers\Windows PE x86 x64-x86_en-us.msi" | ForEach-Object {
if(!(Test-Path -Path "$PSScriptRoot\$_")){
WriteErrorAndExit "file $_ needed for SCVMM Prereqs install not found. Exitting"
}
}
}
if ($LabConfig.InstallSCVMM -eq "SQL"){
"Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe","Temp\ToolsVHD\SCVMM\SQL\setup.exe" | ForEach-Object {
if(!(Test-Path -Path "$PSScriptRoot\$_")){
WriteErrorAndExit "file $_ needed for SQL install not found. Exitting"
}
}
}
if ($LabConfig.InstallSCVMM -eq "ADK"){
"Temp\ToolsVHD\SCVMM\ADK\ADKsetup.exe" | ForEach-Object {
if(!(Test-Path -Path "$PSScriptRoot\$_")){
WriteErrorAndExit "file $_ needed for ADK install not found. Exitting"
}
}
}
}
#check if parent images already exist (this is useful if you have parent disks from another lab and you want to rebuild for example scvmm)
WriteInfoHighlighted "Testing if some parent disk already exists and can be used"
#grab all files in parentdisks folder
$ParentDisksNames=(Get-ChildItem -Path "$PSScriptRoot\ParentDisks" -ErrorAction SilentlyContinue).Name
#check if running on Core Server and check proper values in LabConfig
If ($WindowsInstallationType -eq "Server Core"){
If (!$LabConfig.ServerISOFolder){
WriteErrorAndExit "Server Core detected. Please use ServerISOFolder variable in LabConfig to specify Server iso location"
}
}
#Check if at least 2GB (+200Mb just to be sure) memory is available
WriteInfoHighlighted "Checking if at least 2GB RAM is available"
$MemoryAvailableMB=(Get-Ciminstance Win32_OperatingSystem).FreePhysicalMemory/1KB
if ($MemoryAvailableMB -gt (2048+200)){
WriteSuccess "`t $("{0:n0}" -f $MemoryAvailableMB) MB RAM Available"
}else{
WriteErrorAndExit "`t Please make sure you have at least 2 GB available memory. Exiting"
}
#check if filesystem on volume is NTFS or ReFS
WriteInfoHighlighted "Checking if volume filesystem is NTFS or ReFS"
$driveletter=$PSScriptRoot -split ":" | Select-Object -First 1
if ($PSScriptRoot -like "c:\ClusterStorage*"){
WriteSuccess "`t Volume Cluster Shared Volume. Mountdir will be $env:Temp\MSLabMountdir"
$mountdir="$env:Temp\MSLabMountdir"
$VolumeFileSystem="CSVFS"
}else{
$mountdir="$PSScriptRoot\Temp\MountDir"
$VolumeFileSystem=(Get-Volume -DriveLetter $driveletter).FileSystemType
if ($VolumeFileSystem -match "NTFS"){
WriteSuccess "`t Volume filesystem is $VolumeFileSystem"
}elseif ($VolumeFileSystem -match "ReFS") {
WriteSuccess "`t Volume filesystem is $VolumeFileSystem"
}else {
WriteErrorAndExit "`t Volume filesystem is $VolumeFileSystem. Must be NTFS or ReFS. Exiting"
}
}
#endregion
#region Ask for ISO images and Cumulative updates
#Grab Server ISO
if ($LabConfig.ServerISOFolder){
$ServerISOItem = Get-ChildItem -Path $LabConfig.ServerISOFolder -Recurse -Include '*.iso' -ErrorAction SilentlyContinue
if ($ServerISOItem.count -gt 1){
WriteInfoHighlighted "Multiple ISO files found. Please select Server ISO one you want"
$ServerISOItem=$ServerISOItem | Select-Object Name,FullName | Out-GridView -Title "Multiple ISO files found. Please select Server ISO you want" -OutputMode Single
}
if (!$ServerISOItem){
WriteErrorAndExit "No iso was found in $($LabConfig.ServerISOFolder) ... Exitting"
}
$ISOServer = Mount-DiskImage -ImagePath $ServerISOItem.FullName -PassThru
}else{
WriteInfoHighlighted "Please select ISO image with Windows Server 2016, 2019 or Server Insider"
[reflection.assembly]::loadwithpartialname("System.Windows.Forms")
$openFile = New-Object System.Windows.Forms.OpenFileDialog -Property @{
Title="Please select ISO image with Windows Server 2016, 2019 or Server Insider"
}
$openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*"
If($openFile.ShowDialog() -eq "OK"){
WriteInfo "File $($openfile.FileName) selected"
}
if (!$openFile.FileName){
WriteErrorAndExit "Iso was not selected... Exitting"
}
#Mount ISO
$ISOServer = Mount-DiskImage -ImagePath $openFile.FileName -PassThru
}
#Grab Server Media Letter
$ServerMediaDriveLetter = (Get-Volume -DiskImage $ISOServer).DriveLetter
#Test if it's server media
WriteInfoHighlighted "Testing if selected ISO is Server Media"
$WindowsImage=Get-WindowsImage -ImagePath "$($ServerMediaDriveLetter):\sources\install.wim"
If ($WindowsImage.ImageName[0].contains("Server")){
WriteInfo "`t Server Edition found"
}else{
$ISOServer | Dismount-DiskImage
WriteErrorAndExit "`t Selected media does not contain Windows Server. Exitting."
}
if ($WindowsImage.ImageName[0].contains("Server") -and $windowsimage.count -eq 2){
WriteInfo "`t Semi-Annual Server Media detected"
$ISOServer | Dismount-DiskImage
WriteErrorAndExit "Please provide LTSC media. Exitting."
}
#Test if it's Windows Server 2016 and newer
$BuildNumber=(Get-ItemProperty -Path "$($ServerMediaDriveLetter):\setup.exe").versioninfo.FileBuildPart
If ($BuildNumber -lt 14393){
$ISOServer | Dismount-DiskImage
WriteErrorAndExit "Please provide Windows Server 2016 and newer. Exitting."
}
#Check ISO Language
$imageInfo=(Get-WindowsImage -ImagePath "$($ServerMediaDriveLetter):\sources\install.wim" -Index 4)
$OSLanguage=$imageInfo.Languages | Select-Object -First 1
#Grab packages
#grab server packages
if ($LabConfig.ServerISOFolder){
if ($LabConfig.ServerMSUsFolder){
$packages = (Get-ChildItem -Path $LabConfig.ServerMSUsFolder -Recurse -Include '*.msu' -ErrorAction SilentlyContinue | Sort-Object -Property Length).FullName
}
}elseif($WindowsInstallationType -eq "Server Core"){
WriteInfoHighlighted "Server Core detected, MSU folder not specified. Skipping MSU prompt"
}else{
#ask for MSU patches
WriteInfoHighlighted "Please select Windows Server Updates (*.msu). Click Cancel if you don't want any."
[reflection.assembly]::loadwithpartialname("System.Windows.Forms")
$msupackages = New-Object System.Windows.Forms.OpenFileDialog -Property @{
Multiselect = $true;
Title = "Please select Windows Server Updates (*.msu). Click Cancel if you don't want any."
}
$msupackages.Filter = "msu files (*.msu)|*.msu|All files (*.*)|*.*"
If($msupackages.ShowDialog() -eq "OK"){
WriteInfoHighlighted "Following patches selected:"
WriteInfo "`t $($msupackages.filenames)"
}
$files=@()
foreach ($Filename in $msupackages.filenames){$files+=Get-ChildItem -Path $filename}
#sort by size (to apply Servicing Stack Update first)
$packages=($files |Sort-Object -Property Length).Fullname
}
#endregion
#region Generate VHD Config
$ServerVHDs=@()
if ($BuildNumber -eq 14393){
#Windows Server 2016
$ServerVHDs += @{
Kind = "Full"
Edition="4"
VHDName="Win2016_G2.vhdx"
Size=60GB
}
$ServerVHDs += @{
Kind = "Core"
Edition="3"
VHDName="Win2016Core_G2.vhdx"
Size=30GB
}
<# Removed since it does not work with newer than 14393.2724
$ServerVHDs += @{
Edition="DataCenterNano"
VHDName="Win2016NanoHV_G2.vhdx"
NanoPackages="Microsoft-NanoServer-DSC-Package","Microsoft-NanoServer-FailoverCluster-Package","Microsoft-NanoServer-Guest-Package","Microsoft-NanoServer-Storage-Package","Microsoft-NanoServer-SCVMM-Package","Microsoft-NanoServer-Compute-Package","Microsoft-NanoServer-SCVMM-Compute-Package","Microsoft-NanoServer-SecureStartup-Package","Microsoft-NanoServer-DCB-Package","Microsoft-NanoServer-ShieldedVM-Package"
Size=30GB
}
#>
}elseif ($BuildNumber -eq 17763){
#Windows Server 2019
$ServerVHDs += @{
Kind = "Full"
Edition="4"
VHDName="Win2019_G2.vhdx"
Size=60GB
}
$ServerVHDs += @{
Kind = "Core"
Edition="3"
VHDName="Win2019Core_G2.vhdx"
Size=30GB
}
}elseif ($BuildNumber -eq 20348){
#Windows Server 2022
$ServerVHDs += @{
Kind = "Full"
Edition="4"
VHDName="Win2022_G2.vhdx"
Size=60GB
}
$ServerVHDs += @{
Kind = "Core"
Edition="3"
VHDName="Win2022Core_G2.vhdx"
Size=30GB
}
}elseif ($BuildNumber -ge 17744 -and $SAC){
$ServerVHDs += @{
Kind = "Core"
Edition="2"
VHDName="WinSrvInsiderCore_$BuildNumber.vhdx"
Size=30GB
}
#DCEdition fix
if ($LabConfig.DCEdition -gt 2){
$LabConfig.DCEdition=2
}
}elseif ($BuildNumber -ge 17744){
#Windows Sever Insider
$ServerVHDs += @{
Kind = "Full"
Edition="4"
VHDName="WinSrvInsider_$BuildNumber.vhdx"
Size=60GB
}
$ServerVHDs += @{
Kind = "Core"
Edition="3"
VHDName="WinSrvInsiderCore_$BuildNumber.vhdx"
Size=30GB
}
}else{
$ISOServer | Dismount-DiskImage
WriteErrorAndExit "Plese provide Windows Server 2016, 2019 or Insider greater or equal to build 17744"
}
#Test if Tools.vhdx already exists
if ($ParentDisksNames -contains "tools.vhdx"){
WriteSuccess "`t Tools.vhdx already exists. Creation will be skipped"
}else{
WriteInfo "`t Tools.vhdx not found, will be created"
}
#check if DC exists
if (Get-ChildItem -Path "$PSScriptRoot\LAB\DC\" -Recurse -ErrorAction SilentlyContinue){
$DCFilesExists=$true
WriteInfoHighlighted "Files found in $PSScriptRoot\LAB\DC\. DC Creation will be skipped"
}else{
$DCFilesExists=$false
}
#endregion
#region Create parent disks
#create some folders
'ParentDisks','Temp','Temp\mountdir' | ForEach-Object {
if (!( Test-Path "$PSScriptRoot\$_" )) {
WriteInfoHighlighted "Creating Directory $_"
New-Item -Type Directory -Path "$PSScriptRoot\$_"
}
}
#load Convert-WindowsImage to memory
. "$PSScriptRoot\Temp\Convert-WindowsImage.ps1"
#Create Servers Parent VHDs
WriteInfoHighlighted "Creating Server Parent disk(s)"
$vhdStatusInfo = @{}
foreach ($ServerVHD in $ServerVHDs){
$vhdStatus = @{
Kind = $ServerVHD.Kind
Name = $ServerVHD.VHDName
AlreadyExists = $false
BuildStartDate = Get-Date
}
if ($serverVHD.Edition -notlike "*nano"){
if (!(Test-Path "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)")){
WriteInfo "`t Creating Server Parent $($ServerVHD.VHDName)"
#exit if server wim not found
If (!(Test-Path -Path "$($ServerMediaDriveLetter):\sources\install.wim")){
WriteInfo "`t Dismounting ISO Images"
if ($ISOServer -ne $Null){
$ISOServer | Dismount-DiskImage
}
if ($ISOClient -ne $Null){
$ISOClient | Dismount-DiskImage
}
WriteErrorAndExit "$($ServerMediaDriveLetter):\sources\install.wim not found. Can you try different Server media?"
}
if ($packages){
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $serverVHD.Edition -VHDPath "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)" -SizeBytes $serverVHD.Size -VHDFormat VHDX -DiskLayout UEFI -Package $packages
}else{
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $serverVHD.Edition -VHDPath "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)" -SizeBytes $serverVHD.Size -VHDFormat VHDX -DiskLayout UEFI
}
}else{
$vhdStatus.AlreadyExists = $true
WriteSuccess "`t Server Parent $($ServerVHD.VHDName) found, skipping creation"
}
}
if ($serverVHD.Edition -like "*nano"){
if (!(Test-Path "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)")){
#grab Nano packages
$NanoPackages=@()
foreach ($NanoPackage in $serverVHD.NanoPackages){
$NanoPackages+=(Get-ChildItem -Path "$($ServerMediaDriveLetter):\NanoServer\" -Recurse | Where-Object Name -like $NanoPackage*).FullName
}
#create parent disks
WriteInfo "`t Creating Server Parent $($ServerVHD.VHDName)"
if ($packages){
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\NanoServer\NanoServer.wim" -Edition $serverVHD.Edition -VHDPath "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)" -SizeBytes $serverVHD.Size -VHDFormat VHDX -DiskLayout UEFI -Package ($NanoPackages+$packages)
}else{
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\NanoServer\NanoServer.wim" -Edition $serverVHD.Edition -VHDPath "$PSScriptRoot\ParentDisks\$($ServerVHD.VHDName)" -SizeBytes $serverVHD.Size -VHDFormat VHDX -DiskLayout UEFI -Package $NanoPackages
}
}else{
WriteSuccess "`t Server Parent $($ServerVHD.VHDName) found, skipping creation"
}
}
$vhdStatus.BuildEndDate = Get-Date
$vhdStatusInfo[$vhdStatus.Kind] = $vhdStatus
}
#create Tools VHDX from .\Temp\ToolsVHD
$toolsVhdStatus = @{
Kind = "Tools"
Name = "tools.vhdx"
AlreadyExists = $false
BuildStartDate = Get-Date
}
if (!(Test-Path "$PSScriptRoot\ParentDisks\tools.vhdx")){
WriteInfoHighlighted "Creating Tools.vhdx"
$toolsVHD=New-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx" -SizeBytes 30GB -Dynamic
#mount and format VHD
$VHDMount = Mount-VHD $toolsVHD.Path -Passthru
$vhddisk = $VHDMount| get-disk
$vhddiskpart = $vhddisk | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -UseMaximumSize -AssignDriveLetter |Format-Volume -FileSystem NTFS -AllocationUnitSize 8kb -NewFileSystemLabel ToolsDisk
$VHDPathTest=Test-Path -Path "$PSScriptRoot\Temp\ToolsVHD\"
if (!$VHDPathTest){
New-Item -Type Directory -Path "$PSScriptRoot\Temp\ToolsVHD"
}
if ($VHDPathTest){
WriteInfo "Found $PSScriptRoot\Temp\ToolsVHD\*, copying files into VHDX"
Copy-Item -Path "$PSScriptRoot\Temp\ToolsVHD\*" -Destination "$($vhddiskpart.DriveLetter):\" -Recurse -Force
}else{
WriteInfo "Files not found"
WriteInfoHighlighted "Add required tools into $PSScriptRoot\Temp\ToolsVHD and Press any key to continue..."
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL
Copy-Item -Path "$PSScriptRoot\Temp\ToolsVHD\*" -Destination ($vhddiskpart.DriveLetter+':\') -Recurse -Force
}
Dismount-VHD $vhddisk.Number
$toolsVhdStatus.BuildEndDate = Get-Date
}else{
$toolsVhdStatus.AlreadyExists = $true
WriteSuccess "`t Tools.vhdx found in Parent Disks, skipping creation"
$toolsVHD = Get-VHD -Path "$PSScriptRoot\ParentDisks\tools.vhdx"
}
$vhdStatusInfo[$toolsVhdStatus.Kind] = $toolsVhdStatus
#endregion
#region Hydrate DC
if (-not $DCFilesExists){
WriteInfoHighlighted "Starting DC Hydration"
$dcHydrationStartTime = Get-Date
$vhdpath="$PSScriptRoot\LAB\$DCName\Virtual Hard Disks\$DCName.vhdx"
$VMPath="$PSScriptRoot\LAB\"
#reuse VHD if already created
$DCVHDName=($ServerVHDs | Where-Object Edition -eq $LabConfig.DCEdition).VHDName
if ((($DCVHDName) -ne $null) -and (Test-Path -Path "$PSScriptRoot\ParentDisks\$DCVHDName")){
WriteSuccess "`t $DCVHDName found, reusing, and copying to $vhdpath"
New-Item -Path "$VMPath\$DCName" -Name "Virtual Hard Disks" -ItemType Directory
Copy-Item -Path "$PSScriptRoot\ParentDisks\$DCVHDName" -Destination $vhdpath
}else{
#Create Parent VHD
WriteInfoHighlighted "`t Creating VHD for DC"
if ($packages){
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $LabConfig.DCEdition -VHDPath $vhdpath -SizeBytes 60GB -VHDFormat VHDX -DiskLayout UEFI -package $packages
}else{
Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $LabConfig.DCEdition -VHDPath $vhdpath -SizeBytes 60GB -VHDFormat VHDX -DiskLayout UEFI
}
}
#Get VM Version
[System.Version]$VMVersion=(Get-WindowsImage -ImagePath $VHDPath -Index 1).Version
WriteInfo "`t VM Version is $($VMVersion.Build).$($VMVersion.Revision)"
#If the switch does not already exist, then create a switch with the name $SwitchName
if (-not [bool](Get-VMSwitch -Name $Switchname -ErrorAction SilentlyContinue)) {
WriteInfoHighlighted "`t Creating temp hydration switch $Switchname"
New-VMSwitch -SwitchType Private -Name $Switchname
}
#create VM DC
WriteInfoHighlighted "`t Creating DC VM"
if ($LabConfig.DCVMVersion){
$DC=New-VM -Name $DCName -VHDPath $vhdpath -MemoryStartupBytes 2GB -path $vmpath -SwitchName $Switchname -Generation 2 -Version $LabConfig.DCVMVersion
}else{
$DC=New-VM -Name $DCName -VHDPath $vhdpath -MemoryStartupBytes 2GB -path $vmpath -SwitchName $Switchname -Generation 2
}
$DC | Set-VMProcessor -Count 2
$DC | Set-VMMemory -DynamicMemoryEnabled $true -MinimumBytes 2GB
if ($LabConfig.Secureboot -eq $False) {$DC | Set-VMFirmware -EnableSecureBoot Off}
if ($DC.AutomaticCheckpointsEnabled -eq $True){
$DC | Set-VM -AutomaticCheckpointsEnabled $False
}
#Apply Unattend to VM
if ($VMVersion.Build -ge 17763){
$oeminformation=@"
MSLab
https://aka.ms/mslab
"@
}else{
$oeminformation=$null
}
WriteInfoHighlighted "`t Applying Unattend and copying Powershell DSC Modules"
if (Test-Path $mountdir){
Remove-Item -Path $mountdir -Recurse -Force
}
if (Test-Path "$PSScriptRoot\Temp\unattend"){
Remove-Item -Path "$PSScriptRoot\Temp\unattend.xml"
}
$unattendfile=CreateUnattendFileVHD -Computername $DCName -AdminPassword $AdminPassword -path "$PSScriptRoot\temp\" -TimeZone $TimeZone
New-item -type directory -Path $mountdir -force
[System.Version]$VMVersion=(Get-WindowsImage -ImagePath $VHDPath -Index 1).Version
Mount-WindowsImage -Path $mountdir -ImagePath $VHDPath -Index 1
Use-WindowsUnattend -Path $mountdir -UnattendPath $unattendFile
#&"$PSScriptRoot\Temp\dism\dism" /mount-image /imagefile:$vhdpath /index:1 /MountDir:$mountdir
#&"$PSScriptRoot\Temp\dism\dism" /image:$mountdir /Apply-Unattend:$unattendfile
New-item -type directory -Path "$mountdir\Windows\Panther" -force
Copy-Item -Path $unattendfile -Destination "$mountdir\Windows\Panther\unattend.xml" -force
Copy-Item -Path "$PSScriptRoot\Temp\DSC\*" -Destination "$mountdir\Program Files\WindowsPowerShell\Modules\" -Recurse -force
#Create credentials for DSC
$username = "$($LabConfig.DomainNetbiosName)\Administrator"
$password = $AdminPassword
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
#Create DSC configuration
configuration DCHydration
{
param
(
[Parameter(Mandatory)]
[pscredential]$safemodeAdministratorCred,
[Parameter(Mandatory)]
[pscredential]$domainCred,
[Parameter(Mandatory)]
[pscredential]$NewADUserCred
)
Import-DscResource -ModuleName xActiveDirectory -ModuleVersion "3.0.0.0"
Import-DscResource -ModuleName xDNSServer -ModuleVersion "1.15.0.0"
Import-DSCResource -ModuleName NetworkingDSC -ModuleVersion "7.4.0.0"
Import-DSCResource -ModuleName xDHCPServer -ModuleVersion "2.0.0.0"
Import-DSCResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "8.10.0.0"
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename
{
WindowsFeature ADDSInstall
{
Ensure = "Present"
Name = "AD-Domain-Services"
}
WindowsFeature FeatureGPMC
{
Ensure = "Present"
Name = "GPMC"
DependsOn = "[WindowsFeature]ADDSInstall"
}
WindowsFeature FeatureADPowerShell
{
Ensure = "Present"
Name = "RSAT-AD-PowerShell"
DependsOn = "[WindowsFeature]ADDSInstall"
}
WindowsFeature FeatureADAdminCenter
{
Ensure = "Present"
Name = "RSAT-AD-AdminCenter"
DependsOn = "[WindowsFeature]ADDSInstall"
}
WindowsFeature FeatureADDSTools
{
Ensure = "Present"
Name = "RSAT-ADDS-Tools"
DependsOn = "[WindowsFeature]ADDSInstall"
}
WindowsFeature FeatureDNSTools
{
Ensure = "Present"
Name = "RSAT-DNS-Server"
DependsOn = "[WindowsFeature]ADDSInstall"
}
xADDomain FirstDS
{
DomainName = $Node.DomainName
DomainAdministratorCredential = $domainCred
SafemodeAdministratorPassword = $safemodeAdministratorCred
DomainNetbiosName = $node.DomainNetbiosName
DependsOn = "[WindowsFeature]ADDSInstall"
}
xWaitForADDomain DscForestWait
{
DomainName = $Node.DomainName
DomainUserCredential = $domainCred
RetryCount = $Node.RetryCount
RetryIntervalSec = $Node.RetryIntervalSec
DependsOn = "[xADDomain]FirstDS"
}
xADOrganizationalUnit DefaultOU
{
Name = $Node.DefaultOUName
Path = $Node.DomainDN
ProtectedFromAccidentalDeletion = $true
Description = 'Default OU for all user and computer accounts'
Ensure = 'Present'
DependsOn = "[xADDomain]FirstDS"
}
xADUser SQL_SA
{
DomainName = $Node.DomainName
DomainAdministratorCredential = $domainCred
UserName = "SQL_SA"
Password = $NewADUserCred
Ensure = "Present"
DependsOn = "[xADOrganizationalUnit]DefaultOU"
Description = "SQL Service Account"
Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)"
PasswordNeverExpires = $true
}
xADUser SQL_Agent
{
DomainName = $Node.DomainName
DomainAdministratorCredential = $domainCred
UserName = "SQL_Agent"
Password = $NewADUserCred
Ensure = "Present"
DependsOn = "[xADOrganizationalUnit]DefaultOU"
Description = "SQL Agent Account"
Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)"
PasswordNeverExpires = $true
}
xADUser Domain_Admin
{
DomainName = $Node.DomainName
DomainAdministratorCredential = $domainCred
UserName = $Node.DomainAdminName
Password = $NewADUserCred
Ensure = "Present"
DependsOn = "[xADOrganizationalUnit]DefaultOU"
Description = "DomainAdmin"
Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)"
PasswordNeverExpires = $true
}
xADUser VMM_SA
{
DomainName = $Node.DomainName
DomainAdministratorCredential = $domainCred
UserName = "VMM_SA"
Password = $NewADUserCred
Ensure = "Present"
DependsOn = "[xADUser]Domain_Admin"
Description = "VMM Service Account"
Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)"
PasswordNeverExpires = $true
}
xADGroup DomainAdmins
{
GroupName = "Domain Admins"
DependsOn = "[xADUser]VMM_SA"
MembersToInclude = "VMM_SA",$Node.DomainAdminName
}
xADGroup SchemaAdmins
{
GroupName = "Schema Admins"
GroupScope = "Universal"
DependsOn = "[xADUser]VMM_SA"
MembersToInclude = $Node.DomainAdminName
}
xADGroup EntAdmins
{
GroupName = "Enterprise Admins"
GroupScope = "Universal"
DependsOn = "[xADUser]VMM_SA"
MembersToInclude = $Node.DomainAdminName
}
xADUser AdministratorNeverExpires
{
DomainName = $Node.DomainName
UserName = "Administrator"
Ensure = "Present"
DependsOn = "[xADDomain]FirstDS"
PasswordNeverExpires = $true
}
IPaddress IP
{
IPAddress = ($DHCPscope+"1/24")
AddressFamily = 'IPv4'
InterfaceAlias = 'Ethernet'
}
WindowsFeature DHCPServer
{
Ensure = "Present"
Name = "DHCP"
DependsOn = "[xADDomain]FirstDS"
}
Service DHCPServer #since insider 17035 dhcpserver was not starting for some reason
{
Name = "DHCPServer"
State = "Running"
DependsOn = "[WindowsFeature]DHCPServer"
}
WindowsFeature DHCPServerManagement
{
Ensure = "Present"
Name = "RSAT-DHCP"
DependsOn = "[WindowsFeature]DHCPServer"
}
xDhcpServerScope ManagementScope
{
Ensure = 'Present'
ScopeId = ($DHCPscope+"0")
IPStartRange = ($DHCPscope+"10")
IPEndRange = ($DHCPscope+"254")
Name = 'ManagementScope'
SubnetMask = '255.255.255.0'
LeaseDuration = '00:08:00'
State = 'Active'
AddressFamily = 'IPv4'
DependsOn = "[Service]DHCPServer"
}
xDhcpServerOption MgmtScopeRouterOption
{
Ensure = 'Present'
ScopeID = ($DHCPscope+"0")
DnsDomain = $Node.DomainName
DnsServerIPAddress = ($DHCPscope+"1")
AddressFamily = 'IPv4'
Router = ($DHCPscope+"1")
DependsOn = "[Service]DHCPServer"
}
xDhcpServerAuthorization LocalServerActivation
{
Ensure = 'Present'
}
WindowsFeature DSCServiceFeature
{
Ensure = "Present"
Name = "DSC-Service"
}
xDnsServerADZone addReverseADZone
{
Name = $ReverseDNSrecord
DynamicUpdate = "Secure"
ReplicationScope = "Forest"
Ensure = "Present"
DependsOn = "[xDhcpServerOption]MgmtScopeRouterOption"
}
If ($LabConfig.PullServerDC){
xDscWebService PSDSCPullServer
{
UseSecurityBestPractices = $false
Ensure = "Present"
EndpointName = "PSDSCPullServer"
Port = 8080
PhysicalPath = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer"
CertificateThumbPrint = "AllowUnencryptedTraffic"
ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
State = "Started"
DependsOn = "[WindowsFeature]DSCServiceFeature"
}
File RegistrationKeyFile
{
Ensure = 'Present'
Type = 'File'
DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
Contents = $Node.RegistrationKey
}
}
}
}
$ConfigData = @{
AllNodes = @(
@{
Nodename = $DCName
Role = "Parent DC"
DomainAdminName=$LabConfig.DomainAdminName
DomainName = $LabConfig.DomainName
DomainNetbiosName = $LabConfig.DomainNetbiosName
DomainDN = $LabConfig.DN
DefaultOUName=$LabConfig.DefaultOUName
RegistrationKey='14fc8e72-5036-4e79-9f89-5382160053aa'
PSDscAllowPlainTextPassword = $true
PsDscAllowDomainUser= $true
RetryCount = 50
RetryIntervalSec = 30
}
)
}
#create LCM config
[DSCLocalConfigurationManager()]
configuration LCMConfig
{
Node DC
{
Settings
{
RebootNodeIfNeeded = $true
ActionAfterReboot = 'ContinueConfiguration'
}
}
}
#create DSC MOF files
WriteInfoHighlighted "`t Creating DSC Configs for DC"
LCMConfig -OutputPath "$PSScriptRoot\Temp\config" -ConfigurationData $ConfigData
DCHydration -OutputPath "$PSScriptRoot\Temp\config" -ConfigurationData $ConfigData -safemodeAdministratorCred $cred -domainCred $cred -NewADUserCred $cred
#copy DSC MOF files to DC
WriteInfoHighlighted "`t Copying DSC configurations (pending.mof and metaconfig.mof)"
New-item -type directory -Path "$PSScriptRoot\Temp\config" -ErrorAction Ignore
Copy-Item -path "$PSScriptRoot\Temp\config\dc.mof" -Destination "$mountdir\Windows\system32\Configuration\pending.mof"
Copy-Item -Path "$PSScriptRoot\Temp\config\dc.meta.mof" -Destination "$mountdir\Windows\system32\Configuration\metaconfig.mof"
#close VHD and apply changes
WriteInfoHighlighted "`t Applying changes to VHD"
Dismount-WindowsImage -Path $mountdir -Save
#&"$PSScriptRoot\Temp\dism\dism" /Unmount-Image /MountDir:$mountdir /Commit
#Start DC VM and wait for configuration
WriteInfoHighlighted "`t Starting DC"
$DC | Start-VM
$VMStartupTime = 250
WriteInfoHighlighted "`t Configuring DC using DSC takes a while."
WriteInfo "`t `t Initial configuration in progress. Sleeping $VMStartupTime seconds"
Start-Sleep $VMStartupTime
$i=1
do{
$test=Invoke-Command -VMGuid $DC.id -ScriptBlock {Get-DscConfigurationStatus} -Credential $cred -ErrorAction SilentlyContinue
if ($test -eq $null) {
WriteInfo "`t `t Configuration in Progress. Sleeping 10 seconds"
Start-Sleep 10
}elseif ($test.status -ne "Success" -and $i -eq 1) {
WriteInfo "`t `t Current DSC state: $($test.status), ResourncesNotInDesiredState: $($test.resourcesNotInDesiredState.count), ResourncesInDesiredState: $($test.resourcesInDesiredState.count)."
WriteInfoHighlighted "`t `t Invoking DSC Configuration again"
Invoke-Command -VMGuid $DC.id -ScriptBlock {Start-DscConfiguration -UseExisting} -Credential $cred
$i++
}elseif ($test.status -ne "Success" -and $i -gt 1) {
WriteInfo "`t `t Current DSC state: $($test.status), ResourncesNotInDesiredState: $($test.resourcesNotInDesiredState.count), ResourncesInDesiredState: $($test.resourcesInDesiredState.count)."
WriteInfoHighlighted "`t `t Restarting DC"
Invoke-Command -VMGuid $DC.id -ScriptBlock {Restart-Computer} -Credential $cred
}elseif ($test.status -eq "Success" ) {
WriteInfo "`t `t Current DSC state: $($test.status), ResourncesNotInDesiredState: $($test.resourcesNotInDesiredState.count), ResourncesInDesiredState: $($test.resourcesInDesiredState.count)."
WriteInfoHighlighted "`t `t DSC Configured DC Successfully"
}
}until ($test.Status -eq 'Success' -and $test.rebootrequested -eq $false)
$test
#configure default OU where new Machines will be created using redircmp and add reverse lookup zone (as setting reverse lookup does not work with DSC)
Invoke-Command -VMGuid $DC.id -Credential $cred -ErrorAction SilentlyContinue -ArgumentList $LabConfig -ScriptBlock {
Param($LabConfig);
redircmp "OU=$($LabConfig.DefaultOUName),$($LabConfig.DN)"
Add-DnsServerPrimaryZone -NetworkID ($DHCPscope+"/24") -ReplicationScope "Forest"
}
#install SCVMM or its prereqs if specified so
if (($LabConfig.InstallSCVMM -eq "Yes") -or ($LabConfig.InstallSCVMM -eq "SQL") -or ($LabConfig.InstallSCVMM -eq "ADK") -or ($LabConfig.InstallSCVMM -eq "Prereqs")){
$DC | Add-VMHardDiskDrive -Path $toolsVHD.Path
}
if ($LabConfig.InstallSCVMM -eq "Yes"){
WriteInfoHighlighted "Installing System Center Virtual Machine Manager and its prerequisites"
Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
d:\scvmm\1_SQL_Install.ps1
d:\scvmm\2_ADK_Install.ps1
Restart-Computer
}
Start-Sleep 10
WriteInfoHighlighted "$($DC.name) was restarted, waiting for Active Directory on $($DC.name) to be started."
do{
$test=Invoke-Command -VMGuid $DC.id -Credential $cred -ArgumentList $LabConfig -ErrorAction SilentlyContinue -ScriptBlock {
param($LabConfig);
Get-ADComputer -Filter * -SearchBase "$($LabConfig.DN)" -ErrorAction SilentlyContinue}
Start-Sleep 5
}
until ($test -ne $Null)
WriteSuccess "Active Directory on $($DC.name) is up."
Start-Sleep 30 #Wait as sometimes VMM failed to install without this.
Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
d:\scvmm\3_SCVMM_Install.ps1
}
}
if ($LabConfig.InstallSCVMM -eq "SQL"){
WriteInfoHighlighted "Installing SQL"
Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
d:\scvmm\1_SQL_Install.ps1
}
}
if ($LabConfig.InstallSCVMM -eq "ADK"){
WriteInfoHighlighted "Installing ADK"
Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
d:\scvmm\2_ADK_Install.ps1
}
}
if ($LabConfig.InstallSCVMM -eq "Prereqs"){
WriteInfoHighlighted "Installing System Center VMM Prereqs"
Invoke-Command -VMGuid $DC.id -Credential $cred -ScriptBlock {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force
d:\scvmm\1_SQL_Install.ps1
d:\scvmm\2_ADK_Install.ps1
}
}
if (($LabConfig.InstallSCVMM -eq "Yes") -or ($LabConfig.InstallSCVMM -eq "SQL") -or ($LabConfig.InstallSCVMM -eq "ADK") -or ($LabConfig.InstallSCVMM -eq "Prereqs")){
$DC | Get-VMHardDiskDrive | Where-Object path -eq $toolsVHD.Path | Remove-VMHardDiskDrive
}
$dcHydrationEndTime = Get-Date
}
#endregion
#region backup DC and cleanup
#cleanup DC
if (-not $DCFilesExists){
WriteInfoHighlighted "Backup DC and cleanup"
#shutdown DC
WriteInfo "`t Disconnecting VMNetwork Adapter from DC"
$DC | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter
WriteInfo "`t Shutting down DC"
$DC | Stop-VM
$DC | Set-VM -MemoryMinimumBytes 512MB
#Backup DC config, remove from Hyper-V, return DC config
WriteInfo "`t Creating backup of DC VM configuration"
Copy-Item -Path "$vmpath\$DCName\Virtual Machines\" -Destination "$vmpath\$DCName\Virtual Machines_Bak\" -Recurse
WriteInfo "`t Removing DC"
$DC | Remove-VM -Force
WriteInfo "`t Returning VM config and adding to Virtual Machines.zip"
Remove-Item -Path "$vmpath\$DCName\Virtual Machines\" -Recurse
Rename-Item -Path "$vmpath\$DCName\Virtual Machines_Bak\" -NewName 'Virtual Machines'
Compress-Archive -Path "$vmpath\$DCName\Virtual Machines\" -DestinationPath "$vmpath\$DCName\Virtual Machines.zip"
#cleanup vswitch
WriteInfo "`t Removing switch $Switchname"
Remove-VMSwitch -Name $Switchname -Force -ErrorAction SilentlyContinue
}
#Cleanup The rest
WriteInfo "`t Dismounting ISO Images"
if ($ISOServer -ne $Null){
$ISOServer | Dismount-DiskImage
}
#endregion
#region finishing
WriteSuccess "Script finished at $(Get-date) and took $(((get-date) - $StartDateTime).TotalMinutes) Minutes"
$options = [System.Management.Automation.Host.ChoiceDescription[]] @(
<# 0 #> New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Cleanup .\Temp\ 1_Prereq.ps1 2_CreateParentDisks.ps1 and rename 3_deploy.ps1 to just deploy.ps1"
<# 1 #> New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Keep files (in case DC was not created sucessfully)"
)
$response = $host.UI.PromptForChoice("Unnecessary files cleanup","Do you want to cleanup unnecessary files and folders?", $options, 0 <#default option#>)
If ($response -eq 1){
$renamed = $false
WriteInfo "Skipping cleanup"
}else{
$renamed = $true
WriteInfo "`t `t Cleaning unnecessary items"
Remove-Item -Path "$PSScriptRoot\temp" -Force -Recurse
"$PSScriptRoot\Temp","$PSScriptRoot\1_Prereq.ps1","$PSScriptRoot\2_CreateParentDisks.ps1" | ForEach-Object {
WriteInfo "`t `t `t Removing $_"
Remove-Item -Path $_ -Force -Recurse -ErrorAction SilentlyContinue
}
WriteInfo "`t `t `t Renaming $PSScriptRoot\3_Deploy.ps1 to Deploy.ps1"
Rename-Item -Path "$PSScriptRoot\3_Deploy.ps1" -NewName "Deploy.ps1" -ErrorAction SilentlyContinue
}
# Telemetry Event
if($LabConfig.TelemetryLevel -in $TelemetryEnabledLevels) {
WriteInfo "Sending telemetry info"
$metrics = @{
'script.duration' = ((Get-Date) - $StartDateTime).TotalSeconds
'msu.count' = ($packages | Measure-Object).Count
}
if(-not $DCFilesExists) {
$metrics['dc.duration'] = ($dcHydrationEndTime - $dcHydrationEndTime).TotalSeconds
}
$properties = @{
'dc.exists' = [int]$DCFilesExists
'dc.edition' = $LabConfig.DCEdition
'dc.build' = $BuildNumber
'dc.language' = $OSLanguage
'lab.scriptsRenamed' = $renamed
'lab.installScvmm' = $LabConfig.InstallSCVMM
'os.windowsInstallationType' = $WindowsInstallationType
}
$events = @()
# First for parent disks
foreach($key in $vhdStatusInfo.Keys) {
$status = $vhdStatusInfo[$key]
$buildDuration = 0
if(-not $status.AlreadyExists) {
$buildDuration = ($status.BuildEndDate - $status.BuildStartDate).TotalSeconds
}
$key = $key.ToLower()
$properties["vhd.$($key).exists"] = [int]$status.AlreadyExists
$properties["vhd.$($key).name"] = $status.Name
if($buildDuration -gt 0) {
$metrics["vhd.$($key).duration"] = $buildDuration
}
if($status.AlreadyExists) {
continue # verbose events are interesting only when creating a new vhds
}
$vhdMetrics = @{
'vhd.duration' = $buildDuration
}
$vhdProperties = @{
'vhd.name' = $status.Name
'vhd.kind' = $status.Kind
}
if($status.Kind -ne "Tools") {
$vhdProperties['vhd.os.build'] = $BuildNumber
if($LabConfig.TelemetryLevel -eq "Full") {
$vhdProperties['vhd.os.language'] = $OSLanguage
}
}
$events += New-TelemetryEvent -Event "CreateParentDisks.Vhd" -Metrics $vhdMetrics -Properties $vhdProperties -NickName $LabConfig.TelemetryNickName
}
# and one overall
$events += New-TelemetryEvent -Event "CreateParentDisks.End" -Metrics $metrics -Properties $properties -NickName $LabConfig.TelemetryNickName
Send-TelemetryEvents -Events $events | Out-Null
}
Stop-Transcript
WriteSuccess "Job Done. Press enter to continue..."
Read-Host | Out-Null
#endregion