AutomatedLabADDS.psm1

#region DC Install Scripts
$adInstallRootDcScriptPre2012 = {
    param (
        [string]$DomainName,
        [string]$Password,
        [string]$ForestFunctionalLevel,
        [string]$DomainFunctionalLevel,
        [string]$NetBiosDomainName
    )

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    $dcpromoAnswerFile = @"
      [DCInstall]
      ; New forest promotion
      ReplicaOrNewDomain=Domain
      NewDomain=Forest
      NewDomainDNSName=$DomainName
      ForestLevel=$($ForestFunctionalLevel)
      DomainNetbiosName=$($NetBiosDomainName)
      DomainLevel=$($DomainFunctionalLevel)
      InstallDNS=Yes
      ConfirmGc=Yes
      CreateDNSDelegation=No
      DatabasePath="C:\Windows\NTDS"
      LogPath="C:\Windows\NTDS"
      SYSVOLPath="C:\Windows\SYSVOL"
      ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
      SafeModeAdminPassword=$Password
      ; Run-time flags (optional)
      ;RebootOnCompletion=No
"@

    
    $VerbosePreference = $using:VerbosePreference
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($DomainName) | Out-Null
    
    $dcpromoAnswerFile | Out-File -FilePath C:\DcpromoAnswerFile.txt -Force
    
    dcpromo /unattend:'C:\DcpromoAnswerFile.txt'
    
    if ($LASTEXITCODE -ge 11)
    {
        throw 'Could not install new domain'
    }
    else
    {
        Write-Verbose -Message 'AD-Domain-Services windows feature installed successfully'
    }
    
    Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord
    
    Write-Verbose -Message 'finished installing the Root Domain Controller'
}

$adInstallRootDcScript2012 = {
    param (
        [string]$DomainName,
        [string]$Password,
        [string]$ForestFunctionalLevel,
        [string]$DomainFunctionalLevel,
        [string]$NetBiosDomainName
    )

    $VerbosePreference = $using:VerbosePreference

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($DomainName) | Out-Null
    
    Write-Verbose -Message "Starting installation of Root Domain Controller on '$(HOSTNAME.EXE)'"
    
    Write-Verbose -Message 'Installing AD-Domain-Services windows feature'
    $result = Install-WindowsFeature AD-Domain-Services, DNS -IncludeManagementTools
    if (-not $result.Success)
    {
        throw 'Could not install AD-Domain-Services windows feature'
    }
    else
    {
        Write-Verbose -Message 'AD-Domain-Services windows feature installed successfully'
    }
    
    Write-Verbose -Message "Creating a new forest named '$DomainName' on the machine '$(HOSTNAME.EXE)'"
    $safeModePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
    $result = Install-ADDSForest -DomainName $DomainName `
    -SafeModeAdministratorPassword $safeModePassword `
    -InstallDNS `
    -DomainMode $DomainFunctionalLevel `
    -Force `
    -ForestMode $ForestFunctionalLevel `
    -DomainNetbiosName $NetBiosDomainName

    if ($result.Status -eq 'Error')
    {
        throw 'Could not install new domain'
    }
    else
    {
        Write-Verbose -Message 'AD-Domain-Services windows feature installed successfully'
    }
    
    Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord
    
    Write-Verbose -Message 'finished installing the Root Domain Controller'
}

$adInstallFirstChildDc2012 = {
    param (
        [string]$NewDomainName,
        [string]$ParentDomainName,
        [System.Management.Automation.PSCredential]$RootDomainCredential,
        [string]$DomainMode,
        [int]$Retries,
        [int]$SecondsBetweenRetries,
        [string]$SiteName = 'Default-First-Site-Name',
        [string]$NetBiosDomainName
    )

    $VerbosePreference = $using:VerbosePreference

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($DomainName) | Out-Null
    
    Write-Verbose -Message "Starting installation of First Child Domain Controller of domain '$NewDomainName' on '$(HOSTNAME.EXE)'"
    Write-Verbose -Message "NewDomainName is '$NewDomainName'"
    Write-Verbose -Message "ParentDomainName is '$ParentDomainName'"
    Write-Verbose -Message "RootCredential UserName is '$($RootDomainCredential.UserName)'"
    Write-Verbose -Message "RootCredential Password is '$($RootDomainCredential.GetNetworkCredential().Password)'"
    Write-Verbose -Message "DomainMode is '$DomainMode'"
    
    Write-Verbose -Message "Trying to reach domain $ParentDomainName"
    while (-not $result -and $count -lt 15)
    {
        $result = Test-Connection -ComputerName $ParentDomainName -Count 1 -Quiet
        
        if ($result)
        {
            Write-Verbose -Message "Domain $ParentDomainName was reachable ($count)"
        }
        else
        {
            Write-Warning "Domain $ParentDomainName was not reachable ($count)"
        }
        
        Start-Sleep -Seconds 1
        
        Clear-DnsClientCache
        
        $count++
    }
    if (-not $result)
    {
        Write-Error "The domain '$ParentDomainName' could not be contacted. Trying DC promotion anyway"
    }
    else
    {
        Write-Verbose -Message "The domain '$ParentDomainName' could be reached"
    }
    
    Write-Verbose -Message 'Installing AD-Domain-Services windows feature'
    $result = Install-Windowsfeature AD-Domain-Services, DNS -IncludeManagementTools
    if (-not $result.Success)
    {
        throw 'Could not install AD-Domain-Services windows feature'
    }
    else
    {
        Write-Verbose -Message 'AD-Domain-Services windows feature installed successfully'
    }
    
    $retriesDone = 0
    do
    {
        Write-Warning "The first try to promote '$(HOSTNAME.EXE)' did not work. The error was '$($result.Message)'. Retrying after $SecondsBetweenRetries seconds. Retry count $retriesDone of $Retries."
        ipconfig.exe /flushdns | Out-Null
        
        try
        {
            #if there is a '.' inside the domain name, it is a new domain tree, otherwise a child domain
            if ($NewDomainName.Contains('.'))
            {
                if (-not $NetBiosDomainName)
                {
                    $NetBiosDomainName = $NewDomainName.Substring(0, $NewDomainName.IndexOf('.'))
                }
                $domainType = 'TreeDomain'
                $createDNSDelegation = $false
            }
            else
            {
                if (-not $NetBiosDomainName)
                {
                    $newDomainNetBiosName = $NewDomainName.ToUpper()
                }
                $domainType = 'ChildDomain'
                $createDNSDelegation = $true
            }

            Start-Sleep -Seconds $SecondsBetweenRetries
            
            $result = Install-ADDSDomain -NewDomainName $NewDomainName `
            -NewDomainNetbiosName $NetBiosDomainName `
            -ParentDomainName $ParentDomainName `
            -SiteName $SiteName `
            -InstallDNS `
            -CreateDnsDelegation:$createDNSDelegation `
            -SafeModeAdministratorPassword $RootDomainCredential.Password `
            -Force `
            -Credential $RootDomainCredential `
            -DomainType $domainType `
            -DomainMode $DomainMode
        }
        catch
        {
            Start-Sleep -Seconds $SecondsBetweenRetries
        }
        
        $retriesDone++
    }
    until ($result.Status -ne 'Error' -or $retriesDone -ge $Retries)
    
    if ($result.Status -eq 'Error')
    {
        Write-Error "Could not install new domain '$NewDomainName' on computer '$(HOSTNAME.EXE)' in $Retries retries. Aborting the promotion of '$(HOSTNAME.EXE)'"
        return
    }
    else
    {
        Write-Verbose -Message "Active Directory installed successfully on computer '$(HOSTNAME.EXE)'"
    }
    
    Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord
    
    Write-Verbose -Message 'Finished installing the first child Domain Controller'
}

