Listing 1: Labconfig.ps1

#basic config for Windows Server 2019, that creates VMs for S2D Hyperconverged scenario https://github.com/Microsoft/WSLab/tree/master/Scenarios/S2D%20Hyperconverged


$LabConfig=@{ DomainAdminName='LabAdmin'; AdminPassword='Admin@123'; Prefix = 'WSLab-' ; DCEdition='3'; Internet=$true ; AdditionalNetworksConfig=@(); VMs=@()}

# Windows Server 2019
1..2 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'Win2019Core_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 4; HDDSize= 4TB ; MemoryStartupBytes= 512MB }}

$LabConfig.VMs += @{ VMName = 'MGMT01' ; Configuration = 'Simple' ; ParentVHD = 'Win10RS5_G2.vhdx' ; MemoryStartupBytes= 1GB ; MemoryMinimumBytes=1GB ; AddToolsVHD=$True ; DisableWCF=$True }


#'Win2019Core_G2.vhdx' und  'Win2019Core_G2.vhdx';  werden zuvor mittels CreateParentDisks aus den heruntergeladenen ISOs erstellt.
# Or Azure Stack HCI 

1..4 | ForEach-Object {$VMNames="S2D"; $LABConfig.VMs += @{ VMName = "$VMNames$_" ; Configuration = 'S2D' ; ParentVHD = 'AzSHCI20H2_G2.vhdx'; SSDNumber = 0; SSDSize=800GB ; HDDNumber = 12; HDDSize= 4TB ; MemoryStartupBytes= 1GB }}

Listing 2: Switch über PowerShell anlegen
# Neuen internen Switch anlegen
New-VMSwitch -Name "InternalNAT" -SwitchType Internal
# IP-Adresse für das NAT Gateway einrichten
New-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceAlias "vEthernet (InternalNAT)"
# Das neue NAT network einrichten
New-NetNat -Name "AzSHCINAT" -InternalIPInterfaceAddressPrefix 192.168.0.0/24
# Überprüfen der NAT Konfiguration
Get-NetNat

Listing 3: Basis-VHDs erzeugen#region Variablen

$AdminPassword="Admin@123" #Gilt für alle VMs
$Tools = "D:\Tools"  #Speicherort für convert-windowsimage
$mountdir ="d:\VMs\Temp\MountDir" #Anknüpfungspunkt, um die VHDx bearbeiten zu können
$VMDir = "D:\VMs" # Verzeichnis für die VMs
$SwitchName = "InternalNAT" #Genutzter Switch
$DefaultGateway="192.168.0.1"
#Edition #1=Core Std #2=GUI Std  #3=Core Datacenter  #4=GUI Datacenter
$VHDDefinition= ConvertFrom-Csv @'
Name, Edition, Source, Destination, SizeGB,MSuPath
"DC Core", 3,"D:\ISO\WS2019Eval.iso" , "D:\VMs\WS2019_DC_Core.VHDx", "60", "D:\MSU\1809\2020-07"
"DC GUI", 4,"D:\ISO\WS2019Eval.iso" , "D:\VMs\WS2019_DC_GUI.VHDx", "60", "D:\MSU\1809\2020-07"
"WIN10",1,"D:\ISO\W10Eval.iso" , "D:\VMs\Win10Ent.VHDx", "60", "D:\MSU\1909\2020-07"
"STD GUI", 2,"D:\ISO\WS2019Eval.iso" , "D:\VMs\WS2019_STD_GUI.VHDx", "60", "D:\MSU\1809\2020-07"
'@ #Konfiguration der Basis VHDX mit den verschiedenen Betriebssystemen u. Speicherplatz

