From b6f58dcdf6b644549aa0b681b0384cb8b5393884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= Date: Tue, 19 Nov 2024 17:28:05 +0100 Subject: [PATCH 1/4] Sync dev and master (#610) --- .github/workflows/create-release.yml | 6 +- Scripts/0_DCHydrate.ps1 | 644 +++++++++++++++++++++++++ Scripts/1_Prereq.ps1 | 2 +- Scripts/2_CreateParentDisks.ps1 | 679 ++------------------------- Scripts/3_Deploy.ps1 | 60 ++- Scripts/LabConfig.ps1 | 3 +- build.ps1 | 2 +- 7 files changed, 734 insertions(+), 662 deletions(-) create mode 100644 Scripts/0_DCHydrate.ps1 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index ce330a8d..fed84ad5 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -16,7 +16,7 @@ jobs: environment: release name: Bump version if: "!contains(github.event.head_commit.message, '[no release]')" - runs-on: windows-2019 + runs-on: windows-latest permissions: # needed to create a release contents: write @@ -54,7 +54,7 @@ jobs: new-release: name: Create release if: "!contains(github.event.head_commit.message, '[no release]')" - runs-on: self-hosted + runs-on: windows-latest permissions: # needed to create a release contents: write @@ -69,7 +69,7 @@ jobs: SIGN_SCRIPT_URI: ${{ secrets.SIGN_SCRIPT_URI }} CLIENT_ID: ${{ secrets.CLIENT_ID }} # just to obfusctate it in the output run: | - ./build.ps1 -Version ${{ needs.new-version.outputs.new_tag }} -SignScripts $true -SignScriptUri $env:SIGN_SCRIPT_URI -ClientId $env:CLIENT_ID + ./build.ps1 -Version ${{ needs.new-version.outputs.new_tag }} -SignScripts $false -SignScriptUri $env:SIGN_SCRIPT_URI -ClientId $env:CLIENT_ID Move-Item ./Release.zip mslab_${{ needs.new-version.outputs.new_tag }}.zip - name: Create changelog id: changelog diff --git a/Scripts/0_DCHydrate.ps1 b/Scripts/0_DCHydrate.ps1 new file mode 100644 index 00000000..dea0c3ca --- /dev/null +++ b/Scripts/0_DCHydrate.ps1 @@ -0,0 +1,644 @@ +#region CreateUnattendFileVHD + +#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</PlainText> + </AdministratorPassword> + </UserAccounts> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <TimeZone>$TimeZone</TimeZone> + </component> + </settings> +</unattend> + +"@ + + Set-Content -path $unattendFile -value $fileContent + + #return the file object + Return $unattendFile + } +#endregion + +#region Hydrate DC +function Hydrate-DC { + + param( + [parameter(Mandatory=$true)] + [string]$DCName, + + [parameter(Mandatory=$true)] + [string]$VhdPath, + + [parameter(Mandatory=$true)] + [string]$VmPath, + + [parameter(Mandatory=$true)] + [string]$SwitchName, + + [parameter(Mandatory=$true)] + [string]$TimeZone, + + [parameter(Mandatory=$true)] + [string]$DhcpScope, + + [parameter(Mandatory=$true)] + [string]$AdminPassword) + + WriteInfoHighlighted "Starting DC Hydration" + $dcHydrationStartTime = Get-Date + + #DCHP scope + $ReverseDnsRecord = $DhcpScope -replace '^(\d+)\.(\d+)\.\d+\.(\d+)$','$3.$2.$1.in-addr.arpa' + $DhcpScope = $DhcpScope.Substring(0,$DhcpScope.Length-1) + + #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 + } + if ($LabConfig.InstallSCVMM -eq "Yes"){ + #SCVMM 2022 requires 4GB of memory + $DC | Set-VMMemory -StartupBytes 4GB -MinimumBytes 4GB + } + + #Apply Unattend to VM + if ($VMVersion.Build -ge 17763){ + $oeminformation=@" + <OEMInformation> + <SupportProvider>MSLab</SupportProvider> + <SupportURL>https://aka.ms/mslab</SupportURL> + </OEMInformation> +"@ + }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 + WriteInfoHighlighted "`t Adding Hyper-V feature into DC" + #Install Hyper-V feature + Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Path "$mountdir" + + #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 ActiveDirectoryDsc -ModuleVersion "6.3.0" + Import-DscResource -ModuleName DnsServerDsc -ModuleVersion "3.0.0" + Import-DSCResource -ModuleName NetworkingDSC -ModuleVersion "9.0.0" + Import-DSCResource -ModuleName xDHCPServer -ModuleVersion "3.1.1" + Import-DSCResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "9.1.0" + Import-DSCResource -ModuleName xHyper-V -ModuleVersion "3.18.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 Hyper-V-PowerShell + { + Ensure = "Present" + Name = "Hyper-V-PowerShell" + } + + xVMSwitch VMSwitch + { + Ensure = "Present" + Name = "vSwitch" + Type = "External" + AllowManagementOS = $true + NetAdapterName = "Ethernet" + EnableEmbeddedTeaming = $true + DependsOn = "[WindowsFeature]Hyper-V-PowerShell" + } + + ADDomain FirstDS + { + DomainName = $Node.DomainName + Credential = $domainCred + SafemodeAdministratorPassword = $safemodeAdministratorCred + DomainNetbiosName = $node.DomainNetbiosName + DependsOn = "[WindowsFeature]ADDSInstall" + } + + WaitForADDomain DscForestWait + { + DomainName = $Node.DomainName + Credential = $domainCred + DependsOn = "[ADDomain]FirstDS" + } + + ADOrganizationalUnit DefaultOU + { + Name = $Node.DefaultOUName + Path = $Node.DomainDN + ProtectedFromAccidentalDeletion = $true + Description = 'Default OU for all user and computer accounts' + Ensure = 'Present' + DependsOn = "[ADDomain]FirstDS" + } + + ADUser SQL_SA + { + DomainName = $Node.DomainName + Credential = $domainCred + UserName = "SQL_SA" + Password = $NewADUserCred + Ensure = "Present" + DependsOn = "[ADOrganizationalUnit]DefaultOU" + Description = "SQL Service Account" + Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" + PasswordNeverExpires = $true + } + + ADUser SQL_Agent + { + DomainName = $Node.DomainName + Credential = $domainCred + UserName = "SQL_Agent" + Password = $NewADUserCred + Ensure = "Present" + DependsOn = "[ADOrganizationalUnit]DefaultOU" + Description = "SQL Agent Account" + Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" + PasswordNeverExpires = $true + } + + ADUser Domain_Admin + { + DomainName = $Node.DomainName + Credential = $domainCred + UserName = $Node.DomainAdminName + Password = $NewADUserCred + Ensure = "Present" + DependsOn = "[ADOrganizationalUnit]DefaultOU" + Description = "DomainAdmin" + Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" + PasswordNeverExpires = $true + } + + ADUser VMM_SA + { + DomainName = $Node.DomainName + Credential = $domainCred + UserName = "VMM_SA" + Password = $NewADUserCred + Ensure = "Present" + DependsOn = "[ADUser]Domain_Admin" + Description = "VMM Service Account" + Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" + PasswordNeverExpires = $true + } + + ADGroup DomainAdmins + { + GroupName = "Domain Admins" + DependsOn = "[ADUser]VMM_SA" + MembersToInclude = "VMM_SA",$Node.DomainAdminName + } + + ADGroup SchemaAdmins + { + GroupName = "Schema Admins" + GroupScope = "Universal" + DependsOn = "[ADUser]VMM_SA" + MembersToInclude = $Node.DomainAdminName + } + + ADGroup EntAdmins + { + GroupName = "Enterprise Admins" + GroupScope = "Universal" + DependsOn = "[ADUser]VMM_SA" + MembersToInclude = $Node.DomainAdminName + } + + ADUser AdministratorNeverExpires + { + DomainName = $Node.DomainName + UserName = "Administrator" + Ensure = "Present" + DependsOn = "[ADDomain]FirstDS" + PasswordNeverExpires = $true + } + + IPaddress IP + { + IPAddress = ($DhcpScope+"1/24") + AddressFamily = "IPv4" + InterfaceAlias = "vEthernet (vSwitch)" + DependsOn = "[xVMSwitch]VMSwitch" + } + + WindowsFeature DHCPServer + { + Ensure = "Present" + Name = "DHCP" + DependsOn = "[ADDomain]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" + } + + # Setting scope gateway + DhcpScopeOptionValue 'ScopeOptionGateway' + { + OptionId = 3 + Value = ($DhcpScope+"1") + ScopeId = ($DhcpScope+"0") + VendorClass = '' + UserClass = '' + AddressFamily = 'IPv4' + DependsOn = "[xDhcpServerScope]ManagementScope" + } + + # Setting scope DNS servers + DhcpScopeOptionValue 'ScopeOptionDNS' + { + OptionId = 6 + Value = ($DhcpScope+"1") + ScopeId = ($DhcpScope+"0") + VendorClass = '' + UserClass = '' + AddressFamily = 'IPv4' + DependsOn = "[xDhcpServerScope]ManagementScope" + } + + # Setting scope DNS domain name + DhcpScopeOptionValue 'ScopeOptionDNSDomainName' + { + OptionId = 15 + Value = $Node.DomainName + ScopeId = ($DhcpScope+"0") + VendorClass = '' + UserClass = '' + AddressFamily = 'IPv4' + DependsOn = "[xDhcpServerScope]ManagementScope" + } + + xDhcpServerAuthorization LocalServerActivation + { + IsSingleInstance = 'Yes' + Ensure = 'Present' + } + + WindowsFeature DSCServiceFeature + { + Ensure = "Present" + Name = "DSC-Service" + } + + DnsServerADZone addReverseADZone + { + Name = $ReverseDnsRecord + DynamicUpdate = "Secure" + ReplicationScope = "Forest" + Ensure = "Present" + DependsOn = "[DhcpScopeOptionValue]ScopeOptionGateway" + } + + 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 + #install prereqs + if (Test-Path "D:\SCVMM\SCVMM\Prerequisites\VCRedist\amd64\vcredist_x64.exe"){ + Start-Process -FilePath "D:\SCVMM\SCVMM\Prerequisites\VCRedist\amd64\vcredist_x64.exe" -ArgumentList "/passive /quiet /norestart" -Wait + } + 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 + } + + WriteInfo "`t Disconnecting VMNetwork Adapter from DC" + $DC | Get-VMNetworkAdapter | Disconnect-VMNetworkAdapter + + $dcHydrationEndTime = Get-Date +} +#endregion diff --git a/Scripts/1_Prereq.ps1 b/Scripts/1_Prereq.ps1 index d9e2f442..b0921833 100644 --- a/Scripts/1_Prereq.ps1 +++ b/Scripts/1_Prereq.ps1 @@ -178,7 +178,7 @@ function Get-WindowsBuildNumber { $downloadurl = $webcontent.BaseResponse.ResponseUri.AbsoluteUri.Substring(0,$webcontent.BaseResponse.ResponseUri.AbsoluteUri.LastIndexOf('/'))+($webcontent.Links | where-object { $_.'data-url' -match '/Diskspd.*zip$' }|Select-Object -ExpandProperty "data-url") } #> - $downloadurl="https://github.com/microsoft/diskspd/releases/download/v2.1/DiskSpd.ZIP" + $downloadurl="https://github.com/microsoft/diskspd/releases/download/v2.2/DiskSpd.ZIP" Invoke-WebRequest -Uri $downloadurl -OutFile "$PSScriptRoot\Temp\ToolsVHD\DiskSpd\diskspd.zip" }catch{ WriteError "`t Failed to download Diskspd!" diff --git a/Scripts/2_CreateParentDisks.ps1 b/Scripts/2_CreateParentDisks.ps1 index 15e1526b..ba2a2ea5 100644 --- a/Scripts/2_CreateParentDisks.ps1 +++ b/Scripts/2_CreateParentDisks.ps1 @@ -14,79 +14,7 @@ If (-not $isAdmin) { #region Functions . $PSScriptRoot\0_Shared.ps1 # [!build-include-inline] - - #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 = @" -<?xml version='1.0' encoding='utf-8'?> -<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - - <settings pass="offlineServicing"> - <component - xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - language="neutral" - name="Microsoft-Windows-PartitionManager" - processorArchitecture="amd64" - publicKeyToken="31bf3856ad364e35" - versionScope="nonSxS" - > - <SanPolicy>1</SanPolicy> - </component> - </settings> - <settings pass="specialize"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ComputerName>$Computername</ComputerName> - $oeminformation - <RegisteredOwner>PFE</RegisteredOwner> - <RegisteredOrganization>Contoso</RegisteredOrganization> - </component> - </settings> - <settings pass="oobeSystem"> - <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> - <UserAccounts> - <AdministratorPassword> - <Value>$AdminPassword</Value> - <PlainText>true</PlainText> - </AdministratorPassword> - </UserAccounts> - <OOBE> - <HideEULAPage>true</HideEULAPage> - <SkipMachineOOBE>true</SkipMachineOOBE> - <SkipUserOOBE>true</SkipUserOOBE> - </OOBE> - <TimeZone>$TimeZone</TimeZone> - </component> - </settings> -</unattend> - -"@ - - Set-Content -path $unattendFile -value $fileContent - - #return the file object - Return $unattendFile - } +. $PSScriptRoot\0_DCHydrate.ps1 # [!build-include-inline] #endregion @@ -155,11 +83,6 @@ If (-not $isAdmin) { #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 @@ -274,10 +197,10 @@ If (-not $isAdmin) { } $ISOServer = Mount-DiskImage -ImagePath $ServerISOItem.FullName -PassThru }else{ - WriteInfoHighlighted "Please select ISO image with Windows Server 2016, 2019, 2022 or Server Insider" + WriteInfoHighlighted "Please select ISO image with Windows Server 2016, 2019, 2022, 2025 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, 2022 or Server Insider" + Title="Please select ISO image with Windows Server 2016, 2019, 2022, 2025 or Server Insider" } $openFile.Filter = "iso files (*.iso)|*.iso|All files (*.*)|*.*" If($openFile.ShowDialog() -eq "OK"){ @@ -571,575 +494,55 @@ If (-not $isAdmin) { $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 - } - if ($LabConfig.InstallSCVMM -eq "Yes"){ - #SCVMM 2022 requires 4GB of memory - $DC | Set-VMMemory -StartupBytes 4GB -MinimumBytes 4GB - } - - #Apply Unattend to VM - if ($VMVersion.Build -ge 17763){ - $oeminformation=@" - <OEMInformation> - <SupportProvider>MSLab</SupportProvider> - <SupportURL>https://aka.ms/mslab</SupportURL> - </OEMInformation> -"@ - }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 - WriteInfoHighlighted "`t Adding Hyper-V feature into DC" - #Install Hyper-V feature - Enable-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Path "$mountdir" - - #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 ActiveDirectoryDsc -ModuleVersion "6.3.0" - Import-DscResource -ModuleName DnsServerDsc -ModuleVersion "3.0.0" - Import-DSCResource -ModuleName NetworkingDSC -ModuleVersion "9.0.0" - Import-DSCResource -ModuleName xDHCPServer -ModuleVersion "3.1.1" - Import-DSCResource -ModuleName xPSDesiredStateConfiguration -ModuleVersion "9.1.0" - Import-DSCResource -ModuleName xHyper-V -ModuleVersion "3.18.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 Hyper-V-PowerShell - { - Ensure = "Present" - Name = "Hyper-V-PowerShell" - } - - xVMSwitch VMSwitch - { - Ensure = "Present" - Name = "vSwitch" - Type = "External" - AllowManagementOS = $true - NetAdapterName = "Ethernet" - EnableEmbeddedTeaming = $true - DependsOn = "[WindowsFeature]Hyper-V-PowerShell" - } - - ADDomain FirstDS - { - DomainName = $Node.DomainName - Credential = $domainCred - SafemodeAdministratorPassword = $safemodeAdministratorCred - DomainNetbiosName = $node.DomainNetbiosName - DependsOn = "[WindowsFeature]ADDSInstall" - } - - WaitForADDomain DscForestWait - { - DomainName = $Node.DomainName - Credential = $domainCred - DependsOn = "[ADDomain]FirstDS" - } - - ADOrganizationalUnit DefaultOU - { - Name = $Node.DefaultOUName - Path = $Node.DomainDN - ProtectedFromAccidentalDeletion = $true - Description = 'Default OU for all user and computer accounts' - Ensure = 'Present' - DependsOn = "[ADDomain]FirstDS" - } - - ADUser SQL_SA - { - DomainName = $Node.DomainName - Credential = $domainCred - UserName = "SQL_SA" - Password = $NewADUserCred - Ensure = "Present" - DependsOn = "[ADOrganizationalUnit]DefaultOU" - Description = "SQL Service Account" - Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" - PasswordNeverExpires = $true - } - - ADUser SQL_Agent - { - DomainName = $Node.DomainName - Credential = $domainCred - UserName = "SQL_Agent" - Password = $NewADUserCred - Ensure = "Present" - DependsOn = "[ADOrganizationalUnit]DefaultOU" - Description = "SQL Agent Account" - Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" - PasswordNeverExpires = $true - } - - ADUser Domain_Admin - { - DomainName = $Node.DomainName - Credential = $domainCred - UserName = $Node.DomainAdminName - Password = $NewADUserCred - Ensure = "Present" - DependsOn = "[ADOrganizationalUnit]DefaultOU" - Description = "DomainAdmin" - Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" - PasswordNeverExpires = $true - } - - ADUser VMM_SA - { - DomainName = $Node.DomainName - Credential = $domainCred - UserName = "VMM_SA" - Password = $NewADUserCred - Ensure = "Present" - DependsOn = "[ADUser]Domain_Admin" - Description = "VMM Service Account" - Path = "OU=$($Node.DefaultOUName),$($Node.DomainDN)" - PasswordNeverExpires = $true - } - - ADGroup DomainAdmins - { - GroupName = "Domain Admins" - DependsOn = "[ADUser]VMM_SA" - MembersToInclude = "VMM_SA",$Node.DomainAdminName - } - - ADGroup SchemaAdmins - { - GroupName = "Schema Admins" - GroupScope = "Universal" - DependsOn = "[ADUser]VMM_SA" - MembersToInclude = $Node.DomainAdminName - } - - ADGroup EntAdmins - { - GroupName = "Enterprise Admins" - GroupScope = "Universal" - DependsOn = "[ADUser]VMM_SA" - MembersToInclude = $Node.DomainAdminName - } - - ADUser AdministratorNeverExpires - { - DomainName = $Node.DomainName - UserName = "Administrator" - Ensure = "Present" - DependsOn = "[ADDomain]FirstDS" - PasswordNeverExpires = $true - } - - IPaddress IP - { - IPAddress = ($DHCPscope+"1/24") - AddressFamily = "IPv4" - InterfaceAlias = "vEthernet (vSwitch)" - DependsOn = "[xVMSwitch]VMSwitch" - } - - WindowsFeature DHCPServer - { - Ensure = "Present" - Name = "DHCP" - DependsOn = "[ADDomain]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" - } - - # Setting scope gateway - DhcpScopeOptionValue 'ScopeOptionGateway' - { - OptionId = 3 - Value = ($DHCPscope+"1") - ScopeId = ($DHCPscope+"0") - VendorClass = '' - UserClass = '' - AddressFamily = 'IPv4' - DependsOn = "[xDhcpServerScope]ManagementScope" - } - - # Setting scope DNS servers - DhcpScopeOptionValue 'ScopeOptionDNS' - { - OptionId = 6 - Value = ($DHCPscope+"1") - ScopeId = ($DHCPscope+"0") - VendorClass = '' - UserClass = '' - AddressFamily = 'IPv4' - DependsOn = "[xDhcpServerScope]ManagementScope" - } - - # Setting scope DNS domain name - DhcpScopeOptionValue 'ScopeOptionDNSDomainName' - { - OptionId = 15 - Value = $Node.DomainName - ScopeId = ($DHCPscope+"0") - VendorClass = '' - UserClass = '' - AddressFamily = 'IPv4' - DependsOn = "[xDhcpServerScope]ManagementScope" - } - - xDhcpServerAuthorization LocalServerActivation - { - IsSingleInstance = 'Yes' - Ensure = 'Present' - } - - WindowsFeature DSCServiceFeature - { - Ensure = "Present" - Name = "DSC-Service" - } - - DnsServerADZone addReverseADZone - { - Name = $ReverseDNSrecord - DynamicUpdate = "Secure" - ReplicationScope = "Forest" - Ensure = "Present" - DependsOn = "[DhcpScopeOptionValue]ScopeOptionGateway" - } - - 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 - #install prereqs - if (Test-Path "D:\SCVMM\SCVMM\Prerequisites\VCRedist\amd64\vcredist_x64.exe"){ - Start-Process -FilePath "D:\SCVMM\SCVMM\Prerequisites\VCRedist\amd64\vcredist_x64.exe" -ArgumentList "/passive /quiet /norestart" -Wait - } - 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 - } - } +#region Create DC VHD +if (-not $DCFilesExists){ + $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 + } + } - 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 - } - } + #Get VM Version + [System.Version]$VMVersion=(Get-WindowsImage -ImagePath $VHDPath -Index 1).Version + WriteInfo "`t VM Version is $($VMVersion.Build).$($VMVersion.Revision)" +} +#endregion - 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 +#region create DC if it does not exist + if (-not $DCFilesExists) { + if (-not $LabConfig.NoDehydrateDC){ + Hydrate-DC -DCName $DCName -VhdPath $vhdpath -VmPath $VmPath -SwitchName $Switchname -TimeZone $TimeZone -DhcpScope $LabConfig.DHCPscope -AdminPassword $AdminPassword + $DC=Get-VM -Name $DCName + if ($DC -eq $null){ + WriteErrorAndExit "DC was not created successfully Press any key to continue ..." + } else { + WriteInfo "`t`t Virtual Machine $($DC.name) located in folder $($DC.Path) hydrated" } - - $dcHydrationEndTime = Get-Date + } else { + WriteInfoHighlighted "Skipping creation of dehydrated Domain Controller" + } } #endregion #region backup DC and cleanup #cleanup DC - if (-not $DCFilesExists){ + if (-not $DCFilesExists -and -not $LabConfig.NoDehydrateDC){ 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 diff --git a/Scripts/3_Deploy.ps1 b/Scripts/3_Deploy.ps1 index 06c6b162..810818c5 100644 --- a/Scripts/3_Deploy.ps1 +++ b/Scripts/3_Deploy.ps1 @@ -14,6 +14,7 @@ If (-not $isAdmin) { #region Functions . $PSScriptRoot\0_Shared.ps1 # [!build-include-inline] +. $PSScriptRoot\0_DCHydrate.ps1 # [!build-include-inline] Function CreateUnattendFileBlob{ #Create Unattend (parameter is Blob) @@ -892,6 +893,10 @@ If (-not $isAdmin) { $LabConfig.SwitchName = 'LabSwitch' } + If (!$LabConfig.DHCPscope){ + $LabConfig.DHCPscope="10.0.0.0" + } + WriteInfoHighlighted "List of variables used" WriteInfo "`t Prefix used in lab is $($labconfig.prefix)" @@ -1226,27 +1231,46 @@ If (-not $isAdmin) { } if (!(get-vm -Name ($labconfig.prefix+"DC") -ErrorAction SilentlyContinue)){ - #import DC - WriteInfo "`t Looking for DC to be imported" - $dcCandidates = [array](Get-ChildItem $LABFolder -Recurse | Where-Object {($_.extension -eq '.vmcx' -and $_.directory -like '*Virtual Machines*') -or ($_.extension -eq '.xml' -and $_.directory -like '*Virtual Machines*')}) - $dcCandidates | ForEach-Object -Process { - # If the VM ID is already used create a copy of the DC VM configuration instead of in-place registration - $vm = Get-VM -Id $_.BaseName -ErrorAction SilentlyContinue - if($vm -and $dcCandidates.Length -eq 1) { # allow duplicating of the DC VM only if it is the only one VM in lab folder (as if more than one exists, probably just labprefix was changed after the deployment) - WriteInfoHighlighted "You are trying to deploy a previously deployed lab from a different location as there is another DC VM with a same VM ID (is this a copied lab folder?) -> this DC VM will be registered with new VM ID." - $directory = $_.Directory.FullName.replace("\Virtual Machines", "") - $DC = Import-VM -Path $_.FullName -GenerateNewId -Copy -VirtualMachinePath $directory -VhdDestinationPath "$directory\Virtual Hard Disks" - WriteInfo "`t`t Virtual Machine $($DC.Name) registered with a new VM ID $($DC.Id)" - } else { - $DC = Import-VM -Path $_.FullName - } - } + if ($LabConfig.NoDehydrateDC) { + $DCName = "DC" + $vhdpath="$PSScriptRoot\LAB\$DCName\Virtual Hard Disks\$DCName.vhdx" + $VMPath="$PSScriptRoot\LAB\" + $HydrationSwitchname="DC_HydrationSwitch_$([guid]::NewGuid())" + + Hydrate-DC -DCName $DCName -VhdPath $vhdpath -VMPath $VMPath -Switchname $HydrationSwitchname -TimeZone $TimeZone -DHCPScope $LabConfig.DHCPscope -AdminPassword $LabConfig.AdminPassword + $DC=Get-VM -Name $DCName if ($DC -eq $null){ - WriteErrorAndExit "DC was not imported successfully Press any key to continue ..." - }else{ - WriteInfo "`t`t Virtual Machine $($DC.name) located in folder $($DC.Path) imported" + WriteErrorAndExit "DC was not created successfully Press any key to continue ..." + } else { + WriteInfo "`t`t Virtual Machine $($DC.name) located in folder $($DC.Path) hydrated" } + # Clean up hydration switch + WriteInfo "`t Removing switch $HydrationSwitchname" + Remove-VMSwitch -Name $HydrationSwitchname -Force -ErrorAction SilentlyContinue + } else { + #import DC + WriteInfo "`t Looking for DC to be imported" + $dcCandidates = [array](Get-ChildItem $LABFolder -Recurse | Where-Object {($_.extension -eq '.vmcx' -and $_.directory -like '*Virtual Machines*') -or ($_.extension -eq '.xml' -and $_.directory -like '*Virtual Machines*')}) + $dcCandidates | ForEach-Object -Process { + # If the VM ID is already used create a copy of the DC VM configuration instead of in-place registration + $vm = Get-VM -Id $_.BaseName -ErrorAction SilentlyContinue + if($vm -and $dcCandidates.Length -eq 1) { # allow duplicating of the DC VM only if it is the only one VM in lab folder (as if more than one exists, probably just labprefix was changed after the deployment) + WriteInfoHighlighted "You are trying to deploy a previously deployed lab from a different location as there is another DC VM with a same VM ID (is this a copied lab folder?) -> this DC VM will be registered with new VM ID." + $directory = $_.Directory.FullName.replace("\Virtual Machines", "") + $DC = Import-VM -Path $_.FullName -GenerateNewId -Copy -VirtualMachinePath $directory -VhdDestinationPath "$directory\Virtual Hard Disks" + WriteInfo "`t`t Virtual Machine $($DC.Name) registered with a new VM ID $($DC.Id)" + } else { + $DC = Import-VM -Path $_.FullName + } + } + if ($DC -eq $null){ + WriteErrorAndExit "DC was not imported successfully Press any key to continue ..." + }else{ + WriteInfo "`t`t Virtual Machine $($DC.name) located in folder $($DC.Path) imported" + } + } + #create checkpoint to be able to return to consistent state when cleaned with cleanup.ps1 $DC | Checkpoint-VM -SnapshotName Initial WriteInfo "`t Virtual Machine $($DC.name) checkpoint created" diff --git a/Scripts/LabConfig.ps1 b/Scripts/LabConfig.ps1 index bdb2ec7e..25533011 100644 --- a/Scripts/LabConfig.ps1 +++ b/Scripts/LabConfig.ps1 @@ -2,7 +2,7 @@ $LabConfig=@{AllowedVLANs="1-10,711-719" ; DomainAdminName='LabAdmin'; AdminPassword='LS1setup!'; Prefix = 'MSLab-' ; DCEdition='4'; Internet=$true ; AdditionalNetworksConfig=@(); VMs=@()} # Windows Server 2022 -1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "$S2D$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2022Core_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} +1..4 | ForEach-Object {$LABConfig.VMs += @{ VMName = "S2D$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2022Core_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 512MB }} # Or Azure Stack HCI 23H2 (non-domain joined) https://github.com/DellGEOS/AzureStackHOLs/tree/main/lab-guides/01a-DeployAzureStackHCICluster-CloudBasedDeployment #1..2 | ForEach-Object {$LABConfig.VMs += @{ VMName = "ASNode$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI23H2_G2.vhdx' ; HDDNumber = 4 ; HDDSize= 2TB ; MemoryStartupBytes= 24GB; VMProcessorCount=16 ; vTPM=$true ; Unattend="NoDjoin" ; NestedVirt=$true }} # Or Windows Server 2025 https://github.com/DellGEOS/AzureStackHOLs/tree/main/lab-guides/03-TestingWindowsServerInsider @@ -47,6 +47,7 @@ $LabConfig=@{AllowedVLANs="1-10,711-719" ; DomainAdminName='LabAdmin'; AdminPass SshKeyPath="$($env:USERPROFILE)\.ssh\id_rsa" # (Optional) If set, specified SSH key will be used to build and access Linux images. AutoClosePSWindows=$false; # (Optional) If set, the PowerShell console windows will automatically close once the script has completed successfully. Best suited for use in automated deployments. AutoCleanUp=$false; # (Optional) If set, after creating initial parent disks, files that are no longer necessary will be cleaned up. Best suited for use in automated deployments. + NoDehydrateDC=$false; # (Optional) If set, do not attempt to create a dehydrated DC. AdditionalNetworksConfig=@(); # Just empty array for config below VMs=@(); # Just empty array for config below } diff --git a/build.ps1 b/build.ps1 index 73be65f8..633119c6 100644 --- a/build.ps1 +++ b/build.ps1 @@ -22,7 +22,7 @@ $signedToolsOutputDir = Join-Path $outputBaseDir "Tools" $scriptsOutputFile = "Release.zip" # Files that would be skipped by Build function (no replacements) -[array]$scriptsBuildIgnoredFiles = "0_Shared.ps1" +[array]$scriptsBuildIgnoredFiles = @("0_Shared.ps1", "0_DCHydrate.ps1") [array]$toolsBuildIgnoredFiles = @() # Files that won't be signed after build function From f5789813123f607599e85987cc781210043f4f2a Mon Sep 17 00:00:00 2001 From: Matt McSpirit <mattmc@outlook.com> Date: Thu, 20 Mar 2025 12:21:44 -0700 Subject: [PATCH 2/4] Update LabConfig.ps1 Adding option to make DHCP Scope inactive. Default will be $true and 'Active' --- Scripts/LabConfig.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/LabConfig.ps1 b/Scripts/LabConfig.ps1 index a026d3e2..825d7829 100644 --- a/Scripts/LabConfig.ps1 +++ b/Scripts/LabConfig.ps1 @@ -37,6 +37,7 @@ $LabConfig=@{AllowedVLANs="1-10,711-719" ; DomainAdminName='LabAdmin'; AdminPass EnableGuestServiceInterface=$false; # (Optional) If True, then Guest Services integration component will be enabled on all VMs. DCVMProcessorCount=2; # (Optional) 2 is default. If specified more/less, processorcount will be modified. DHCPscope="10.0.0.0"; # (Optional) 10.0.0.0 is configured if nothing is specified. Scope has to end with .0 (like 10.10.10.0). It's always /24 + DHCPscopeActive=$true; # (Optional) If set to $false, DHCP Scope is created, but set to 'Inactive' DCVMVersion="9.0"; # (Optional) Latest is used if nothing is specified. Make sure you use values like "8.0","8.3","9.0" TelemetryLevel=""; # (Optional) If configured, script will stop prompting you for telemetry. Values are "None","Basic","Full" TelemetryNickname=""; # (Optional) If configured, telemetry will be sent with NickName to correlate data to specified NickName. So when leaderboards will be published, MSLab users will be able to see their own stats From a1f8c0af9c6d7b2877bbaf80ae3d3eab133c128f Mon Sep 17 00:00:00 2001 From: Matt McSpirit <mattmc@outlook.com> Date: Thu, 20 Mar 2025 12:54:49 -0700 Subject: [PATCH 3/4] Update DHCP Scope State --- Scripts/0_DCHydrate.ps1 | 7 +++++-- Scripts/3_Deploy.ps1 | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Scripts/0_DCHydrate.ps1 b/Scripts/0_DCHydrate.ps1 index dea0c3ca..e59a9f2e 100644 --- a/Scripts/0_DCHydrate.ps1 +++ b/Scripts/0_DCHydrate.ps1 @@ -96,6 +96,9 @@ function Hydrate-DC { [parameter(Mandatory=$true)] [string]$DhcpScope, + [parameter(Mandatory=$true)] + [string]$DhcpScopeState, + [parameter(Mandatory=$true)] [string]$AdminPassword) @@ -396,8 +399,8 @@ function Hydrate-DC { IPEndRange = ($DhcpScope+"254") Name = 'ManagementScope' SubnetMask = '255.255.255.0' - LeaseDuration = '00:08:00' - State = 'Active' + LeaseDuration = ((New-TimeSpan -Hours 8).ToString()) + State = $DHCPScopeState AddressFamily = 'IPv4' DependsOn = "[Service]DHCPServer" } diff --git a/Scripts/3_Deploy.ps1 b/Scripts/3_Deploy.ps1 index 88afbeb1..afcef0b5 100644 --- a/Scripts/3_Deploy.ps1 +++ b/Scripts/3_Deploy.ps1 @@ -1287,7 +1287,14 @@ If (-not $isAdmin) { $VMPath="$PSScriptRoot\LAB\" $HydrationSwitchname="DC_HydrationSwitch_$([guid]::NewGuid())" - Hydrate-DC -DCName $DCName -VhdPath $vhdpath -VMPath $VMPath -Switchname $HydrationSwitchname -TimeZone $TimeZone -DHCPScope $LabConfig.DHCPscope -AdminPassword $LabConfig.AdminPassword + if ($LabConfig.DHCPscopeActive -eq $false){ + $DHCPScopeState = 'Inctive' + } + else { + $DHCPScopeState = 'Active' + } + + Hydrate-DC -DCName $DCName -VhdPath $vhdpath -VMPath $VMPath -Switchname $HydrationSwitchname -TimeZone $TimeZone -DHCPScope $LabConfig.DHCPscope -DHCPScopeState $DHCPScopeState -AdminPassword $LabConfig.AdminPassword $DC=Get-VM -Name $DCName if ($DC -eq $null){ WriteErrorAndExit "DC was not created successfully Press any key to continue ..." From b76567d50c3f9c3b35a00b7828d2b0f1c0f5958e Mon Sep 17 00:00:00 2001 From: Matt McSpirit <mattmc@outlook.com> Date: Thu, 20 Mar 2025 13:23:36 -0700 Subject: [PATCH 4/4] Updating to allow DHCP Scope Active/Inactive --- Scripts/3_Deploy.ps1 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Scripts/3_Deploy.ps1 b/Scripts/3_Deploy.ps1 index afcef0b5..21e236a7 100644 --- a/Scripts/3_Deploy.ps1 +++ b/Scripts/3_Deploy.ps1 @@ -947,6 +947,16 @@ If (-not $isAdmin) { $LabConfig.DHCPscope="10.0.0.0" } + if (!$LabConfig.DHCPscopeActive){ + $DHCPScopeState = 'Active' + } + elseif ($LabConfig.DHCPscopeActive -eq $false){ + $DHCPScopeState = 'Inactive' + } + else { + $DHCPScopeState = 'Active' + } + WriteInfoHighlighted "List of variables used" WriteInfo "`t Prefix used in lab is $($labconfig.prefix)" @@ -1287,13 +1297,6 @@ If (-not $isAdmin) { $VMPath="$PSScriptRoot\LAB\" $HydrationSwitchname="DC_HydrationSwitch_$([guid]::NewGuid())" - if ($LabConfig.DHCPscopeActive -eq $false){ - $DHCPScopeState = 'Inctive' - } - else { - $DHCPScopeState = 'Active' - } - Hydrate-DC -DCName $DCName -VhdPath $vhdpath -VMPath $VMPath -Switchname $HydrationSwitchname -TimeZone $TimeZone -DHCPScope $LabConfig.DHCPscope -DHCPScopeState $DHCPScopeState -AdminPassword $LabConfig.AdminPassword $DC=Get-VM -Name $DCName if ($DC -eq $null){