$adInstallFirstChildDcPre2012 = {
    param (
        [string]$NewDomainName,
        [string]$ParentDomainName,
        [System.Management.Automation.PSCredential]$RootDomainCredential,
        [string]$DomainMode,
        [int]$Retries,
        [int]$SecondsBetweenRetries,
        [string]$SiteName = 'Default-First-Site-Name',
        [string]$NetBiosDomainName
    )

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($ParentDomainName) | Out-Null
    
    Write-Verbose -Message "Starting installation of First Child Domain Controller of domain '$NewDomainName' on '$(HOSTNAME.EXE)'"
    Write-Verbose -Message "NewDomainName is '$NewDomainName'"
    Write-Verbose -Message "ParentDomainName is '$ParentDomainName'"
    Write-Verbose -Message "RootCredential UserName is '$($RootDomainCredential.UserName)'"
    Write-Verbose -Message "RootCredential Password is '$($RootDomainCredential.GetNetworkCredential().Password)'"
    Write-Verbose -Message "DomainMode is '$DomainMode'"
    
    Write-Verbose -Message "Starting installation of First Child Domain Controller of domain '$NewDomainName' on '$(HOSTNAME.EXE)'"
    
    Write-Verbose -Message "Trying to reach domain $ParentDomainName"
    while (-not $result -and $count -lt 15)
    {
        $result = Test-Connection -ComputerName $ParentDomainName -Count 1 -Quiet
        
        if ($result)
        {
            Write-Verbose -Message "Domain $ParentDomainName was reachable ($count)"
        }
        else
        {
            Write-Warning "Domain $ParentDomainName was not reachable ($count)"
        }
        
        Start-Sleep -Seconds 1
        
        ipconfig.exe /flushdns | Out-Null
        
        $count++
    }
    if (-not $result)
    {
        Write-Error "The domain $ParentDomainName could not be contacted. Trying the DCPromo anyway"
    }
    else
    {
        Write-Verbose -Message "The domain $ParentDomainName could be reached"
    }
    
    Write-Verbose -Message "Credentials prepared for user $logonName"
    
    $tempName = $NewDomainName #using seems not to work in a if statement
    if ($tempName.Contains('.'))
    {
        $domainType = 'Tree'
        if (-not $NetBiosDomainName)
        {
            $NetBiosDomainName = $NewDomainName.Substring(0, $NewDomainName.IndexOf('.')).ToUpper()
        }
    }
    else
    {
        $domainType = 'Child'
        if (-not $NetBiosDomainName)
        {
            $NetBiosDomainName = $NewDomainName.ToUpper()
        }
    }
    
    $dcpromoAnswerFile = @"
      [DCInstall]
      ; New child domain promotion
      ReplicaOrNewDomain=Domain
      NewDomain=$domainType
      ParentDomainDNSName=$($ParentDomainName)
      NewDomainDNSName=$($NetBiosDomainName)
      ChildName=$($NewDomainName)
      DomainNetbiosName=$($NetBiosDomainName)
      DomainLevel=$($DomainMode)
      SiteName=$($SiteName)
      InstallDNS=Yes
      ConfirmGc=Yes
      UserDomain=$($RootDomainCredential.UserName.Split('\')[0])
      UserName=$($RootDomainCredential.UserName.Split('\')[1])
      Password=$($RootDomainCredential.GetNetworkCredential().Password)
      DatabasePath="C:\Windows\NTDS"
      LogPath="C:\Windows\NTDS"
      SYSVOLPath="C:\Windows\SYSVOL"
      ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
      SafeModeAdminPassword=$($RootDomainCredential.GetNetworkCredential().Password)
      ; Run-time flags (optional)
      ; RebootOnCompletion=No
"@

    
    if ($domainType -eq 'Child')
    {
        $dcpromoAnswerFile += ("
                CreateDNSDelegation=Yes
                DNSDelegationUserName=$($RootDomainCredential.UserName)
        DNSDelegationPassword=$($RootDomainCredential.GetNetworkCredential().Password)"
)
    }
    else
    {
        $dcpromoAnswerFile += ('
        CreateDNSDelegation=No'
)
    }

    $dcpromoAnswerFile | Out-File -FilePath C:\DcpromoAnswerFile.txt -Force
    Copy-Item -Path C:\DcpromoAnswerFile.txt -Destination C:\DcpromoAnswerFileBackup.txt
    
    Write-Verbose -Message 'Installing AD-Domain-Services windows feature'
    Write-Verbose -Message "Promoting machine '$(HOSTNAME.EXE)' to domain $($NewDomainName)"
    dcpromo /unattend:'C:\DcpromoAnswerFile.txt'
    
    $retriesDone = 0
    while ($LASTEXITCODE -ge 11 -and $retriesDone -lt $Retries)
    {
        Write-Warning "Promoting the Domain Controller '$(HOSTNAME.EXE)' did not work. The error code was '$LASTEXITCODE'. Retrying after $SecondsBetweenRetries seconds. Retry count $retriesDone of $Retries."
        ipconfig.exe /flushdns | Out-Null
        
        Start-Sleep -Seconds $SecondsBetweenRetries
        
        Copy-Item -Path C:\DcpromoAnswerFileBackup.txt -Destination C:\DcpromoAnswerFile.txt
        dcpromo /unattend:'C:\DcpromoAnswerFile.txt'
        Write-Verbose -Message "Return code of DCPromo was '$LASTEXITCODE'"
        
        $retriesDone++
    }
    
    if ($LASTEXITCODE -ge 11)
    {
        Write-Error "Could not install new domain '$NewDomainName' on computer '$(HOSTNAME.EXE)' in $Retries retries. Aborting the promotion of '$(HOSTNAME.EXE)'"
        return
    }
    else
    {
        Write-Verbose -Message "AD-Domain-Services windows feature installed successfully on computer '$(HOSTNAME.EXE)'"
    }
    
    Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters `
    -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord -ErrorAction Stop
    
    Write-Verbose -Message 'finished installing the the first child Domain Controller'
}

$adInstallDc2012 = {
    param (
        [string]$DomainName,
        [System.Management.Automation.PSCredential]$RootDomainCredential,
        [bool]$IsReadOnly,
        [int]$Retries,
        [int]$SecondsBetweenRetries,
        [string]$SiteName = 'Default-First-Site-Name'
    )

    $VerbosePreference = $using:VerbosePreference

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($DomainName) | Out-Null
    
    Write-Verbose -Message "Starting installation of an additional Domain Controller on '$(HOSTNAME.EXE)'"
    Write-Verbose -Message "DomainName is '$DomainName'"
    Write-Verbose -Message "RootCredential UserName is '$($RootDomainCredential.UserName)'"
    Write-Verbose -Message "RootCredential Password is '$($RootDomainCredential.GetNetworkCredential().Password)'"
    
    #The random delay is very important when promoting more than one Domain Controller.
    Start-Sleep -Seconds (Get-Random -Minimum 60 -Maximum 180)
    
    Write-Verbose -Message "Trying to reach domain $DomainName"
    $count = 0
    while (-not $result -and $count -lt 15)
    {
        Clear-DnsClientCache
        
        $result = Test-Connection -ComputerName $DomainName -Count 1 -Quiet
        
        if ($result)
        {
            Write-Verbose -Message "Domain $DomainName was reachable ($count)"
        }
        else
        {
            Write-Warning "Domain $DomainName was not reachable ($count)"
        }
        
        Start-Sleep -Seconds 1
        
        $count++
    }
    if (-not $result)
    {
        Write-Error "The domain '$DomainName' could not be contacted. Trying DC promotion anyway"
    }
    else
    {
        Write-Verbose -Message "The domain '$DomainName' could be reached"
    }
    
    Write-Verbose -Message 'Installing AD-Domain-Services windows feature'
    Install-Windowsfeature AD-Domain-Services, DNS -IncludeManagementTools
    
    Write-Verbose -Message "Promoting machine '$(HOSTNAME.EXE)' to domain '$DomainName'"
    
    #this is required for RODCs
    $expectedNetbionDomainName = ($DomainName -split '\.')[0]
    
    $param = @{
        DomainName = $DomainName
        SiteName = $SiteName
        SafeModeAdministratorPassword = $RootDomainCredential.Password
        Force = $true
        Credential = $RootDomainCredential
    }


    if ($IsReadOnly)
    {
        $param.Add('ReadOnlyReplica', $true)
        
        $param.Add('DenyPasswordReplicationAccountName',
            @('BUILTIN\Administrators',
                'BUILTIN\Server Operators',
                'BUILTIN\Backup Operators',
                'BUILTIN\Account Operators',
        "$expectedNetbionDomainName\Denied RODC Password Replication Group"))
        
        $param.Add('AllowPasswordReplicationAccountName', @("$expectedNetbionDomainName\Allowed RODC Password Replication Group"))
    }
    else
    {
        $param.Add('CreateDnsDelegation', $false)
    }

    try
    {
        $result = Install-ADDSDomainController @param
    }
    catch
    {
        Write-Error -Message 'Error occured in installation of Domain Controller. Error:'
        Write-Error -Message $_
    }
    
    Write-Verbose -Message 'First attempt of installation finished'
    $retriesDone = 0
    while ($result.Status -eq 'Error' -and $retriesDone -lt $Retries)
    {
        Write-Warning "The first try to promote '$(HOSTNAME.EXE)' did not work. The error was '$($result.Message)'. Retrying after $SecondsBetweenRetries seconds. Retry count $retriesDone of $Retries."
        ipconfig.exe /flushdns | Out-Null
        
        Start-Sleep -Seconds $SecondsBetweenRetries
        try
        {
            $result = Install-ADDSDomainController @param
        }
        catch { }
        
        $retriesDone++
    }
    
    if ($result.Status -eq 'Error')
    {
        Write-Error "The problem could not be solved in $Retries retries. Aborting the promotion of '$(HOSTNAME.EXE)'"
        return
    }
    
    Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord
    
    Write-Verbose -Message 'finished installing the Root Domain Controller'
}

$adInstallDcPre2012 = {
    param (
        [string]$DomainName,
        [System.Management.Automation.PSCredential]$RootDomainCredential,
        [bool]$IsReadOnly,
        [int]$Retries,
        [int]$SecondsBetweenRetries,
        [string]$SiteName = 'Default-First-Site-Name'
    )

    $VerbosePreference = $using:VerbosePreference

    Start-Transcript -Path C:\DeployDebug\ALDCPromo.log
    
    ([WMIClass]'Win32_NetworkAdapterConfiguration').SetDNSSuffixSearchOrder($DomainName) | Out-Null
    
    Write-Verbose -Message "Starting installation of an additional Domain Controller on '$(HOSTNAME.EXE)'"
    Write-Verbose -Message "DomainName is '$DomainName'"
    Write-Verbose -Message "RootCredential UserName is '$($RootDomainCredential.UserName)'"
    Write-Verbose -Message "RootCredential Password is '$($RootDomainCredential.GetNetworkCredential().Password)'"
    
    #$type is required for the pre-2012 installatioon
    if ($IsReadOnly)
    {
        $type = 'ReadOnlyReplica'
    }
    else
    {
        $type = 'Replica'
    }
    
    Start-Sleep -Seconds (Get-Random -Minimum 60 -Maximum 180)
    
    $dcpromoAnswerFile = @"
      [DCInstall]
      ; Read-Only Replica DC promotion
      ReplicaOrNewDomain=$type
      ReplicaDomainDNSName=$DomainName
      SiteName=$SiteName
      InstallDNS=Yes
      ConfirmGc=Yes
      UserDomain=$($RootDomainCredential.UserName.Split('\')[0])
      UserName=$($RootDomainCredential.UserName.Split('\')[1])
      Password=$($RootDomainCredential.GetNetworkCredential().Password)
      DatabasePath="C:\Windows\NTDS"
      LogPath="C:\Windows\NTDS"
      SYSVOLPath="C:\Windows\SYSVOL"
      ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
      SafeModeAdminPassword=$($RootDomainCredential.GetNetworkCredential().Password)
      ; RebootOnCompletion=No
"@

    
    if ($type -eq 'ReadOnlyReplica')
    {
        $dcpromoAnswerFile += ('
                PasswordReplicationDenied="BUILTIN\Administrators"
                PasswordReplicationDenied="BUILTIN\Server Operators"
                PasswordReplicationDenied="BUILTIN\Backup Operators"
                PasswordReplicationDenied="BUILTIN\Account Operators"
                PasswordReplicationDenied="{0}\Denied RODC Password Replication Group"
        PasswordReplicationAllowed="{0}\Allowed RODC Password Replication Group"'
 -f $DomainName)
    }
    else
    {
        $dcpromoAnswerFile += ('
        CreateDNSDelegation=No'
)
    }
    
    $dcpromoAnswerFile | Out-File -FilePath C:\DcpromoAnswerFile.txt -Force
    #The backup file is required to be able to start dcpromo a second time as the passwords are getting
    #removed by dcpromo
    Copy-Item -Path C:\DcpromoAnswerFile.txt -Destination C:\DcpromoAnswerFileBackup.txt
    
    #For debug
    Copy-Item -Path C:\DcpromoAnswerFile.txt -Destination C:\DeployDebug\DcpromoAnswerFile.txt
    
    Write-Verbose -Message "Starting installation of an additional Domain Controller on '$(HOSTNAME.EXE)'"
    
    Write-Verbose -Message "Trying to reach domain $DomainName"
    $count = 0
    while (-not $result -and $count -lt 15)
    {
        ipconfig.exe /flushdns | Out-Null
        
        $result = Test-Connection -ComputerName $DomainName -Count 1 -Quiet
        
        if ($result)
        {
            Write-Verbose -Message "Domain $DomainName was reachable ($count)"
        }
        else
        {
            Write-Warning "Domain $DomainName was not reachable ($count)"
        }
        
        Start-Sleep -Seconds 1
        
        $count++
    }
    if (-not $result)
    {
        Write-Error "The domain $DomainName could not be contacted. Trying the DCPromo anyway"
    }
    else
    {
        Write-Verbose -Message "The domain $DomainName could be reached"
    }
    
    Write-Verbose -Message 'Installing AD-Domain-Services windows feature'
    Write-Verbose -Message "Promoting machine '$(HOSTNAME.EXE)' to domain '$($DomainName)'"
    Copy-Item -Path C:\DcpromoAnswerFileBackup.txt -Destination C:\DcpromoAnswerFile.txt
    dcpromo /unattend:'C:\DcpromoAnswerFile.txt'
    Write-Verbose -Message "Return code of DCPromo was '$LASTEXITCODE'"
    
    $retriesDone = 0
    while ($LASTEXITCODE -ge 11 -and $retriesDone -lt $Retries)
    {
        Write-Warning "The first try to promote '$(HOSTNAME.EXE)' did not work. The error code was '$LASTEXITCODE'. Retrying after $SecondsBetweenRetries seconds. Retry count $retriesDone of $Retries."
        ipconfig.exe /flushdns | Out-Null
        
        Start-Sleep -Seconds $SecondsBetweenRetries
        
        Copy-Item -Path C:\DcpromoAnswerFileBackup.txt -Destination C:\DcpromoAnswerFile.txt
        dcpromo /unattend:'C:\DcpromoAnswerFile.txt'
        Write-Verbose -Message "Return code of DCPromo was '$LASTEXITCODE'"
        
        $retriesDone++
    }
    
    if ($LASTEXITCODE -ge 11)
    {
        Write-Error "The problem could not be solved in $Retries retries. Aborting the promotion of '$(HOSTNAME.EXE)'"
        return
    }
    else
    {
        Write-Verbose -Message 'finished installing the Domain Controller'
        
        Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters `
        -Name 'Repl Perform Initial Synchronizations' -Value 0 -Type DWord -ErrorAction Stop
    }
}
#endregion DC Install Scripts

#region Install-LabRootDcs
function Install-LabRootDcs
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param (
        [int]$DcPromotionRestartTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionRestartAfterDcpromo,
        
        [int]$AdwsReadyTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionAdwsReady,
        
        [switch]$CreateCheckPoints
    )
    
    Write-LogFunctionEntry
    
    $lab = Get-Lab
    if (-not $lab.Machines)
    {
        Write-LogFunctionExitWithError -Message 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first'
        return
    }
    
    $machines = Get-LabVM -Role RootDC
    
    if (-not $machines)
    {
        Write-Warning -Message "There is no machine with the role 'RootDC'"
        Write-LogFunctionExit
        return
    }
    
    
    Write-ScreenInfo -Message 'Waiting for machines to start up' -NoNewline
    Start-LabVM -RoleName RootDC -Wait -DoNotUseCredSsp -ProgressIndicator 10 -PostDelaySeconds 5
    
    #Determine if any machines are already installed as Domain Controllers and exclude these
    $machinesAlreadyInstalled = foreach ($machine in $machines)
    {
        if (Test-LabADReady -ComputerName $machine)
        {
            $machine.Name
        }
    }
    
    $machines = $machines | Where-Object Name -notin $machinesAlreadyInstalled
    foreach ($m in $machinesAlreadyInstalled)
    {
        Write-ScreenInfo -Message "Machine '$m' is already a Domain Controller. Skipping this machine." -Type Warning
    }
    
    $jobs = @()
    if ($machines)
    {
        Invoke-LabCommand -ComputerName $machines -ActivityName "Create folder 'C:\DeployDebug' for debug info" -NoDisplay -ScriptBlock {
            New-Item -ItemType Directory -Path 'c:\DeployDebug' -ErrorAction SilentlyContinue | Out-Null

            $acl = Get-Acl -Path C:\DeployDebug
            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule('Everyone', 'Read', 'ObjectInherit', 'None', 'Allow')
            $acl.AddAccessRule($rule)
            Set-Acl -Path C:\DeployDebug -AclObject $acl
        } -DoNotUseCredSsp
        
        foreach ($machine in $machines)
        {
            $rootDcRole = $machine.Roles | Where-Object Name -eq 'RootDC'
        
            $version = (New-Object -TypeName AutomatedLab.OperatingSystem -ArgumentList ($machine.OperatingSystem)).Version
            if ($version -le 6.1)
            {
                #Pre 2012
                $scriptblock = $adInstallRootDcScriptPre2012
                $forestFunctionalLevel = [int][AutomatedLab.ActiveDirectoryFunctionalLevel]$rootDcRole.Properties.ForestFunctionalLevel
                $domainFunctionalLevel = [int][AutomatedLab.ActiveDirectoryFunctionalLevel]$rootDcRole.Properties.DomainFunctionalLevel
            }
            else
            {
                $scriptblock = $adInstallRootDcScript2012
                $forestFunctionalLevel = $rootDcRole.Properties.ForestFunctionalLevel
                $domainFunctionalLevel = $rootDcRole.Properties.DomainFunctionalLevel
            }

            if ($rootDcRole.Properties.ContainsKey('NetBiosDomainName'))
            {
                $netBiosDomainName = $rootDcRole.Properties.NetBiosDomainName
            }
            else
            {
                $netBiosDomainName = $machine.DomainName.Substring(0, $machine.DomainName.IndexOf('.'))
            }

            #only print out warnings if verbose logging is enabled
            $WarningPreference = $VerbosePreference
            
            $jobs += Invoke-LabCommand -ComputerName $machine.Name `
            -ActivityName "Install Root DC ($($machine.name))" `
            -AsJob `
            -UseLocalCredential `
            -DoNotUseCredSsp `
            -PassThru `
            -NoDisplay `
            -ScriptBlock $scriptblock `
            -ArgumentList $machine.DomainName,
            $machine.InstallationUser.Password,
            $forestFunctionalLevel,
            $domainFunctionalLevel,
            $netBiosDomainName
        }
        
        
        Write-ScreenInfo -Message 'Waiting for Root Domain Controllers to complete installation of Active Directory and restart' -NoNewLine
        
        $machinesToStart = @()
        $machinesToStart += Get-LabMachine -Role FirstChildDC, DC
        #starting machines in a multi net environment may not work
        if (-not (Get-LabMachine -Role Routing))
        {
            $machinesToStart += Get-LabMachine | Where-Object { -not $_.IsDomainJoined }
        }

        Wait-LabVMRestart -ComputerName $machines.Name -StartMachinesWhileWaiting $machinesToStart -DoNotUseCredSsp -ProgressIndicator 30 -TimeoutInMinutes $DcPromotionRestartTimeout -ErrorAction Stop -MonitorJob $jobs
        
        Write-ScreenInfo -Message 'Root Domain Controllers have now restarted. Waiting for Active Directory to start up' -NoNewLine
        
        Wait-LabVM -ComputerName $machines -DoNotUseCredSsp -TimeoutInMinutes 30 -ProgressIndicator 30 -NoNewLine
        Wait-LabADReady -ComputerName $machines -TimeoutInMinutes $AdwsReadyTimeout -ErrorAction Stop -ProgressIndicator 30 -NoNewLine
        
        Invoke-LabCommand -ActivityName 'Configuring DNS Forwarders on Azure Root DCs' -ComputerName $machines -ScriptBlock {
            dnscmd /ResetForwarders 168.63.129.16
        } -DoNotUseCredSsp -NoDisplay
        
        #Create reverse lookup zone (forest scope)
        foreach ($network in ((Get-LabVirtualNetworkDefinition).AddressSpace.IpAddress.AddressAsString))
        {
            Invoke-LabCommand -ComputerName $machines[0] -ActivityName 'Create reverse lookup zone' -NoDisplay -ScriptBlock `
            {
                param
                (
                    [string]$ip
                )
                
                $zoneName = "$($ip.split('.')[2]).$($ip.split('.')[1]).$($ip.split('.')[0]).in-addr.arpa"
                dnscmd . /ZoneAdd "$zoneName" /DsPrimary /DP /forest
                dnscmd . /Config "$zoneName" /AllowUpdate 2
                ipconfig.exe -registerdns
            } -ArgumentList $network
        }
        

        #Make sure the specified installation user will be forest admin
        $cmd = {
            $PSDefaultParameterValues = @{
                '*-AD*:Server' = $env:COMPUTERNAME
            }
            
            $user = Get-ADUser -Identity ([System.Security.Principal.WindowsIdentity]::GetCurrent().User) -Server localhost
        
            Add-ADGroupMember -Identity 'Domain Admins' -Members $user -Server localhost
            Add-ADGroupMember -Identity 'Enterprise Admins' -Members $user -Server localhost
            Add-ADGroupMember -Identity 'Schema Admins' -Members $user -Server localhost
        }
        Invoke-LabCommand -ComputerName $machines -ActivityName 'Make installation user Domain Admin' -NoDisplay -ScriptBlock $cmd -ErrorAction SilentlyContinue
    
        #Non-domain-joined machine are not registered in DNS hence cannot be found from inside the lab.
        #creating an A record for each non-domain-joined machine in the first forst solves that.
        #Every non-domain-joined machine get the first forest's name as the primary DNS domain.
        $dnsCmd = Get-LabMachine -All | Where-Object { -not $_.IsDomainJoined -and $_.IpV4Address } | ForEach-Object {
            "dnscmd /recordadd $(@($rootDomains)[0]) $_ A $($_.IpV4Address)`n"
        }
        $dnsCmd += "Restart-Service -Name DNS -WarningAction SilentlyContinue`n"    
        Invoke-LabCommand -ComputerName $machines[0] -ActivityName 'Register non domain joined machines in DNS' -NoDisplay -ScriptBlock ([scriptblock]::Create($dnsCmd))

        Invoke-LabCommand -ComputerName $machines -ActivityName 'Add flat domain name DNS record to speed up start of gpsvc in 2016' -NoDisplay -ScriptBlock {
            $machine = $args[0] | Where-Object { $_.Name -eq $env:COMPUTERNAME }
            dnscmd localhost /recordadd $env:USERDNSDOMAIN $env:USERDOMAIN A $machine.IpV4Address
        } -ArgumentList $machines

        Restart-LabVM -ComputerName $machines -Wait
        Wait-LabADReady -ComputerName $machines
        
        Enable-LabVMRemoting -ComputerName $machines
        
        #Restart the Network Location Awareness service to ensure that Windows Firewall Profile is 'Domain'
        Restart-ServiceResilient -ComputerName $machines -ServiceName nlasvc -NoNewLine
        
        #DNS client configuration is change by DCpromo process. Change this back
        Reset-DNSConfiguration -ComputerName (Get-LabMachine -Role RootDC) -ProgressIndicator 30 -NoNewLine
        
        #Need to make sure that A records for domain is registered
        Write-Verbose -Message 'Restarting DNS and Netlogon service on Root Domain Controllers'
        $jobs = @()
        foreach ($dc in (@(Get-LabMachine -Role RootDC)))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 5 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 5 -NoDisplay -NoNewLine
        Write-ProgressIndicatorEnd
        
        foreach ($machine in $machines)
        {
            $dcRole = $machine.Roles | Where-Object Name -like '*DC'
            
            if ($dcRole.Properties.SiteName)
            {
                New-LabADSite -ComputerName $machine -SiteName $dcRole.Properties.SiteName -SiteSubnet $dcRole.Properties.SiteSubnet
                Move-LabDomainController -ComputerName $machine -SiteName $dcRole.Properties.SiteName
            }
        }        
        
        
        if ($CreateCheckPoints)
        {
            foreach ($machine in ($machines | Where-Object HostType -eq 'HyperV'))
            {
                Checkpoint-LWVM -ComputerName $machine -SnapshotName 'Post DC Promotion'
            }
        }
    }
    else
    {
        Write-ScreenInfo -Message 'All Root Domain Controllers are already installed' -Type Warning -TaskEnd
        return
    }
    Get-PSSession | Where-Object State -ne Disconnected | Remove-PSSession

    #this sections is required to join all machines to the domain. This is happening when starting the machines, that's why all machines are started.
    $domains = $machines.DomainName
    $filterScript = { 'RootDC' -notin $_.Roles.Name -and 'FirstChildDC' -notin $_.Roles.Name -and 'DC' -notin $_.Roles.Name -and
        -not $_.HasDomainJoined -and $_.DomainName -in $domains -and $_.HostType -eq 'Azure' }
    $retries = 3

    while ((Get-LabVM | Where-Object -FilterScript $filterScript) -or $retries -le 0 )
    {
        $machinesToJoin = Get-LabVM | Where-Object -FilterScript $filterScript

        Write-ScreenInfo "Restarting the $($machinesToJoin.Count) machines to complete the domain join of ($($machinesToJoin.Name -join ', ')). Retries remaining = $retries"
        Restart-LabVM -ComputerName $machinesToJoin -Wait
        $retries--
    }
    
    Write-LogFunctionExit
}
#endregion Install-LabRootDcs

#region Install-LabFirstChildDcs
function Install-LabFirstChildDcs
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param (
        [int]$DcPromotionRestartTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionRestartAfterDcpromo,
        
        [int]$AdwsReadyTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionAdwsReady,
        
        [switch]$CreateCheckPoints
    )
    
    Write-LogFunctionEntry
    
    $lab = Get-Lab
    if (-not $lab.Machines)
    {
        Write-LogFunctionExitWithError -Message 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first'
        return
    }
    
    $machines = $lab.Machines | Where-Object { $_.Roles.Name -contains 'FirstChildDC' }
    if (-not $machines)
    {
        Write-Warning -Message "There is no machine with the role 'FirstChildDC'"
        Write-LogFunctionExit
        return
    }
    
    Write-ScreenInfo -Message 'Waiting for machines to start up' -NoNewline
    Start-LabVM -RoleName FirstChildDC -Wait -DoNotUseCredSsp -ProgressIndicator 15 -PostDelaySeconds 5
    
    #Determine if any machines are already installed as Domain Controllers and exclude these
    $machinesAlreadyInstalled = foreach ($machine in $machines)
    {
        if (Test-LabADReady -ComputerName $machine)
        {
            $machine.Name
        }
    }
    
    $machines = $machines | Where-Object Name -notin $machinesAlreadyInstalled
    foreach ($m in $machinesAlreadyInstalled)
    {
        Write-ScreenInfo -Message "Machine '$m' is already a Domain Controller. Skipping this machine." -Type Warning
    }
    
    if ($machines)
    {
        Invoke-LabCommand -ComputerName $machines -ActivityName "Create folder 'C:\DeployDebug' for debug info" -NoDisplay -ScriptBlock {
            New-Item -ItemType Directory -Path 'c:\DeployDebug' -ErrorAction SilentlyContinue | Out-Null

            $acl = Get-Acl -Path C:\DeployDebug
            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule('Everyone', 'Read', 'ObjectInherit', 'None', 'Allow')
            $acl.AddAccessRule($rule)
            Set-Acl -Path C:\DeployDebug -AclObject $acl
        } -DoNotUseCredSsp
        
        $jobs = @()
        foreach ($machine in $machines)
        {
            $dcRole = $machine.Roles | Where-Object Name -eq 'FirstChildDc'
            
            $parentDomainName = $dcRole.Properties['ParentDomain']
            $newDomainName = $dcRole.Properties['NewDomain']
            $domainFunctionalLevel = $dcRole.Properties['DomainFunctionalLevel']
            $parentDomain = $lab.Domains | Where-Object Name -eq $parentDomainName
            
            #get the root domain to build the root domain credentials
            if (-not $parentDomain)
            {
                throw "New domain '$newDomainName' could not be installed. The root domain ($parentDomainName) could not be found in the lab"
            }
            $rootCredential = $parentDomain.GetCredential()
            
            #if there is a '.' inside the domain name, it is a new domain tree, otherwise a child domain hence we need to
            #create a DNS zone for the child domain in the parent domain
            if ($NewDomainName.Contains('.'))
            {
                $parentDc = Get-LabMachine -Role RootDC, FirstChildDC | Where-Object DomainName -eq $ParentDomainName
                Write-Verbose -Message "Setting up a new domain tree hence creating a stub zone on Domain Controller '$($parentDc.Name)'"
                
                $cmd = "dnscmd . /zoneadd $NewDomainName /dsstub $((Get-LabMachine -Role RootDC,FirstChildDC,DC | Where-Object DomainName -eq $NewDomainName).IpV4Address -join ', ') /dp /forest"
                
                Invoke-LabCommand -ScriptBlock ([scriptblock]::Create($cmd)) -ComputerName $parentDc -NoDisplay -ActivityName 'Add DNS zones'
                Invoke-LabCommand -ScriptBlock {Restart-Service Dns} -ComputerName $parentDc -NoDisplay -ActivityName 'Restart DNS'
            }
            
            Write-Verbose -Message 'Invoking script block for DC installation and promotion'
            $version = (New-Object -TypeName AutomatedLab.OperatingSystem -ArgumentList ($machine.OperatingSystem)).Version
            if ($version -le 6.1)
            {
                $scriptBlock = $adInstallFirstChildDcPre2012
                $domainFunctionalLevel = [int][AutomatedLab.ActiveDirectoryFunctionalLevel]$domainFunctionalLevel
            }
            else
            {
                $scriptBlock = $adInstallFirstChildDc2012
            }            
            
            
            $siteName = 'Default-First-Site-Name'

            if ($dcRole.Properties.SiteName)
            {
                $siteName = $dcRole.Properties.SiteName
                New-LabADSite -ComputerName $machine -SiteName $siteName -SiteSubnet $dcRole.Properties.SiteSubnet
            }

            #only print out warnings if verbose logging is enabled
            $WarningPreference = $VerbosePreference

            $jobs += Invoke-LabCommand -ComputerName $machine.Name `
            -ActivityName "Install FirstChildDC ($($machine.Name))" `
            -AsJob `
            -PassThru `
            -UseLocalCredential `
            -NoDisplay `
            -ScriptBlock $scriptBlock `
            -ArgumentList $newDomainName,
            $parentDomainName,
            $rootCredential,
            $domainFunctionalLevel,
            7,
            120,
            $siteName,
            $dcRole.Properties.NetBiosDomainName
        }
        
        
        Write-ScreenInfo -Message 'Waiting for First Child Domain Controllers to complete installation of Active Directory and restart' -NoNewline
        
        $domains = @((Get-LabMachine -Role RootDC).DomainName)
        foreach ($domain in $domains)
        {
            if (Get-LabMachine -Role DC | Where-Object DomainName -eq $domain)
            {
                $domains = $domain | Where-Object { $_ -ne $domain }
            }
        }

        $machinesToStart = @()
        $machinesToStart += Get-LabMachine -Role DC
        #starting machines in a multi net environment may not work
        if (-not (Get-LabMachine -Role Routing))
        {
            $machinesToStart += Get-LabMachine | Where-Object { -not $_.IsDomainJoined }
            $machinesToStart += Get-LabMachine | Where-Object DomainName -in $domains
        }
        
        Wait-LabVMRestart -ComputerName $machines.name -StartMachinesWhileWaiting $machinesToStart -ProgressIndicator 45 -TimeoutInMinutes $DcPromotionRestartTimeout -ErrorAction Stop -MonitorJob $jobs
        
        Write-ScreenInfo -Message 'First Child Domain Controllers have now restarted. Waiting for Active Directory to start up' -NoNewLine
        
        #Wait a little to be able to connect in first attempt
        Wait-LWLabJob -Job (Start-Job -Name 'Delay waiting for machines to be reachable' -ScriptBlock {Start-Sleep -Seconds 60}) -ProgressIndicator 20 -NoDisplay -NoNewLine
        
        Wait-LabVM -ComputerName $machines -TimeoutInMinutes 30 -ProgressIndicator 20 -NoNewLine
        
        Wait-LabADReady -ComputerName $machines -TimeoutInMinutes $AdwsReadyTimeout -ErrorAction Stop -ProgressIndicator 20 -NoNewLine
        
        
        #Make sure the specified installation user will be domain admin
        $cmd = {
            $PSDefaultParameterValues = @{
                '*-AD*:Server' = $env:COMPUTERNAME
            }
            
            $user = Get-ADUser -Identity ([System.Security.Principal.WindowsIdentity]::GetCurrent().User)
            
            Add-ADGroupMember -Identity 'Domain Admins' -Members $user
        }
        Invoke-LabCommand -ComputerName $machines -ActivityName 'Make installation user Domain Admin' -NoDisplay -ScriptBlock $cmd -ErrorAction SilentlyContinue

        Invoke-LabCommand -ComputerName $machines -ActivityName 'Add flat domain name DNS record to speed up start of gpsvc in 2016' -NoDisplay -ScriptBlock {
            $machine = $args[0] | Where-Object { $_.Name -eq $env:COMPUTERNAME }
            dnscmd localhost /recordadd $env:USERDNSDOMAIN $env:USERDOMAIN A $machine.IpV4Address
        } -ArgumentList $machines

        Restart-LabVM -ComputerName $machines -Wait
        Wait-LabADReady -ComputerName $machines
        
        Enable-LabVMRemoting -ComputerName $machines
        
        #Restart the Network Location Awareness service to ensure that Windows Firewall Profile is 'Domain'
        Restart-ServiceResilient -ComputerName $machines -ServiceName nlasvc -NoNewLine
        
        #DNS client configuration is change by DCpromo process. Change this back
        Reset-DNSConfiguration -ComputerName (Get-LabMachine -Role FirstChildDC) -ProgressIndicator 20 -NoNewLine
        
        
        Write-Verbose -Message 'Restarting DNS and Netlogon services on Root and Child Domain Controllers and triggering replication'
        $jobs = @()
        foreach ($dc in (@(Get-LabMachine -Role RootDC)))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 20 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 20 -NoDisplay -NoNewLine
        $jobs = @()
        foreach ($dc in (@(Get-LabMachine -Role FirstChildDC)))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 20 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 20 -NoDisplay -NoNewLine
        Write-ProgressIndicatorEnd
        
        if ($CreateCheckPoints)
        {
            foreach ($machine in ($machines | Where-Object HostType -eq 'HyperV'))
            {
                Checkpoint-LWVM -ComputerName $machine -SnapshotName 'Post DC Promotion'
            }
        }
    }
    else
    {
        Write-ScreenInfo -Message 'All First Child Domain Controllers are already installed' -Type Warning -TaskEnd
        return
    }
    
    Get-PSSession | Where-Object State -ne Disconnected | Remove-PSSession

    #this sections is required to join all machines to the domain. This is happening when starting the machines, that's why all machines are started.
    $domains = $machines.DomainName
    $filterScript = { 'RootDC' -notin $_.Roles.Name -and 'FirstChildDC' -notin $_.Roles.Name -and 'DC' -notin $_.Roles.Name -and
        -not $_.HasDomainJoined -and $_.DomainName -in $domains -and $_.HostType -eq 'Azure' }
    $retries = 3

    while ((Get-LabVM | Where-Object -FilterScript $filterScript) -or $retries -le 0 )
    {
        $machinesToJoin = Get-LabVM | Where-Object -FilterScript $filterScript

        Write-ScreenInfo "Restarting the $($machinesToJoin.Count) machines to complete the domain join of ($($machinesToJoin.Name -join ', ')). Retries remaining = $retries"
        Restart-LabVM -ComputerName $machinesToJoin -Wait
        $retries--
    }
    
    Write-LogFunctionExit
}
#endregion Install-LabFirstChildDcs

#region Install-LabDcs
function Install-LabDcs
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param (
        [int]$DcPromotionRestartTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionRestartAfterDcpromo,
        
        [int]$AdwsReadyTimeout = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_DcPromotionAdwsReady,
        
        [switch]$CreateCheckPoints
    )
    
    Write-LogFunctionEntry
    
    $lab = Get-Lab
    if (-not $lab.Machines)
    {
        Write-LogFunctionExitWithError -Message 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first'
        return
    }
    
    $machines = Get-LabMachine -Role DC
    
    if (-not $machines)
    {
        Write-Warning -Message "There is no machine with the role 'DC'"
        Write-LogFunctionExit
        return
    }
    
    Write-ScreenInfo -Message 'Waiting for machines to start up' -NoNewline
    Start-LabVM -RoleName DC -Wait -DoNotUseCredSsp -ProgressIndicator 15 -PostDelaySeconds 5

    #Determine if any machines are already installed as Domain Controllers and exclude these
    $machinesAlreadyInstalled = foreach ($machine in $machines)
    {
        if (Test-LabADReady -ComputerName $machine)
        {
            $machine.Name
        }
    }
    
    $machines = $machines | Where-Object Name -notin $machinesAlreadyInstalled
    foreach ($m in $machinesAlreadyInstalled)
    {
        Write-ScreenInfo -Message "Machine '$m' is already a Domain Controller. Skipping this machine." -Type Warning
    }
    
    if ($machines)
    {
        Invoke-LabCommand -ComputerName $machines -ActivityName "Create folder 'C:\DeployDebug' for debug info" -NoDisplay -ScriptBlock {
            New-Item -ItemType Directory -Path 'c:\DeployDebug' -ErrorAction SilentlyContinue | Out-Null

            $acl = Get-Acl -Path C:\DeployDebug
            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule('Everyone', 'Read', 'ObjectInherit', 'None', 'Allow')
            $acl.AddAccessRule($rule)
            Set-Acl -Path C:\DeployDebug -AclObject $acl
        } -DoNotUseCredSsp
        
        $rootDcs = Get-LabMachine -Role RootDC
        $childDcs = Get-LabMachine -Role FirstChildDC
        
        $jobs = @()
        
        foreach ($machine in $machines)
        {
            $dcRole = $machine.Roles | Where-Object Name -like '*DC'
            
            $isReadOnly = $dcRole.Properties['IsReadOnly']
            if ($isReadOnly -eq 'true')
            {
                $isReadOnly = $true
            }
            else
            {
                $isReadOnly = $false
            }
            
            #get the root domain to build the root domain credentials
            $parentDc = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $lab.GetParentDomain($machine.DomainName).Name
            $parentCredential = $parentDc.GetCredential((Get-Lab))
            
            Write-Verbose -Message 'Invoking script block for DC installation and promotion'
            $version = (New-Object -TypeName AutomatedLab.OperatingSystem -ArgumentList ($machine.OperatingSystem)).Version
            if ($version -le 6.1)
            {
                $scriptblock = $adInstallDcPre2012
            }
            else
            {
                $scriptblock = $adInstallDc2012
            }            
            
            
            $siteName = 'Default-First-Site-Name'

            if ($dcRole.Properties.SiteName)
            {
                $siteName = $dcRole.Properties.SiteName
                New-LabADSite -ComputerName $machine -SiteName $siteName -SiteSubnet $dcRole.Properties.SiteSubnet
            }
            
            #only print out warnings if verbose logging is enabled
            $WarningPreference = $VerbosePreference

            $jobs += Invoke-LabCommand -ComputerName $machine `
            -ActivityName "Install DC ($($machine.name))" `
            -AsJob `
            -PassThru `
            -UseLocalCredential `
            -NoDisplay `
            -ScriptBlock $scriptblock `
            -ArgumentList $machine.DomainName,
            $parentCredential,
            $isReadOnly,
            7,
            120,
            $siteName
        }
        
        Write-ScreenInfo -Message 'Waiting for additional Domain Controllers to complete installation of Active Directory and restart' -NoNewLine
        
        $domains = (Get-LabMachine -Role DC).DomainName

        $machinesToStart = @()
        #starting machines in a multi net environment may not work
        if (-not (Get-LabMachine -Role Routing))
        {
            $machinesToStart += Get-LabMachine | Where-Object { -not $_.IsDomainJoined }
            $machinesToStart += Get-LabMachine | Where-Object DomainName -notin $domains
        }

        Wait-LabVMRestart -ComputerName $machines -StartMachinesWhileWaiting $machinesToStart -TimeoutInMinutes $DcPromotionRestartTimeout -ErrorAction Stop -ProgressIndicator 60 -MonitorJob $jobs
        
        Write-ScreenInfo -Message 'Additional Domain Controllers have now restarted. Waiting for Active Directory to start up' -NoNewLine
        
        #Wait a little to be able to connect in first attempt
        Wait-LWLabJob -Job (Start-Job -Name 'Delay waiting for machines to be reachable' -ScriptBlock {Start-Sleep -Seconds 60}) -ProgressIndicator 20 -NoDisplay -NoNewLine
        
        Wait-LabVM -ComputerName $machines -TimeoutInMinutes 30 -ProgressIndicator 20 -NoNewLine
        
        Wait-LabADReady -ComputerName $machines -TimeoutInMinutes $AdwsReadyTimeout -ErrorAction Stop -ProgressIndicator 20 -NoNewLine
        
        #Restart the Network Location Awareness service to ensure that Windows Firewall Profile is 'Domain'
        Restart-ServiceResilient -ComputerName $machines -ServiceName nlasvc -NoNewLine
        
        Enable-LabVMRemoting -ComputerName $machines
        
        #DNS client configuration is change by DCpromo process. Change this back
        Reset-DNSConfiguration -ComputerName (Get-LabMachine -Role DC) -ProgressIndicator 20 -NoNewLine
        
        
        Write-Verbose -Message 'Restarting DNS and Netlogon services on all Domain Controllers and triggering replication'
        $jobs = @()
        foreach ($dc in (Get-LabMachine -Role RootDC))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 20 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 20 -NoDisplay -NoNewLine
        $jobs = @()
        foreach ($dc in (Get-LabMachine -Role FirstChildDC))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 20 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 20 -NoDisplay -NoNewLine
        $jobs = @()
        foreach ($dc in (Get-LabMachine -Role DC))
        {
            $jobs += Sync-LabActiveDirectory -ComputerName $dc -ProgressIndicator 20 -AsJob -Passthru
        }
        Wait-LWLabJob -Job $jobs -ProgressIndicator 20 -NoDisplay -NoNewLine
        Write-ProgressIndicatorEnd
        
        if ($CreateCheckPoints)
        {
            foreach ($machine in ($machines | Where-Object HostType -eq 'HyperV'))
            {
                Checkpoint-LWVM -ComputerName $machine -SnapshotName 'Post DC Promotion'
            }
        }
    }
    else
    {
        Write-ScreenInfo -Message 'All additional Domain Controllers are already installed' -Type Warning -TaskEnd
        return
    }
    
    Get-PSSession | Where-Object State -ne Disconnected | Remove-PSSession
    
    Write-LogFunctionExit
}
#endregion Install-LabDcs

#region Wait-LabADReady
function Wait-LabADReady
{
    # .ExternalHelp AutomatedLab.Help.xml
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName,
        
        [int]$TimeoutInMinutes = 15,

        [int]$ProgressIndicator,

        [switch]$NoNewLine
    )
    
    Write-LogFunctionEntry
    
    $start = Get-Date
    
    $machines = Get-LabMachine -ComputerName $ComputerName
    $machines | Add-Member -Name AdRetries -MemberType NoteProperty -Value 2 -Force
    
    $ProgressIndicatorTimer = (Get-Date)
    do
    {
        foreach ($machine in $machines)
        {
            if ($machine.AdRetries)
            {
                $adReady = Test-LabADReady -ComputerName $machine
                
                if ($DebugPreference)
                {
                    Write-Debug -Message "Return '$adReady' from '$($machine)'"
                }
                
                if ($adReady)
                {
                    $machine.AdRetries--
                }
            }
            
            if (-not $machine.AdRetries)
            {
                Write-Verbose -Message "Active Directory is now ready on Domain Controller '$machine'"
            }
            else
            {
                Write-Debug "Active Directory is NOT ready yet on Domain Controller: '$machine'"
            }
        }
        
        if (((Get-Date) - $ProgressIndicatorTimer).TotalSeconds -ge $ProgressIndicator)
        {
            if ($ProgressIndicator)
            {
                Write-ProgressIndicator
            }
            $ProgressIndicatorTimer = (Get-Date)
        }
        
        if ($DebugPreference)
        {
            $machines | ForEach-Object {
                Write-Debug -Message "$($_.Name.PadRight(18)) $($_.AdRetries)"
            }
        }
        
        if ($machines | Where-Object { $_.AdRetries })
        {
            Start-Sleep -Seconds 3
        }
    }
    until (($machines.AdRetries | Measure-Object -Maximum).Maximum -le 0 -or (Get-Date).AddMinutes(-$TimeoutInMinutes) -gt $start)
    
    if ($ProgressIndicator -and -not $NoNewLine)
    {
        Write-ProgressIndicatorEnd
    }
    
    if (($machines.AdRetries | Measure-Object -Maximum).Maximum -le 0)
    {
        Write-Verbose -Message 'Domain Controllers specified are now ready:'
        Write-Verbose -Message ($machines.Name -join ', ')
    }
    else
    {
        $machines | Where-Object { $_.AdRetries -gt 0 } | ForEach-Object {
            Write-Error -Message "Timeout occured waiting for Active Directory to be ready on Domain Controller: $_. Retry count is $($_.AdRetries)" -TargetObject $_
        }
    }
    
    Write-LogFunctionExit
}
#endregion Wait-LabADReady

