This article is intended to guide you to build a lab environment where you can know almost all Windows Server 2016 new roles and features, and others not so new, but very useful:

  • Nested Virtualization
  • Nano Server
  • Stretch Cluster
  • Storage Replica
  • Storage Spaces Direct
  • PowerShell Direct
  • Deduplication
  • ReFS
  • Thin and Tiers
  • Infrastructure as a Code (IaaC)

 

At the picture below, we can understand that our lab will start at Level 2 of Nested Virtualization.

The intention is to use an Azure E2s v3 Virtual Machine as a base (AzureVM, Level 2), that supports Nested Virtualization. Install Hyper-V role in it and create three VMs. All O.S. will be Windows Server 2016. One VM will be the Domain Controller (DC, Level 3) and two VMs will be Nano Servers (NanoHost01/NanoHost02, Level 3) with Storage, Hyper-V and Failover Cluster roles.

The Nano Servers will form a Storage Spaces Direct and failover cluster (NanoCluster, Level 3) sharing a CSV to Hyper-V create two new nested VMs (Guest01/Guest02, Level 4) with Storage Replica, Failover Cluster, Deduplication and File Server, creating a Stretch Cluster (GuestCluster, Level 4) with Storage Replica and deduplication.

Let's summarize all necessary to start:

Create Azure VM

Create an Azure Virtual Machine E2s v3 named AzureVM.



Add Data Disk


Add a 200GB (SSD) data disk to AzureVM with Read/Write Cache and format as NTFS and assign the H letter.




Connect  To VM With RDP


At this point, we need to connect at AzureVM through RDP, open PowerShell ISE as Administrator and follow one of the methods. · Install Hyper-V and Deduplication features and restart:

Install-WindowsFeature -Name Hyper-V, FS-Data-Deduplication -IncludeAllSubFeature -IncludeManagementTools -Restart

Turn 200GB disk online, initialize as GPT, create an H volume and format as NTFS:
Get-Disk | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -AssignDriveLetter H -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Hyper-V" -Confirm:$false

Configure Data Deduplication

The next step is to configure data deduplication at H: as HyperV:

 

IaaC Method

At AzureVM, just run the H:\IaaC.ps1

PowerShell Method

A good start is creating a transcript file, logging all PowerShell output for further analysis or deployment documentation.

 

Start-Transcript -Path C:\Transcript.log

We will need to authenticate created VMs using the password defined in Unattended files. For this lab, we set all VMs passwords as P@ssw0rd. As a security best practice, we didn't hardcode the password, asking for it at runtime.

$SecurePassword = Read-Host -Prompt "Enter password" -AsSecureString

Mount the ISO that we'll use to generate the VHDX

$ISO = 'H:\en_windows_server_2016_x64_dvd_9718492.iso'
 
Mount-DiskImage -ImagePath $ISO | Out-Null
 
Set-Variable -Name "Drive" -Scope Global -Value ((Get-DiskImage -ImagePath $ISO | Get-Volume).DriveLetter)

Prepare some AzureVM Hyper-V and network options to provide route and internet (NAT) between levels of nested virtualization.

 
Set-VMhost -EnableEnhancedSessionMode $true
 
New-VMSwitch -Name 'BR-SP-PRD' -SwitchType Internal
 
New-VMSwitch -Name 'Cluster PRD' -SwitchType Internal
 
New-VMSwitch -Name 'BR-SP-DR' -SwitchType Internal
 
New-VMSwitch -Name 'Cluster DR' -SwitchType Internal
 
New-NetIPAddress -IPAddress 192.168.4.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*BR-SP-PRD*').InterfaceIndex
 
New-NetIPAddress -IPAddress 192.168.5.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*Cluster PRD*').InterfaceIndex
 
New-NetIPAddress -IPAddress 192.168.6.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*BR-SP-DR*').InterfaceIndex
 
New-NetIPAddress -IPAddress 192.168.7.1 -PrefixLength 24 -InterfaceIndex (Get-NetAdapter -Name '*Cluster DR*').InterfaceIndex
 
New-NetRoute  -DestinationPrefix 192.168.8.0/24 -InterfaceAlias "vEthernet (BR-SP-PRD)"  -AddressFamily IPv4 -NextHop 192.168.4.11
 
New-NetRoute -DestinationPrefix 192.168.9.0/24  -InterfaceAlias "vEthernet (Cluster PRD)" -AddressFamily IPv4 -NextHop 192.168.5.11
 