$VMDefinition = ConvertFrom-Csv @'
Name, MemStartGB,MaxMemGB,  Parent,  VCore,AddNetwork, AddHDD,IP,DNS,End
"DC01",      4 ,4,"D:\VMs\WS2019_DC_GUI.VHDx" ,2, , ,"192.168.0.2","1.1.1.1",X
"MGMT01",    4 ,4,"D:\VMs\Win10Ent.VHDx"      ,2, , ,"192.168.0.3","192.168.0.2",X
"MGMT02",    4 ,4,"D:\VMs\WS2019_DC_GUI.VHDx" ,2, , ,"192.168.0.99","192.168.0.2",X
"S2D-Node1", 4 ,4,"D:\VMs\WS2019_DC_Core.VHDx",4,3,4,"192.168.0.4","192.168.0.2",X
"S2D-Node2", 4 ,4,"D:\VMs\WS2019_DC_Core.VHDx",4,3,4,"192.168.0.5","192.168.0.2",X

'@ #Konfiguration der VMs mit Speicher, Image, Anzahl Kernen, zus. Netzwerkkarten u.HDDs,IP-Config
#Erstellung der Antwortdatei 
  $unattend =   @"
<?xml version='1.0' encoding='utf-8'?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
 <settings pass="specialize">
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
        <InputLocale>0407:00000407</InputLocale> <SystemLocale>de-DE</SystemLocale>
        <UILanguage>de-DE</UILanguage> <UILanguageFallback>de-DE</UILanguageFallback>
        <UserLocale>de-DE</UserLocale>
    </component>
 </settings>
 <settings pass="oobeSystem">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <AutoLogon> <Password> <Value>$AdminPassword </Value> <PlainText>true</PlainText> </Password>
         <Username>Administrator</Username> <Enabled>true</Enabled> <LogonCount>1</LogonCount> 
      </AutoLogon>
      <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>
"@
#endregion

Listing 4: Basisdatenträger erstellen #region Basisdatenträgererstellung
 
. "$Tools\Convert-WindowsImage.ps1" # Die Skriptsammlung Convert-WindowsImage.ps1 laden
$TimeZone = (Get-TimeZone).id       # Automatisch vom Hostsystem übenehmen
#Temporäres Verzeichnis als Mountpunkt anlegen
if(!(Test-Path -Path $mountdir)){New-item -type directory -Path $mountdir -force}
foreach ($VHDConfig in $VHDDefinition) { 
    $VHDConfig #Konfiguration anzeigen
    if (!(Test-Path -Path $VHDConfig.Destination)) { #Nur, wenn VHDx noch nicht vorhanden ist.
    if (Test-Path -Path $VHDConfig.Source) { #Nur, wenn ISO-Datei auch vorhanden ist.
        # Das ISO-Image für die Installation einbinden
        $ISOServer = Mount-DiskImage -ImagePath $VHDConfig.Source -PassThru
        # Den zugeordneten Laufwerksbuchstaben ermitteln und speichern
        $ServerMediaDriveLetter = (Get-Volume -DiskImage $ISOServer).DriveLetter
        #Die Install-Wim einbinden
        $WindowsImage=Get-WindowsImage -ImagePath "$($ServerMediaDriveLetter):\sources\install.wim"
        $windowsimage.ImageName
        #MSU-Pakete ermitteln und sicherstellen, dass das kleinere Service-Stack-Paket zuerst installiert wird    
        $packages = Get-ChildItem $VHDConfig.MSUPath -Filter *.msu | % { $_.FullName |Sort-Object -Property Length} 
        # Mithilfe von Conver-WindowsImage die BootVHD für die VM erstellen
        [uint64]$Size = [uint64]$vhdconfig.SizeGB * 1024*1024*1024# Bereitstellung der Größenangabe im richtigen Kontext
        if ($packages){ #Wenn MSU-Pakete vorhanden sind, dann mit einbinden
                Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $vhdconfig.Edition -VHDPath $VHDConfig.Destination -SizeBytes $size -VHDFormat VHDX -DiskLayout UEFI -Package $packages}
        else {  #ohne MSU-Pakete die VHDx erstellen
                Convert-WindowsImage -SourcePath "$($ServerMediaDriveLetter):\sources\install.wim" -Edition $VHDConfig.Edition -VHDPath $VHDConfig.Destination -SizeBytes $size -VHDFormat VHDX -DiskLayout UEFI }
        write "Die VHDx $VHDConfig wurde erstellt!"
        $ISOServer | Dismount-DiskImage #Das Diskimage wird wieder entfernt
        # Die erstellte VHD in ein Verzeichnis mounten
        Mount-WindowsImage  -ImagePath $VHDConfig.Destination -Index 1  -Path $mountdir
        # auf der VHD ein Verzeichnis "Panther"in C:\Windows\ anlegen und dort die Unattend.xml hineinschreiben
        New-item -type directory -Path "$mountdir\Windows\Panther" -force
        new-item  "$mountdir\Windows\Panther\" -name "unattend.xml" -type "file" -value $unattend
        Dismount-WindowsImage -Path $mountdir -Save # Die VHD speichern und wieder dismounten
}    }   }
#endregion 

