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