New-NetRoute -DestinationPrefix 192.168.10.0/24  -InterfaceAlias "vEthernet (BR-SP-DR)" -AddressFamily IPv4-NextHop 192.168.6.11
 
New-NetRoute -DestinationPrefix 192.168.11.0/24  -InterfaceAlias "vEthernet (Cluster DR)" -AddressFamily IPv4 -NextHop 192.168.7.11
 
New-NetNat -Name MyNATnetwork -InternalIPInterfaceAddressPrefix 192.168.4.0/22
 
Get-NetAdapter | Set-NetIPInterface -Forwarding Enabled

 

The first computer to support our lab is the Domain Controller (DC)

Function CreateDC ($Computer,$Volume){
 
    # Convert ISO to VHDX
 
    . ${Drive}:\NanoServer\NanoServerImageGenerator\Convert-WindowsImage.ps1
 
    Convert-WindowsImage -SourcePath "${Drive}:\sources\install.wim" -Edition Datacenter -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -VHDFormat VHDX -DiskLayout UEFI -RemoteDesktopEnable -BCDinVHD VirtualMachine -UnattendPath H:\DC-Unattend.xml
 
    
 
    # Cria, configura e inicia a VM
 
    New-VM -Name $Computer -Path ${Volume}:\Hyper-V -MemoryStartupBytes 2048MB -Generation 2 -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName 'BR-SP-PRD'
 
    Set-VMProcessor –VMName $Computer -Count 2
 
    Rename-VMNetworkAdapter -VMName $Computer -Name 'Network Adapter' -NewName 'BR-SP-PRD'
 
    Get-VM -Name DC | Add-VMDvdDrive
 
    Set-VMDvdDrive -VMName DC -Path $ISO
 
    Start-VM -Name $Computer
 
}
 
CreateDC -Computer "DC" -Volume "H"

 



While DC start we can create both NanoHost01 and NanoHost02 with Windows Server 2016 Datacenter Nano that will form the Storage Spaces Direct and Hyper-V Failover Cluster (NanoCluster) with CSV to support the next Level (3) of Nested VM Guests.

 

Function CreateNano ($Computer,$IPPRD,$IPGW,$Volume,$Enviroment){
  
    # Convert ISO to VHDX
  
    Import-Module ${Drive}:\NanoServer\NanoServerImageGenerator
  
    New-NanoServerImage -Edition Datacenter -DeploymentType Guest -MediaPath "${Drive}:\" -BasePath ${Volume}:\Hyper-V\Base -TargetPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -ComputerName $Computer -Compute -Clustering -Storage -AdministratorPassword $SecurePassword -InterfaceNameOrIndex 'Ethernet' -Ipv4Address $IPPRD -Ipv4SubnetMask 255.255.255.0 -Ipv4Gateway $IPGW -Ipv4Dns 192.168.4.20 -EnableRemoteManagementPort -UnattendPath 'H:\Nano-Unattend.xml'
  
    # Create, start and configure the VM
  
    New-VM -Name $Computer -Path ${Volume}:\Hyper-V -MemoryStartupBytes 4352MB -Generation 2 -VHDPath "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName "BR-SP-$Enviroment"
  
    # Enable Nested Virtualization
  
    Set-VMProcessor -VMName $Computer -ExposeVirtualizationExtensions $true -Count 2
  
    Set-VMMemory -VMName $Computer -DynamicMemoryEnabled $false
  
    New-VHD -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-SSD.vhdx" -SizeBytes 10GB -Dynamic
  
    New-VHD -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-HDD.vhdx" -SizeBytes 60GB -Dynamic
  
    Add-VMHardDiskDrive -VMName $Computer -ControllerType SCSI -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-SSD.vhdx" -ControllerNumber 0 -ControllerLocation 1
  
    Add-VMHardDiskDrive -VMName $Computer -ControllerType SCSI -Path "${Volume}:\Hyper-V\$Computer\Virtual Hard Disks\$Computer-HDD.vhdx" -ControllerNumber 0 -ControllerLocation 2
  
    Enable-VMIntegrationService -Name "Guest Service Interface" -VMName $Computer
  
    Set-VMFirmware –Vmname $Computer -EnableSecureBoot Off
  
    Start-VM -Name $Computer
  
    Start-Sleep -Seconds 20
  
    ${NICPRD} = (Get-VMNetworkAdapter -VMName $Computer | Where-Object IPAddresses -Match 192.168.).Name
  
    Rename-VMNetworkAdapter -VMName $Computer -Name ${NICPRD} -NewName "BR-SP-$Enviroment"
  
    Set-VMNetworkAdapter -VMName $Computer -Name "BR-SP-$Enviroment" -MacAddressSpoofing On
  
    Add-VMNetworkAdapter -VMName $Computer -Name "Cluster $Enviroment" -SwitchName "Cluster $Enviroment"
  
    Set-VMNetworkAdapter -VMName $Computer -Name "Cluster $Enviroment" -MacAddressSpoofing On
  
}
  