#region Test-LabADReady
function Test-LabADReady
{
    # .ExternalHelp AutomatedLab.Help.xml
    param (
        [Parameter(Mandatory)]
        [string]$ComputerName
    )
    
    Write-LogFunctionEntry
    
    $machine = Get-LabMachine -ComputerName $ComputerName
    if (-not $machine)
    {
        Write-Error "The machine '$ComputerName' could not be found in the lab"
        return
    }
    
    $adReady = Invoke-LabCommand -ComputerName $machine -ActivityName GetAdwsServiceStatus -ScriptBlock {
     
        if ((Get-Service -Name ADWS).Status -eq 'Running')
        {
            try
            {
                $env:ADPS_LoadDefaultDrive = 0
                $WarningPreference = 'SilentlyContinue'
                Import-Module -Name ActiveDirectory -ErrorAction Stop
                [bool](Get-ADDomainController -Server $env:COMPUTERNAME -ErrorAction SilentlyContinue)
            }
            catch
            {
                $false
            }
        }
        
    } -DoNotUseCredSsp -PassThru -NoDisplay  -ErrorAction SilentlyContinue
    
    [bool]$adReady
    
    Write-LogFunctionExit
}
#endregion Test-LabADReady

#region Reset-DNSConfiguration
function Reset-DNSConfiguration
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param
    (
        [string[]]$ComputerName,

        [int]$ProgressIndicator,

        [switch]$NoNewLine
    )

    Write-LogFunctionEntry
    
    $machines = Get-LabMachine -ComputerName $ComputerName

    $jobs = @()
    foreach ($machine in $machines)
    {
        $jobs += Invoke-LabCommand -ComputerName $machine -ActivityName 'Reset DNS client configuration to match specified DNS configuration' -ScriptBlock `
        {
            param
            (
                $DnsServers
            )
            $AdapterNames = (Get-WmiObject -Namespace Root\CIMv2 -Class Win32_NetworkAdapter | Where-Object {$_.PhysicalAdapter}).NetConnectionID
            foreach ($AdapterName in $AdapterNames)
            {
                netsh.exe interface ipv4 set dnsservers "$AdapterName" static $DnsServers primary
                
                "netsh interface ipv6 delete dnsservers '$AdapterName' all"
                netsh.exe interface ipv6 delete dnsservers "$AdapterName" all
            } 
        } -AsJob -PassThru -NoDisplay -ArgumentList $machine.DNSServers
    }
    
    Wait-LWLabJob -Job $jobs -NoDisplay -Timeout 30 -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine

    Write-LogFunctionExit
}
#endregion Reset-DNSConfiguration

#region Sync-LabActiveDirectory
function Sync-LabActiveDirectory
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [string[]]$ComputerName,
        
        [int]$ProgressIndicator,

        [switch]$AsJob,
        
        [switch]$Passthru
    )
    
    Write-LogFunctionEntry

    $machines = Get-LabMachine -ComputerName $ComputerName
    $lab = Get-Lab

    if (-not $machines)
    {
        Write-Error "The machine '$ComputerName' could not be found in the current lab"
        return
    }
    
    foreach ($machine in $machines)
    {
        if (-not $machine.DomainName)
        {
            Write-Verbose -Message 'The machine is not domain joined hence AD replication cannot be triggered'
            return
        }

        #region Force Replication Scriptblock
        $adForceReplication = {
            $VerbosePreference = $using:VerbosePreference
        
            ipconfig.exe -flushdns
        
            Write-Verbose -Message 'Getting list of DCs'
            $dcs = repadmin.exe /viewlist *
            Write-Verbose -Message "List: '$($dcs -join ', ')'"
            (Get-Date -Format 'yyyy-MM-dd hh:mm:ss') | Add-Content -Path c:\DeployDebug\DCList.log -Force
            $dcs | Add-Content -Path c:\DeployDebug\DCList.log

            foreach ($dc in $dcs)
            {
                if ($dc)
                {
                    $dcName = $dc.Split()[2]
                    Write-Verbose -Message "Executing 'repadmin.exe /SyncAll /Ae $dcname'"
                    $result = repadmin.exe /SyncAll /Ae $dcName
                    (Get-Date -Format 'yyyy-MM-dd hh:mm:ss') | Add-Content -Path "c:\DeployDebug\Syncs-$($dcName).log" -Force
                    $result | Add-Content -Path "c:\DeployDebug\Syncs-$($dcName).log"
                }
            }
            Write-Verbose -Message "Executing 'repadmin.exe /ReplSum'"
            $result = repadmin.exe /ReplSum
            $result | Add-Content -Path c:\DeployDebug\repadmin.exeResult.log
        
            Restart-Service -Name DNS -WarningAction SilentlyContinue
        
            ipconfig.exe /registerdns
        
            Write-Verbose -Message 'Getting list of DCs'
            $dcs = repadmin.exe /viewlist *
            Write-Verbose -Message "List: '$($dcs -join ', ')'"
            (Get-Date -Format 'yyyy-MM-dd hh:mm:ss') | Add-Content -Path c:\DeployDebug\DCList.log -Force
            $dcs | Add-Content -Path c:\DeployDebug\DCList.log
            foreach ($dc in $dcs)
            {
                if ($dc)
                {
                    $dcName = $dc.Split()[2]
                    Write-Verbose -Message "Executing 'repadmin.exe /SyncAll /Ae $dcname'"
                    $result = repadmin.exe /SyncAll /Ae $dcName
                    (Get-Date -Format 'yyyy-MM-dd hh:mm:ss') | Add-Content -Path "c:\DeployDebug\Syncs-$($dcName).log" -Force
                    $result | Add-Content -Path "c:\DeployDebug\Syncs-$($dcName).log"
                }
            }
            Write-Verbose -Message "Executing 'repadmin.exe /ReplSum'"
            $result = repadmin.exe /ReplSum
            $result | Add-Content -Path c:\DeployDebug\repadmin.exeResult.log
        
            ipconfig.exe /registerdns
        
            Restart-Service -Name DNS -WarningAction SilentlyContinue
        
            #for debugging
            #dnscmd /zoneexport $env:USERDNSDOMAIN "c:\DeployDebug\$($env:USERDNSDOMAIN).txt"
        }
        #endregion Force Replication Scriptblock
    
        Invoke-LabCommand -ActivityName "Performing ipconfig /registerdns on '$ComputerName'" `
        -ComputerName $ComputerName -ScriptBlock { ipconfig.exe /registerdns } -NoDisplay
    
        if ($AsJob)
        {
            $job = Invoke-LabCommand -ActivityName "Triggering replication on '$ComputerName'" -ComputerName $ComputerName -ScriptBlock $adForceReplication -AsJob -Passthru -NoDisplay 

            if ($PassThru)
            {
                $job
            }
        }
        else
        {
            $result = Invoke-LabCommand -ActivityName "Triggering replication on '$ComputerName'" -ComputerName $ComputerName -ScriptBlock $adForceReplication -Passthru -NoDisplay

            if ($PassThru)
            {
                $result
            }
        }
    }
    
    Write-LogFunctionExit
}
#endregion Sync-LabActiveDirectory