Listing 5: IP-Konfiguration#region VM-Erstellung

foreach ($VM in $VMDefinition) {
if (!(Test-Path -Path ("$vmdir\"+($vm).name))) 
    {$VHDPath = "$VMDir\"+$vm.name+"\"
    [uint64]$MemStartGB = [uint64]$vm.MemStartGB *1024*1024*1024
    [uint64]$MaximumBytes= [uint64]$vm.MaxMemGB *1024*1024*1024
    #Anlage der System-VHD als Differencing Disk
    If (!(Test-Path -path "$VHDPath\Virtual Hard Disks\System.VHDx")) {
    New-VHD -ParentPath $vm.Parent -Path "$VHDPath\Virtual Hard Disks\System.VHDx" -Differencing  }
    #Anlage der VM mit den definierten Parametern
    New-VM -Name $VM.Name -MemoryStartupBytes $MemStartGB -SwitchName $SwitchName -Path $VMDir -VHDPath "$VHDPath\Virtual Hard Disks\System.VHDx" -Generation 2 
    # Optionale Konfiguration von Dynamic Memory für die VM um RAM sparen zu können
    Set-VMMemory $VM.Name -DynamicMemoryEnabled $true -MinimumBytes 1GB -StartupBytes $MemStartGB -MaximumBytes $MaximumBytes
    # Wiederherstellungspunkte für die VM deaktivieren und V-Cores definieren 
    Set-VM -VMName $VM.Name -CheckpointType Disabled -ProcessorCount  $vm.VCore
    #Nested Virtualisation einschalten
    Set-VMProcessor -VMName $VM.Name -ExposeVirtualizationExtensions $true -Verbose
    # Zusätzliche vituelle Netzwerkkarten hinzufügen, wenn in Config angegeben
    if ($vm.AddNetwork) {
    1..$vm.AddNetwork | ForEach-Object { 
        Add-VMNetworkAdapter -VMName $VM.Name -SwitchName $SwitchName
        Set-VMNetworkAdapter -VMName $VM.Name -MacAddressSpoofing On -AllowTeaming On }
        }
    if ($vm.AddHDD) {#zusätzliche VHDs für S2D Anlegen, wenn in Config angegeben
    $dataDrives = 1..$vm.AddHDD | ForEach-Object { New-VHD -Path "$VHDPath\Virtual Hard Disks\DATA0$_.vhdx" -Dynamic -Size 100GB }
    $dataDrives | ForEach-Object {    Add-VMHardDiskDrive -Path $_.path -VMName $VM.Name }}
    # Verbindung zur VM und Start der VM
    vmconnect.exe localhost $VM.Name
    Start-VM -Name $VM.Name    }
}
#endregion 

$Cred = New-Object System.Management.Automation.PSCredential "Administrator", (ConvertTo-SecureString $AdminPassword -AsPlainText -Force)
#region IP-Konfiguration
foreach ($VM in $VMDefinition) {
if ((get-vm -name $vm.Name).state -eq "Running") {
    # Test Ob die Maschine ansprechpbar ist
    while ((Invoke-Command -VMName $vm.Name -Credential $Cred {"Test"} -ErrorAction SilentlyContinue) -ne "Test") {
        Write "Warte auf VM $($VM.Name)"
        Start-Sleep -Seconds 1    }
    Invoke-Command -VMName $vm.Name -Credential $Cred -ScriptBlock {
        # Konfiguration der 1. Netzwerkkarte für die VM
        $dcIP = Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "Ethernet" | Select-Object IPAddress
        New-NetIPAddress -IPAddress $($using:vm.ip)  -DefaultGateway $($using:DefaultGateway) -InterfaceAlias "Ethernet" -PrefixLength "24" | Out-Null
        Set-DnsClientServerAddress -InterfaceAlias "Ethernet" -ServerAddresses $($using:vm.DNS)
        # Computernamen durch VM-Namen ersetzen und Neustarten
        Rename-Computer -NewName $($using:Vm.Name)    }
        Write-Verbose "Rebooting VM for $($vm.Name) change to take effect" -Verbose
        Stop-VM -Name $vm.Name
        Start-VM -Name $vm.Name        }
}
#endregion

Listing 6: Domain-Controller-Dienste auf dem DC01 installieren

# Passwort für den Directory Services Restore Mode
$DSRMPWord = ConvertTo-SecureString -String "Admin@123" -AsPlainText -Force
# Benötigte Windowsfeatures hinzufügen
Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
# Installation des ADs
Install-ADDSForest -CreateDnsDelegation:$false -DatabasePath "C:\Windows\NTDS" `
        -DomainMode 7 -DomainName "SBSrv.net" -ForestMode 7 -InstallDns:$true `
        -SafeModeAdministratorPassword $DSRMPWord -LogPath "C:\Windows\NTDS" `
        -NoRebootOnCompletion:$true -SysvolPath "C:\Windows\SYSVOL" -Force:$true 
Restart-Computer 

Listing 7: Remote ausführen

$AdminPassword="Admin@123" #Gilt für alle VMs
$Cred = New-Object System.Management.Automation.PSCredential "Administrator", (ConvertTo- SecureString $AdminPassword -AsPlainText -Force)

Invoke-Command -VMName DC01 -Credential $Cred -ScriptBlock {

######## Befehle hier einsetzen ##########

} 

Listing 8: Skriptblock erweitern

$AdminPassword="Admin@123" #Gilt für alle VMs
$Cred = New-Object System.Management.Automation.PSCredential "Administrator", (ConvertTo- SecureString $AdminPassword -AsPlainText -Force)

# Konfiguration vom Active Directory auf DC01
Invoke-Command -VMName DC01 -Credential $Cred -ScriptBlock {
    # Passwort für den Directory Services Restore Mode
    $DSRMPWord = ConvertTo-SecureString -String "Admin@123" -AsPlainText -Force
    # Benötigte Windowsfeatures hinzufügen
    Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
    # Installation des ADs
    Install-ADDSForest -CreateDnsDelegation:$false -DatabasePath "C:\Windows\NTDS" `
        -DomainMode 7 -DomainName "SBSrv.net" -ForestMode 7 -InstallDns:$true `
        -SafeModeAdministratorPassword $DSRMPWord -LogPath "C:\Windows\NTDS" `
        -NoRebootOnCompletion:$true -SysvolPath "C:\Windows\SYSVOL" -Force:$true
    Restart-Computer} 

Listing 9: Weiteren Admin anlegen

$DomCred = New-Object System.Management.Automation.PSCredential "SBSrv.net\Administrator", (ConvertTo-SecureString $AdminPassword -AsPlainText -Force)
$newUser = "LabAdmin"
Invoke-Command -VMName DC01 -Credential $DomCred -ScriptBlock {
    New-ADUser -Name $using:newUser -AccountPassword $using:Cred.Password -Enabled $True
    Get-ADUser -Identity $using:newUser
    Add-ADGroupMember -Identity "Domain Admins" -Members $using:newUser
    Add-ADGroupMember -Identity "Enterprise Admins" -Members $using:newUser
    Add-ADGroupMember -Identity "Schema Admins" -Members $using:newUser    } 

Listing 10: MGMT01 und andere Server der Domäne hinzufügen

$Cred = New-Object System.Management.Automation.PSCredential "Administrator", (ConvertTo- SecureString $AdminPassword -AsPlainText -Force)
$DomCred = New-Object System.Management.Automation.PSCredential "SBSrv.net\Administrator", (ConvertTo-SecureString $AdminPassword -AsPlainText -Force)

$Computers = "MGMT01","S2D-NODE1","S2D-NODE2"
Foreach ($Computer in $Computers) {
# Alle Computer in der Liste hintereinander in die Domäne aufnehmen
Invoke-Command -VMName $Computer -Credential $Cred -ScriptBlock {
    Add-Computer –DomainName sbsrv.net  –Credential $using:domCred -Force
Restart-computer    } }

Listing 11: Windows-Features installieren

$ServerList = "S2D-NODE1","S2D-NODE2"
$FeatureList = "Hyper-V", "Failover-Clustering", "Data-Center-Bridging", "RSAT-Clustering- PowerShell", "Hyper-V-PowerShell", "FS-FileServer" ,"BitLocker" ,"FS-Data-Deduplication"
$Computers = "S2D-NODE1","S2D-NODE2"
Foreach ($Computer in $Serverlist) {
# Die Alle Features aus der Featurelist auf den VMs der Serverlist installieren!
Invoke-Command -VMName $Computer -Credential $DomCred -ScriptBlock {
     Install-WindowsFeature -Name $Using:Featurelist -IncludeAllSubFeature -IncludeManagementTools
     Restart-Computer    } }

Listing 12: Einträge zu WSMan

Foreach ($Computer in $Serverlist) {
# Alle Computer in der Liste hintereinander in die Domäne aufnehmen
Invoke-Command -VMName $Computer -Credential $DomCred -ScriptBlock {
 Enable-PSRemoting -Force
Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any
Set-NetFirewallRule -Name WINRM-HTTP-In-TCP -RemoteAddress Any
Set-Item WSMAN:\Localhost\Client\TrustedHosts -Value *.sbsrv.net -Force
Get-Service winrm
Get-Item wsman:\localhost\Service\RootSDDL
Get-Item wsman:\localhost\Client\TrustedHosts
cd WSMan:\localhost\Client; dir
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 } } 

Listing 13: Netzwerkadapter umbenennen und IP-Adressen vergeben

Foreach ($Computer in $ServerList) {
Invoke-Command -VMName $Computer -Credential $DomCred -ScriptBlock {
Rename-NetAdapter -Name "Ethernet" -NewName "Mgmt"
Rename-NetAdapter -Name "Ethernet 2" -NewName "Storage1"
Rename-NetAdapter -Name "Ethernet 3" -NewName "Storage2"
Rename-NetAdapter -Name "Ethernet 4" -NewName "VM"
$ip= (Get-NetIPAddress -InterfaceAlias "Mgmt" -AddressFamily IPv4).IPAddress 
$lastokt=($ip.split("."))[3] 
#Das letzte Oktett der Management-IP wird für alle Adapter übernommen
New-NetIPAddress -IPAddress "192.168.101.$lastokt" -InterfaceAlias "Storage1" -PrefixLength "24" | Out-Null
New-NetIPAddress -IPAddress "192.168.102.$lastokt" -InterfaceAlias "Storage2" -PrefixLength "24" | Out-Null
New-NetIPAddress -IPAddress "192.168.103.$lastokt" -DefaultGateway 192.168.103.1 -InterfaceAlias "VM" -PrefixLength "24" | Out-Null } }