CreateNano -Computer NanoHost01 -IPPRD 192.168.4.11 -IPGW 192.168.4.1 -Volume "H" -Enviroment 'PRD'
  
CreateNano -Computer NanoHost02 -IPPRD 192.168.6.11 -IPGW 192.168.6.1 -Volume "H" -Enviroment 'DR'


   


While NanoHosts do its first start, let’s configure and promote the DC using PowerShell Direct from AzureVM (host) to DC (guest), without the need of network or IP configured at guest NIC at this time.

 

Function ConfigureDC{
 
    $cred= New-Object System.Management.Automation.PSCredential ("DC\Administrator",$SecurePassword)
 
    # PowerShell Direct
 
    Invoke-Command -VMName DC -Credential $cred {param($SecurePassword)
 
        # Configure Network
 
        Get-NetAdapter | Rename-NetAdapter -NewName "BR-SP-PRD"
 
        New-NetIPAddress -InterfaceAlias "BR-SP-PRD" -IPAddress '192.168.4.20' -AddressFamily IPv4 -PrefixLength 24 -DefaultGateway '192.168.4.1'
 
  
 
        # As PDC of root forest, it need to sync with external source
 
        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" -Name "Type" -Value "NTP" -Force
 
  
 
        # Install Roles and Features
 
        Add-WindowsFeature AD-Domain-Services,RSAT-ADDS,RSAT-ADDS-Tools,RSAT-AD-PowerShell,DNS,RSAT-DNS-Server,RSAT-Hyper-V-Tools,Hyper-V-Powershell,RSAT-Clustering,RSAT-Clustering-Powershell,FS-Data-Deduplication
 
        
 
        # Configure DNS Server and Client
 
        $DNS = Get-DnsServerSetting -All ; $DNS.ListeningIPAddress = @("192.168.4.20") ; Set-DNSServerSetting -InputObject $DNS
 
        Set-DnsClientServerAddress -InterfaceAlias "BR-SP-PRD" -ServerAddresses 192.168.4.20
 
   
 
        # Promote as Domain Controller
 
        Install-ADDSForest -CreateDnsDelegation:$false -DatabasePath "C:\Windows\NTDS" -DomainMode "WinThreshold" -DomainName "bauab.local" -DomainNetbiosName "BAUAB" -ForestMode "WinThreshold" -InstallDns:$true -LogPath "C:\Windows\NTDS" -SysvolPath "C:\Windows\SYSVOL" -Force:$true -SafeModeAdministratorPassword $SecurePassword
 
  
 
    } -ArgumentList ($SecurePassword)
 
    Start-Sleep -Seconds 600
 
}
 
ConfigureDC



 

Once promoted, we need to keep the domain credential for future logons

 

$DomainCred = New-Object System.Management.Automation.PSCredential ("Bauab\Administrator",$SecurePassword)

Now we can configure DC Roles, adjusting Sites, Site Links, Subnets, DNS Zones and Scavenging.

 

Function ConfigureDCRoles{
 
    Invoke-Command -VMName DC -Credential $DomainCred {
 
        Get-ADReplicationSite -Filter {Name -eq "Default-First-Site-Name"} | Rename-ADObject -NewName "BR-SP-PRD"
 
        Get-ADReplicationSite -Filter {Name -eq 'BR-SP-PRD'} | Set-ADReplicationSite -Description 'Production Site'
 
        New-ADReplicationSite -Name BR-SP-DR -Description 'Disaster Recovery Site'
 
        Get-ADReplicationSiteLink -Filter {Name -eq "DEFAULTIPSITELINK"} | Rename-ADObject -NewName "BR-SP-PRD<>BR-SP-DR"
 
        Get-ADReplicationSiteLink -Filter {Name -eq "BR-SP-PRD<>BR-SP-DR"} | Set-ADReplicationSiteLink -SitesIncluded @{Add="BR-SP-DR"}
 
        New-ADReplicationSubnet -Name 192.168.4.0/24 -Description "Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
 
        New-ADReplicationSubnet -Name 192.168.5.0/24 -Description "Cluster Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
 
        New-ADReplicationSubnet -Name 192.168.6.0/24 -Description "Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
 
        New-ADReplicationSubnet -Name 192.168.7.0/24 -Description "Cluster Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
 
        New-ADReplicationSubnet -Name 192.168.8.0/24 -Description "Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
 
        New-ADReplicationSubnet -Name 192.168.9.0/24 -Description "Cluster Production" -Location "São Paulo,Brasil" -Site "BR-SP-PRD"
 
        New-ADReplicationSubnet -Name 192.168.10.0/24 -Description "Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
 
        New-ADReplicationSubnet -Name 192.168.11.0/24 -Description "Cluster Disaster Recovery" -Location "São Paulo,Brasil" -Site "BR-SP-DR"
 
        Add-DnsServerPrimaryZone -DynamicUpdate Secure -Name "192.in-addr.arpa" -ReplicationScope Domain
 
        Set-DnsServerScavenging -ScavengingState $true -ApplyOnAllZones -ScavengingInterval 7.00:00:00
 
        }
 
    }
 