#region Add-LabDomainAdmin
function Add-LabDomainAdmin
{
    # .ExternalHelp AutomatedLab.Help.xml
    param(
        [Parameter(Mandatory)]
        [string]$Name,

        [Parameter(Mandatory)]
        [System.Security.SecureString]$Password,

        [string]$ComputerName
    )

    $cmd = {
        param(
            [Parameter(Mandatory)]
            [string]$Name,

            [Parameter(Mandatory)]
            [System.Security.SecureString]$Password
        )

        $server = 'localhost'

        $user = New-ADUser -Name $Name -AccountPassword $Password -Enabled $true -PassThru

        Add-ADGroupMember -Identity 'Domain Admins' -Members $user -Server $server

        try
        {
            Add-ADGroupMember -Identity 'Enterprise Admins' -Members $user -Server $server
            Add-ADGroupMember -Identity 'Schema Admins' -Members $user -Server $server
        }
        catch
        {
            #if adding the groups failed, this is executed propably in a child domain
        }
    }

    Invoke-LabCommand -ComputerName $ComputerName -ActivityName AddDomainAdmin -ScriptBlock $cmd -ArgumentList $Name, $Password
}
#endregion Add-LabDomainAdmin

#region New-LabADSubnet
function New-LabADSubnet
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param(
        [switch]$PassThru
    )
  
    Write-LogFunctionEntry
  
    $createSubnetScript = {
        param(
            $NetworkInfo
        )
        
        $PSDefaultParameterValues = @{
            '*-AD*:Server' = $env:COMPUTERNAME
        }
    
        #$defaultSite = Get-ADReplicationSite -Identity Default-First-Site-Name -Server localhost
        $ctx = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest)
        $defaultSite = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::FindByName($ctx, 'Default-First-Site-Name') 
        $subnetName = "$($NetworkInfo.Network)/$($NetworkInfo.MaskLength)"
    
        try
        {
            $subnet = Get-ADReplicationSubnet -Identity $subnetName -Server localhost
        }
        catch { }
    
        if (-not $subnet)
        {
            #New-ADReplicationSubnet seems to have a bug and reports Access Denied.
            #New-ADReplicationSubnet -Name $subnetName -Site $defaultSite -PassThru -Server localhost
            $subnet = New-Object System.DirectoryServices.ActiveDirectory.ActiveDirectorySubnet($ctx, $subnetName)
            $subnet.Site = $defaultSite
            $subnet.Save()
        }
    }
  
    $machines = Get-LabMachine -Role RootDC, FirstChildDC
    $lab = Get-Lab
  
    foreach ($machine in $machines)
    {
        $ipAddress = ($machine.IpAddress -split '/')[0]
        $subnetMask = ($machine.IpAddress -split '/')[1] | ConvertTo-Mask
    
        $networkInfo = Get-NetworkSummary -IPAddress $ipAddress -SubnetMask $subnetMask
        Write-Verbose -Message "Creating subnet '$($networkInfo.Network)' with mask '$($networkInfo.MaskLength)' on machine '$($machine.Name)'"
    
        #if the machine is not a Root Domain Controller
        if (-not ($machine.Roles | Where-Object { $_.Name -eq 'RootDC'}))
        {
            $rootDc = $machines | Where-Object { $_.Roles.Name -eq 'RootDC' -and $_.DomainName -eq $lab.GetParentDomain($machine.DomainName) }
        }
        else
        {
            $rootDc = $machine
        }
    
        if ($rootDc)
        {
            Invoke-LabCommand -ComputerName $rootDc -ActivityName 'Create AD Subnet' -NoDisplay `
            -ScriptBlock $createSubnetScript -AsJob -ArgumentList $networkInfo
        }
        else
        {
            Write-ScreenInfo -Message 'Root domain controller could not be found, cannot Create AD Subnet automatically.' -Type Warning
        }
    }
  
    Write-LogFunctionExit
}
#endregion New-LabADSubnet

#region function New-LabADSite
function New-LabADSite
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [string]$SiteName,
        
        [Parameter(Mandatory)]
        [string]$SiteSubnet
    )
    
    Write-LogFunctionEntry
    
    $machine = Get-LabMachine -ComputerName $ComputerName
    $dcRole = $machine.Roles | Where-Object Name -like '*DC'
    
    if (-not $dcRole)
    {
        Write-Verbose "No Domain Controller roles found on computer '$Computer'"
        return
    }
    
    $forest = $dcRole.Properties.ParentDomain
            
    Write-Verbose -Message "Try to find domain root machine for '$ComputerName'"
    $domainRootMachine = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $machine.DomainName
    if (-not $domainRootMachine)
    {
        Write-Verbose -Message "No RootDC found in same domain as '$ComputerName'. Looking for FirstChildDC instead"

        $domainRootMachine = Get-LabMachine -role FirstChildDC | Where-Object DomainName -eq $machine.DomainName
    }

    #if no domain tree
    if (-not ($forest))
    {
        Write-Verbose -Message "Forest of Domain root machine '$domainRootMachine' not found"

        $domain = $machine.DomainName
        if ($domain.Split('.').Count -le 2)
        {
            $forest = $domain
            Write-Verbose -Message "Forest set to '$forest' based on domain name of '$ComputerName' only has 2 names"
        }
        else
        {
            $forest = $domain.Split('.', 2)[-1]
            Write-Verbose -Message "Forest set to '$forest' based on two last names of domain name '$domain' of '$ComputerName'"
        }
    }
    
    $rootDcForMachine = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $forest
    if (-not $rootDcForMachine)
    {
        $rootDcForMachine = Get-LabMachine -Role FirstChildDC | Where-Object DomainName -eq $forest
        $dcRole = $rootDcForMachine.Roles | Where-Object Name -eq 'FirstChild'
        $forest = $dcRole.Properties.ParentDomain
    }
    
    
    $result = Invoke-LabCommand -ComputerName $rootDcForMachine -NoDisplay -PassThru -ScriptBlock `
    {
        param
        (
            $ComputerName, $SiteName, $SiteSubnet
        )
        
        $PSDefaultParameterValues = @{
            '*-AD*:Server' = $env:COMPUTERNAME
        }
        
        Write-Verbose -Message "For computer '$ComputerName', create AD site '$SiteName' in subnet '$SiteSubnet'"
        
        if (-not (Get-ADReplicationSite -Filter "Name -eq '$SiteName'"))
        {
            Write-Verbose -Message "SiteName '$SiteName' does not exist. Attempting to create it now"
            New-ADReplicationSite -Name $SiteName
        }
        else
        {
            Write-Verbose -Message "SiteName '$SiteName' already exists"
        }
        
        
        if (-not (Get-ADReplicationSubNet -Filter "Name -eq '$SiteSubnet'"))
        {
            Write-Verbose -Message "SiteSubnet does not exist. Attempting to create it now and associate it with site '$SiteName'"
            New-ADReplicationSubnet -Name $SiteSubnet -Site $SiteName -Location $SiteName
        }
        else
        {
            Write-Verbose -Message "SiteSubnet '$SiteSubnet' already exists"
        }

        
        $sites = (Get-AdReplicationSite -Filter 'Name -ne "Default-First-Name-Site"').Name
        foreach ($site in $sites)
        {
            $otherSites = $sites | Where-Object { $_ -ne $site }
            foreach ($otherSite in $otherSites)
            {
                if (-not (Get-ADReplicationSiteLink -Filter "(name -eq '[$site]-[$otherSite]')") -and -not 
                (Get-ADReplicationSiteLink -Filter "(name -eq '[$otherSite]-[$Site]')"))
                {
                    Write-Verbose -Message "Site link '[$site]-[$otherSite]' does not exist. Creating it now"
                    New-ADReplicationSiteLink -Name "[$site]-[$otherSite]" `
                    -SitesIncluded $site, $otherSite `
                    -Cost 100 `
                    -ReplicationFrequencyInMinutes 15 `
                    -InterSiteTransportProtocol IP `
                    -OtherAttributes @{ 'options' = 5 }
                }
            }
        }
    } -ArgumentList $ComputerName, $SiteName, $SiteSubnet
    
    
    Write-LogFunctionExit
}
#endregion function New-LabADSite

#region function Move-LabDomainController
function Move-LabDomainController
{
    # .ExternalHelp AutomatedLab.Help.xml
    [cmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [string]$SiteName
    )
    
    Write-LogFunctionEntry
    
    
    $dcRole = (Get-LabMachine -ComputerName $ComputerName).Roles | Where-Object Name -like '*DC'
    
    if (-not $dcRole)
    {
        Write-Verbose "No Domain Controller roles found on computer '$ComputerName'"
        return
    }
    
    $forest = $dcRole.Properties.ParentDomain
    $machine = Get-LabMachine -ComputerName $ComputerName
            
    Write-Verbose -Message "Try to find domain root machine for '$ComputerName'"
    $domainRootMachine = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $machine.DomainName
    if (-not $domainRootMachine)
    {
        Write-Verbose -Message "No RootDC found in same domain as '$ComputerName'. Looking for FirstChildDC instead"

        $domainRootMachine = Get-LabMachine -Role FirstChildDC | Where-Object DomainName -eq $machine.DomainName
    }

    #if no domain tree
    if (-not $forest)
    {
        Write-Verbose -Message "Forest of Domain root machine '$domainRootMachine' not found"

        $domain = $machine.DomainName
        if ($domain.Split('.').Count -le 2)
        {
            $forest = $domain
            Write-Verbose -Message "Forest set to '$forest' based on domain name of '$ComputerName' only has 2 names"
        }
        else
        {
            $forest = $domain.Split('.', 2)[-1]
            Write-Verbose -Message "Forest set to '$forest' based on two last names of domain name '$domain' of '$ComputerName'"
        }
    }
    
    $rootDcForMachine = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $forest
    if (-not $rootDcForMachine)
    {
        $rootDcForMachine = Get-LabMachine -Role FirstChildDC | Where-Object DomainName -eq $forest
        $dcRole = $rootDcForMachine.Roles | Where-Object Name -eq 'FirstChild'
        $forest = $dcRole.Properties.ParentDomain
    }
    
    
    $result = Invoke-LabCommand -ComputerName $rootDcForMachine -NoDisplay -PassThru -ScriptBlock `
    {
        param
        (
            $ComputerName, $SiteName
        )
        
        $searchBase = (Get-ADRootDSE).ConfigurationNamingContext
        
        Write-Verbose -Message "Moving computer '$ComputerName' to AD site '$SiteName'"
        $targetSite = Get-ADObject -Filter 'ObjectClass -eq "site" -and CN -eq $SiteName' -SearchBase $searchBase
        Write-Verbose -Message "Target site: '$targetSite'"
        $dc =  Get-ADObject -Filter "ObjectClass -eq 'server' -and Name -eq '$ComputerName'" -SearchBase $searchBase
        Write-Verbose -Message "DC distinguished name: '$dc'"
        Move-ADObject -Identity $dc -TargetPath "CN=Servers,$($TargetSite.DistinguishedName)"

    } -ArgumentList $ComputerName, $SiteName
    
    Write-LogFunctionExit
}
#endregion function Move-LabDomainController

#region Install-LabDnsForwarder
function Install-LabDnsForwarder
{
    # .ExternalHelp AutomatedLab.Help.xml
    $forestNames = (Get-LabMachine -Role RootDC).DomainName
    if (-not $forestNames)
    {
        Write-Error 'Could not get forest names from the lab'
        return
    }

    $forwarders = Get-FullMesh -List $forestNames

    foreach ($forwarder in $forwarders)
    {
        $targetMachine = Get-LabMachine -Role RootDC | Where-Object { $_.DomainName -eq $forwarder.Source }
        $masterServers = Get-LabMachine -Role DC,RootDC,FirstChildDC | Where-Object { $_.DomainName -eq $forwarder.Destination }
    
        $cmd = @"
            `$hostname = hostname.exe
            Write-Verbose "Creating a DNS forwarder on server '$hostname'. Forwarder name is '$($forwarder.Destination)' and target DNS server is '$($masterServers.IpV4Address)'..."
            #Add-DnsServerConditionalForwarderZone -ReplicationScope Forest -Name $($forwarder.Destination) -MasterServers $($masterServers.IpV4Address)
            dnscmd . /zoneadd $($forwarder.Destination) /forwarder $($masterServers.IpV4Address)
            Write-Verbose '...done'
"@


        Invoke-LabCommand -ComputerName $targetMachine -ScriptBlock ([scriptblock]::Create($cmd)) -NoDisplay
    }
    
    $azureRootDCs = Get-LabMachine -Role RootDC | Where-Object HostType -eq Azure
    if ($azureRootDCs)
    {
        Invoke-LabCommand -ActivityName 'Configuring DNS Forwarders on Azure Root DCs' -ComputerName $azureRootDCs -ScriptBlock {
            dnscmd /ResetForwarders 168.63.129.16
        }
    }
}
#region Install-LabDnsForwarder

#region Install-LabADDSTrust
function Install-LabADDSTrust
{
    # .ExternalHelp AutomatedLab.Help.xml
    $forestNames = (Get-LabMachine -Role RootDC).DomainName
    if (-not $forestNames)
    {
        Write-Error 'Could not get forest names from the lab'
        return
    }

    $forwarders = Get-FullMesh -List $forestNames

    foreach ($forwarder in $forwarders)
    {
        $targetMachine = Get-LabMachine -Role RootDC | Where-Object { $_.DomainName -eq $forwarder.Source }
        $masterServers = Get-LabMachine -Role DC,RootDC,FirstChildDC | Where-Object { $_.DomainName -eq $forwarder.Destination }
    
        $cmd = @"
            `$hostname = hostname.exe
            Write-Verbose "Creating a DNS forwarder on server '$hostname'. Forwarder name is '$($forwarder.Destination)' and target DNS server is '$($masterServers.IpV4Address)'..."
            #Add-DnsServerConditionalForwarderZone -ReplicationScope Forest -Name $($forwarder.Destination) -MasterServers $($masterServers.IpV4Address)
            dnscmd . /zoneadd $($forwarder.Destination) /forwarder $($masterServers.IpV4Address)
            Write-Verbose '...done'
"@


        Invoke-LabCommand -ComputerName $targetMachine -ScriptBlock ([scriptblock]::Create($cmd)) -NoDisplay
    }

    Get-LabMachine -Role RootDC | ForEach-Object {
        Invoke-LabCommand -ComputerName $_ -NoDisplay -ScriptBlock {
            Write-Verbose -Message "Replicating forest `$(`$env:USERDNSDOMAIN)..."
        
            Write-Verbose -Message 'Getting list of DCs'
            $dcs = repadmin.exe /viewlist *
            Write-Verbose -Message "List: '$($dcs -join ', ')'"

            foreach ($dc in $dcs)
            {
                if ($dc)
                {
                    $dcName = $dc.Split()[2]
                    Write-Verbose -Message "Executing 'repadmin.exe /SyncAll /Ae $dcname'"
                    $result = repadmin.exe /SyncAll /AeP $dcName
                }
            }        
            Write-Verbose '...done'
        }
    }

    $rootDcs = Get-LabMachine -Role RootDC
    $trustMesh = Get-FullMesh -List $forestNames -OneWay

    foreach ($rootDc in $rootDcs)
    {
        $trusts = $trustMesh | Where-Object { $_.Source -eq $rootDc.DomainName }

        Write-Verbose "Creating trusts on machine $($rootDc.Name)"
        foreach ($trust in $trusts)
        {
            $domainAdministrator = ((Get-Lab).Domains | Where-Object { $_.Name -eq ($rootDcs | Where-Object { $_.DomainName -eq $trust.Destination }).DomainName }).Administrator

            $cmd = @"
                `$thisForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
 
                `$otherForestCtx = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext(
                    [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest,
                    '$($trust.Destination)',
                    '$($domainAdministrator.UserName)',
                    '$($domainAdministrator.Password)')
                `$otherForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest(`$otherForestCtx)
 
                Write-Verbose "Creating forest trust between forests '`$(`$thisForest.Name)' and '`$(`$otherForest.Name)'"
 
                `$thisForest.CreateTrustRelationship(
                    `$otherForest,
                    [System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional
                )
 
                Write-Verbose 'Forest trust created'
"@

        
            Invoke-LabCommand -ComputerName $rootDc -ScriptBlock ([scriptblock]::Create($cmd)) -NoDisplay
        }
    }
}
#region Install-LabADDSTrust