ConfigureDCRoles

 



 

Done, we are ready to join the NanoHosts to Bauab.local domain, configure VMSwitches, adapters and routes to support Guests VMs.

 

Function ConfigureNano ($Computer,$IPProd,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment){
 
    $cred = New-Object System.Management.Automation.PSCredential ("$Computer\Administrator",$SecurePassword)
 
    Invoke-Command -VMName DC -Credential $DomainCred -ScriptBlock {param($Computer,$IPProd,$cred)
 
        # Set NanoHosts as trusted hosts to stablish a WSMan session to copy the djoin file
 
        Set-Item WSMan:\localhost\Client\TrustedHosts $Computer -Concatenate -Force
 
        Add-DnsServerResourceRecordA -IPv4Address $IPProd -Name $Computer -ZoneName bauab.local -AllowUpdateAny -CreatePtr -AgeRecord
 
        $SessionNano = New-PSSession -ComputerName $Computer -Credential $cred
 
        Djoin.exe /provision /domain bauab.local /machine $Computer /savefile C:\$Computer.djoin
 
        Copy-Item C:\$Computer.djoin C:\ -ToSession $SessionNano
 
        } -ArgumentList ($Computer,$IPProd,$cred)
 
    Invoke-Command -VMName $Computer -Credential $cred -ScriptBlock {param($Computer,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment)
 
        Set-VMhost -EnableEnhancedSessionMode $true
 
        New-VMSwitch -Name "Cluster and Client" -SwitchType Internal
 
        New-VMSwitch -Name "Cluster Only" -SwitchType Internal
 
        Get-NetAdapter -Name "vEthernet (Cluster and Client)" | New-NetIPAddress -IPAddress $IPClusterClient -AddressFamily IPv4 -PrefixLength 24
 
        Get-NetAdapter -Name "vEthernet (Cluster Only)" | New-NetIPAddress -IPAddress $IPClusterOnly -AddressFamily IPv4 -PrefixLength 24
 
        Get-NetAdapter | Set-NetIPInterface -Forwarding Enabled
 
        Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -Match '169.' -and $_.AddressState -eq 'Preferred' -and $_.AddressFamily -eq 'IPv4'}).InterfaceAlias) | Rename-NetAdapter -NewName "Cluster $Enviroment" | Set-DnsClient -RegisterThisConnectionsAddress $False
 
        New-NetIPAddress -InterfaceAlias "Cluster $Enviroment" -IPAddress $IPCluster -AddressFamily IPv4 -PrefixLength 24
 
        Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -eq $IPProd -and $_.AddressState -eq 'Preferred'}).InterfaceAlias) | Rename-NetAdapter -NewName "BR-SP-$Enviroment"
 
        If ($Computer -eq 'NanoHost01'){
 
            New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "vEthernet (Cluster Only)" -AddressFamily IPv4 -NextHop 192.168.9.111
 
            New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.5.1
 
            New-NetRoute -DestinationPrefix 192.168.5.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.5.1
 
        }
 
        Else {
 
            New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "vEthernet (Cluster Only)" -AddressFamily IPv4 -NextHop 192.168.11.111
 
            New-NetRoute -DestinationPrefix 192.168.7.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.7.1
 
            New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.7.1
 
        }
 
  
 
        # You can adjust to reflect yours TZ
 
        Set-TimeZone "E. South America Standard Time"
 
  
 
        Djoin.exe /RequestODJ /loadfile C:\$Computer.djoin /windowspath c:\windows /localos
 
        } -ArgumentList ($Computer,$IPCluster,$IPClusterClient,$IPClusterOnly,$DestinationPrefix,$NextHop,$Enviroment)
 
    Stop-VM $Computer ; Start-VM $Computer
 
    }
 
ConfigureNano -Computer "NanoHost01" -IPProd 192.168.4.11 -IPCluster 192.168.5.11 -IPClusterClient 192.168.8.1 -IPClusterOnly 192.168.9.1 -DestinationPrefix 192.168.9.0/24 -NextHop 192.168.9.111 -Enviroment 'PRD'
 
ConfigureNano -Computer "NanoHost02" -IPProd 192.168.6.11 -IPCluster 192.168.7.11 -IPClusterClient 192.168.10.1 -IPClusterOnly 192.168.11.1 -DestinationPrefix 192.168.10.0/24 -NextHop 192.168.10.111 -Enviroment 'DR'
 
Start-Sleep -Seconds 30

 



  

  

Now that we have both NanoHosts joined in domain, we can form the Hyper-V Failover Cluster (NanoCluster) using Storage Pool Direct, to provide the CSV for guest nested computers (Guest01\Guest02). We will set some VHDX as SSD and others as HDD just to try Tiers.



# Form Hyper-V Failover Cluster with Storage Spaces Direct with CSV and Tiers
 
Invoke-Command -VMName DC -Credential $DomainCred {
 
    New-Cluster -Name NanoCluster -Node NanoHost01,NanoHost02 -StaticAddress 192.168.4.10,192.168.6.10 -NoStorage
 
    New-Item C:\Quorum -ItemType Directory
 
    $acl = Get-Acl -Path C:\Quorum
 
    $perm = "bauab\NanoCluster$", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
 
    $rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
 
    $acl.SetAccessRule($rule)
 
    $acl | Set-Acl -Path C:\Quorum
 
    New-SmbShare -Name "Quorum" -Path "C:\Quorum" -FullAccess "bauab\NanoCluster$"
 
    Start-Sleep 60
 
    Set-ClusterQuorum -FileShareWitness \\dc\Quorum -Cluster NanoCluster.bauab.local
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.4.0"}).Name = "BR-SP-PRD"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.5.0"}).Name = "Cluster PRD"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.6.0"}).Name = "BR-SP-DR"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.7.0"}).Name = "Cluster DR"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.8.0"}).Name = "Cluster and Client"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.9.0"}).Name = "Cluster Only"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.10.0"}).Name = "Cluster and Client DR"
 
    (Get-ClusterNetwork -Cluster NanoCluster.bauab.local | Where-Object {$_.Address -eq "192.168.11.0"}).Name = "Cluster Only DR"
 
  
 
    # Enable Storage Pool Direct at cluster
 
    $ClusterSession = New-CimSession -ComputerName NanoCluster.bauab.local
 
    Enable-ClusterStorageSpacesDirect –CimSession $ClusterSession -CacheState Disabled -SkipEligibilityChecks -confirm:$false
 
  
 
    # Set SSD and HDD
 
    Get-PhysicalDisk -CimSession $ClusterSession | Where Size -EQ 10GB | Set-PhysicalDisk -CimSession $ClusterSession -MediaType SSD
 
    Get-PhysicalDisk -CimSession $ClusterSession | Where Size -EQ 60GB | Set-PhysicalDisk -CimSession $ClusterSession -MediaType HDD
 
    Get-PhysicalDisk -CimSession $ClusterSession | select Size,mediatype
 
  
 
    # Create Storage Tiers
 
    Get-StoragePool -CimSession $ClusterSession -FriendlyName "S2D*" | New-StorageTier –FriendlyName Performance –MediaType SSD
 
    Get-StorageTier -CimSession $ClusterSession | FT FriendlyName,Size
 
    Get-StoragePool -CimSession $ClusterSession -FriendlyName "S2D*" | FL Size, AllocatedSize
 
    Get-StorageTierSupportedSize -CimSession $ClusterSession Performance | FT -AutoSize
 
    Get-StorageTierSupportedSize -CimSession $ClusterSession Capacity | FT -AutoSize
 
  
 
    # Create disk and CSV volume with tiers SSD and HDD
 
    $Volume = New-Volume -CimSession $ClusterSession -StoragePoolFriendlyName "S2D*" -FriendlyName VMs -FileSystem CSVFS_NTFS -StorageTierFriendlyNames Performance,Capacity -StorageTierSizes 7GB,56GB -ResiliencySettingName Mirror -WriteCacheSize 1GB -ProvisioningType Thin
 
}

 



    

  
With NanoCluster ready, we proceed to deploy and start two Windows 2016 Datacenter VMs (Guest01 and Guest02) with Failover Cluster, Storage Replica and Deduplication as nested virtual machines. (Level 4)
Function CreateGuest ($Computer,$Enviroment){
 
  
 
    # Generate Windows Server 2016 Datacenter VHDX
 
    $NanoHost = "NanoHost0$($Computer.Substring($Computer.Length -1))"
 
    . ${Drive}:\NanoServer\NanoServerImageGenerator\Convert-WindowsImage.ps1
 
    Convert-WindowsImage -SourcePath "${Drive}:\sources\install.wim" -Edition Datacenter -VHDPath "H:\Hyper-V\$Computer-C.vhdx" -VHDFormat VHDX -DiskLayout UEFI -RemoteDesktopEnable -BCDinVHD VirtualMachine -UnattendPath "H:\Guest0$($Computer.Substring($Computer.Length -1))-Unattend.xml"
 
    Copy-VMFile -FileSource Host -SourcePath "H:\Hyper-V\$Computer-C.vhdx" -Name NanoHost01 -DestinationPath "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -CreateFullPath
 
  
 
    Invoke-Command -VMName DC -Credential $DomainCred {Param($Computer,$NanoHost,$Enviroment)
 
  
 
        # Create and configure the VM at NanoHost
 
        New-VM -ComputerName $NanoHost -Name $Computer -Path C:\ClusterStorage\Volume1\Hyper-V -MemoryStartupBytes 1792MB -Generation 2 -VHDPath "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-C.vhdx" -SwitchName "Cluster and Client"
 
        Set-VMProcessor -ComputerName $NanoHost -VMName $Computer -Count 2
 
        Set-VMMemory -ComputerName $NanoHost -VMName $Computer -DynamicMemoryEnabled $false
 
        Rename-VMNetworkAdapter -ComputerName $NanoHost -VMName $Computer -Name "Network Adapter" -NewName "BR-SP-$Enviroment"
 
  
 
        # Add two Thin disks for storage replica, one for
 
        New-VHD -ComputerName $NanoHost -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Log.vhdx" -SizeBytes 9GB -Dynamic
 
        New-VHD -ComputerName $NanoHost -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Data.vhdx" -SizeBytes 10GB -Dynamic
 
        Add-VMHardDiskDrive -ComputerName $NanoHost -VMName $Computer -ControllerType SCSI -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Log.vhdx" -ControllerNumber 0 -ControllerLocation 1
 
        Add-VMHardDiskDrive -ComputerName $NanoHost -VMName $Computer -ControllerType SCSI -Path "C:\ClusterStorage\Volume1\Hyper-V\$Computer\Virtual Hard Disks\$Computer-Data.vhdx" -ControllerNumber 0 -ControllerLocation 2
 
        
 
        # Add VM to Cluster and start
 
        Add-ClusterVirtualMachineRole -Cluster NanoCluster -VirtualMachine $Computer
 
        Set-ClusterOwnerNode -Cluster NanoCluster -Group $Computer -Owners $NanoHost
 
        Start-VM -ComputerName NanoCluster -Name $Computer
 
                
 
    } -ArgumentList($Computer,$NanoHost,$Enviroment)
 
}
 
CreateGuest -Computer "Guest01" -Enviroment 'PRD'
 
CreateGuest -Computer "Guest02" -Enviroment 'DR'
 
Start-Sleep -Seconds 600
  



After first start of Guests, lets configure its network, roles, features, disks and join to domain.
Function ConfigureGuest($NanoHost,$Guest,$IPPRD,$IPClus,$IPGW,$Enviroment){
 
    $credGuest= New-Object System.Management.Automation.PSCredential ("$Guest\Administrator",$SecurePassword)
 
    Invoke-Command -VMName $NanoHost -Credential $DomainCred {Param($Guest,$IPPRD,$IPClus,$IPGW,$Enviroment,$credGuest)
 
        While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 169.) -eq $null){Start-Sleep 5}
 
        Invoke-Command -VMName $Guest -Credential $credGuest {Param($IPPRD,$IPGW,$Enviroment)
 
            # Configure Client and Cluster Network
 
            Get-NetAdapter | Rename-NetAdapter -NewName "BR-SP-$Enviroment"
 
            New-NetIPAddress -InterfaceAlias "BR-SP-$Enviroment" -IPAddress $IPPRD -AddressFamily IPv4 -PrefixLength 24 -DefaultGateway $IPGW
 
            Set-DnsClientServerAddress -InterfaceAlias "BR-SP-$Enviroment" -ServerAddresses 192.168.4.20
 
            # Install Roles and Features
 
            Add-WindowsFeature Failover-Clustering,FS-Data-Deduplication,Storage-Replica,FS-FileServer -IncludeManagementTools -Restart
 
        } -ArgumentList($IPPRD,$IPGW,$Enviroment)
 
        While ((Get-VM -VMName $Guest).Uptime.Minutes -gt 1) {Start-Sleep 5}
 
        Add-VMNetworkAdapter -VMName $Guest -Name "Cluster $Enviroment" -SwitchName "Cluster Only"
 
        While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 169.) -eq $null){Start-Sleep 5}
 
        Invoke-Command -VMName $Guest -Credential $credGuest {Param($Guest,$IPClus,$Enviroment)
 
            # Configure cluster network
 
            Get-NetAdapter -Name ((Get-NetIPAddress | Where-Object {$_.IPAddress -Match '169.' -and $_.AddressState -eq 'Preferred' -and $_.AddressFamily -eq 'IPv4'}).InterfaceAlias) | Rename-NetAdapter -NewName "Cluster $Enviroment"
 
            Get-NetAdapter -Name "Cluster $Enviroment" | Set-DnsClient -RegisterThisConnectionsAddress $False
 
            New-NetIPAddress -InterfaceAlias "Cluster $Enviroment" -IPAddress $IPClus -AddressFamily IPv4 -PrefixLength 24
 
            If ($Guest -eq 'Guest01'){
 
                New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.9.1
 
                New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster PRD" -AddressFamily IPv4 -NextHop 192.168.9.1
 
                }
 
            Else{
 
                New-NetRoute -DestinationPrefix 192.168.11.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.11.1
 
                New-NetRoute -DestinationPrefix 192.168.9.0/24 -InterfaceAlias "Cluster DR" -AddressFamily IPv4 -NextHop 192.168.11.1
 
                }
 
            # Initialize disks, create partitions, NTFS volumes and enable Deduplication.
 
            Get-Disk | Where size -eq 10GB | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -DriveLetter D  -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Dados" -Confirm:$false
 
            Get-Disk | Where size -eq 9GB | Initialize-Disk -PartitionStyle GPT -PassThru | New-Partition -DriveLetter L -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "Log" -Confirm:$false
 
            Get-Volume | Where size -LE 10GB | Enable-DedupVolume -UsageType Default
 
        } -ArgumentList($Guest,$IPClus,$Enviroment)
 
    } -ArgumentList($Guest,$IPPRD,$IPClus,$IPGW,$Enviroment,$credGuest)
 
    Invoke-Command -VMName DC -Credential $DomainCred {param($Guest,$IPPRD,$credGuest)
 
        # Configure Guest as trusted for WSMan
 
        Set-Item WSMan:\localhost\Client\TrustedHosts $Guest -Concatenate -Force
 
        Add-DnsServerResourceRecordA -IPv4Address $IPPRD -Name $Guest -ZoneName bauab.local -AllowUpdateAny -CreatePtr -AgeRecord
 
        If (Test-Connection -ComputerName $Guest -Quiet)
 
            {
 
            $SessionGuest = New-PSSession -ComputerName $Guest -Credential $credGuest
 
            Djoin.exe /provision /domain bauab.local /machine $Guest /savefile C:\$Guest.djoin
 
            Copy-Item C:\$Guest.djoin C:\ -ToSession $SessionGuest
 
            }
 
        } -ArgumentList ($Guest,$IPPRD,$credGuest)
 
    Invoke-Command -VMName $NanoHost -Credential $DomainCred {Param($Guest,$credGuest)
 
        While ((Get-VMNetworkAdapter -VMName $Guest | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
 
        Invoke-Command -VMName $Guest -Credential $credGuest {Param($Guest)
 
            # Join to bauab.local domain
 
            Djoin.exe /RequestODJ /loadfile C:\$Guest.djoin /windowspath c:\windows /localos
 
        } -ArgumentList($Guest)
 
        Stop-VM $Guest ; Start-VM $Guest
 
    } -ArgumentList($Guest,$credGuest)
 
}
 
ConfigureGuest -NanoHost NanoHost01 -Guest Guest01 -IPPRD 192.168.8.111 -IPClus 192.168.9.111 -IPGW 192.168.8.1 -Enviroment PRD
 
ConfigureGuest -NanoHost NanoHost02 -Guest Guest02 -IPPRD 192.168.10.111 -IPClus 192.168.11.111 -IPGW 192.168.10.1 -Enviroment DR
 
 
  
 
Finally the last step! At this point we will create the GuestCluster, a Stretch Cluster with File Share Role, appropriate for Disaster Recovery sites, using Storage Replica.
  
 
Function ConfigureGuestCluster{
 
    Invoke-Command -VMName NanoHost01 -Credential $DomainCred -ScriptBlock {Param($DomainCred)
 
        While ((Get-VMNetworkAdapter -VMName Guest01 | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
 
        While ((Get-VMNetworkAdapter -VMName Guest02 | Where-Object IPAddresses -Match 192.) -eq $null){Start-Sleep 5}
 
        Invoke-Command -VMName Guest01 -Credential $DomainCred -ScriptBlock {
 
            # Create Stretch Cluster
 
            New-Cluster GuestCluster –Node Guest01,Guest02 –StaticAddress 192.168.8.100,192.168.10.100 -NoStorage
 
        }
 
    } -ArgumentList($DomainCred)
 
    
 
    If (Test-Connection -ComputerName GuestCluster -Quiet)
 
        {
 
        Invoke-Command -VMName DC -Credential $DomainCred -ScriptBlock {
 
            # Create share for GuestCluster
 
            New-Item C:\GuestQuorum -ItemType Directory
 
            $acl = Get-Acl -Path C:\GuestQuorum
 
            $perm = "bauab\GuestCluster$", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
 
            $rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
 
            $acl.SetAccessRule($rule)
 
            $acl | Set-Acl -Path C:\GuestQuorum
 
            New-SmbShare -Name "GuestQuorum" -Path "C:\GuestQuorum" -FullAccess "bauab\GuestCluster$"
 
        }
 
        Invoke-Command -VMName NanoHost01 -Credential $DomainCred -ScriptBlock {Param($DomainCred)
 
            Invoke-Command -VMName Guest01 -Credential $DomainCred -ScriptBlock {
 
                Set-ClusterQuorum -FileShareWitness \\dc\GuestQuorum -Cluster GuestCluster.bauab.local
 
                (Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.8.0"}).Name = "BR-SP-PRD"
 
                (Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.9.0"}).Name = "Cluster PRD"
 
                (Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.10.0"}).Name = "BR-SP-DR"
 
                (Get-ClusterNetwork -Cluster GuestCluster | Where-Object {$_.Address -eq "192.168.11.0"}).Name = "Cluster DR"
 
                Get-ClusterResource -Cluster GuestCluster -Name "Cluster Name" | Set-ClusterParameter HostRecordTTL 300
 
      
 
                # Configure Stretch Cluster site awareness
 
                New-ClusterFaultDomain -Name BR-SP-PRD -Type Site -Description “Production" -Location “BR-SP-PRD"
 
                New-ClusterFaultDomain -Name BR-SP-DR -Type Site -Description “DR" -Location “BR-SP-DR"
 
                Set-ClusterFaultDomain -Name Guest01 -Parent BR-SP-PRD
 
                Set-ClusterFaultDomain -Name Guest02 -Parent BR-SP-DR
 
                (Get-Cluster).PreferredSite=“BR-SP-PRD“
 
                Get-ClusterAvailableDisk -All -Cluster GuestCluster | Add-ClusterDisk
 
  
 
                # Create File Share Role
 
                Add-ClusterFileServerRole -Cluster GuestCluster -Name FS -Storage "Cluster Disk 3" -StaticAddress 192.168.8.101,192.168.10.101
 
                MD D:\Shares\Public
 
                $acl = Get-Acl -Path D:\Shares\Public
 
                $perm = "bauab\Administrator", 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
 
                $rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $perm
 
                $acl.SetAccessRule($rule)
 
                $acl | Set-Acl -Path D:\Shares\Public
 
                New-SmbShare -Name Public -Path D:\Shares\Public -ContinuouslyAvailable $false -FullAccess "bauab\Administrator"
 
                Get-ClusterResource -Cluster GuestCluster -Name FS | Set-ClusterParameter HostRecordTTL 300
 
          
 
                # Enable Storage Replica
 
                New-SRPartnership -SourceComputerName Guest01.bauab.local -SourceRGName rg01 -SourceVolumeName D: -SourceLogVolumeName L: -DestinationComputerName Guest02.bauab.local -DestinationRGName rg02 -DestinationVolumeName D: -DestinationLogVolumeName L:
 
            }
 
        } -ArgumentList($DomainCred)
 
    }
 
}
 
ConfigureGuestCluster

 

We can stop the transcript and retain it as deploy documentation (C:\Transcript.log)

 

Stop-Transcript