Search
StarWind is a hyperconverged (HCI) vendor with focus on Enterprise ROBO, SMB & Edge

Automating Hyper-V Replica Configuration for Diverse Environments

  • December 26, 2024
  • 40 min read
Storage and Virtualization Engineer. Volodymyr has broad experience in solution architecture and data protection, backed by a technical background in applied physics.
Storage and Virtualization Engineer. Volodymyr has broad experience in solution architecture and data protection, backed by a technical background in applied physics.

Disaster recovery is a critical aspect of IT infrastructure management. The ability to quickly recover from unexpected outages can minimize downtime and data loss. Hyper-V Replica is a powerful tool for creating and maintaining copies of virtual machines (VMs) for this purpose. However, configuring Hyper-V Replica across different network environments, such as domains and workgroups, can be complex and time-consuming.

I’ve been working with Hyper-V Replica for a while now, and I’ve often found myself performing the same set of actions to get it up and running, especially when dealing with workgroups or mixed environments. It’s not too bad when you’re working within a domain, but things get tricky when you have to replicate between hosts in different workgroups, or between a domain and a workgroup. I couldn’t find a good way to automate this, so I decided to create a PowerShell script to handle it. This script is designed to simplify and automate the initial configuration of Hyper-V Replica in various scenarios, including:

  • Replication between hosts within the same Active Directory forest
  • Replication between hosts in different workgroups
  • Mixed replication scenarios (domain to workgroup, and vice-versa)

The challenge of diverse environments

While configuring Hyper-V Replica is relatively straightforward within an Active Directory forest, replicating between workgroups or mixed environments requires additional steps. These steps include:

  • Establishing trust between hosts
  • Managing certificates for authentication
  • Configuring firewall rules
  • Modifying host files

These manual configurations can be error-prone and time-consuming, especially when dealing with multiple hosts.

The PowerShell solution

To address these challenges, I developed a PowerShell script to automate the Hyper-V Replica configuration process. This script streamlines the setup, reduces the risk of errors, and saves administrators valuable time.

Key features

The script offers the following key features:

  • Automated configuration: The script automates all necessary steps, including configuring TrustedHosts, managing certificates, and setting up replication.
  • Support for diverse environments: The script supports replication between hosts in Active Directory forests, workgroups, and mixed environments.
  • Error handling: The script includes robust error handling to identify and report issues, ensuring a smooth configuration process.
  • User-friendly wizard: The script presents a wizard-like interface, prompting you for necessary information and guiding you through the process.
  • VM selection: The script allows you to replicate all VMs or select specific ones.
  • Logging and error reporting: The script provides detailed logging and error messages to help troubleshoot any issues.

How the Script works

The script performs the following actions:

  1. Initial setup:
    • Preserves the current TrustedHosts settings.
    • Verifies IP addresses and credentials for both hosts.
    • Verifies that Hyper-V is installed and running on both hosts.
    • Configures firewall rules on both hosts.
  2. Certificate management (for workgroups and mixed environments):
    • Creates self-signed certificates on both hosts (valid for two years).
    • Exports the certificates.
    • Copies the certificates to the other host.
    • Imports the certificates on both hosts.
  3. Host file configuration (for workgroups and mixed environments):
    • Adds entries for both hosts in the hosts file on each host.
  4. Replication configuration:
    • Configures Hyper-V Replica on both the source and replica hosts, using either Kerberos (for Active Directory replication) or certificates (for workgroups and mixed environments).
  5. Finalization:
    • Displays a success or failure message.
    • Restores the original TrustedHosts settings.
    • Removes temporary files and certificates.

Using the Script

Here’s how to use the script:

  1. Copy the script below and save it as VM_Replication.ps1.
  2. Run PowerShell as an administrator.
  3. Execute the script: .\VM_Replication.ps1
  4. Follow the on-screen prompts to provide the necessary information, such as hostnames, IP addresses, and credentials.

Prerequisites

  • The script must be run as an administrator.
  • The hosts involved in replication must have network connectivity.
  • The Hyper-V role must be installed on both hosts.
  • For workgroup and mixed environments, ensure that File and Printer Sharing (Echo Request – ICMPv4-In) is enabled in the firewall settings on both hosts.
  • The script is designed for hosts running the same OS version (tested on Windows Server 2019 and 2022). Use with caution on other versions.
  • Disable any existing Hyper-V replication on the hosts.

Important considerations

  • Security: When using self-signed certificates (for workgroups and mixed environments), ensure that the hosts are in a secure network environment. For production environments, consider using certificates issued by a trusted Certificate Authority (CA).
  • Firewall: The script automatically configures firewall rules. However, verify that your firewall settings do not block the necessary traffic for Hyper-V Replica. Specifically, ensure that the following rules are enabled: Hyper-V Replica HTTP Listener (TCP-In) on port 80 and Hyper-V Replica HTTPS Listener (TCP-In) on port 443. If using Kerberos, ensure that Kerberos traffic (TCP/UDP port 88) is allowed.
  • TrustedHosts: The script temporarily adds hosts to the TrustedHosts list. While the script restores the original settings, be aware of the security implications of this setting.
  • Replication Traffic: Hyper-V Replica generates significant network traffic. Ensure that your network infrastructure can handle the additional load.

Disclaimer

This script is provided “as is,” without any warranty. Test the script in a non-production environment before using it in a production setting. The author is not responsible for any data loss or damage caused by the use of this script.

The Script:

<#

.SYNOPSIS A wizard script for initial configuration and turning on VM replication between Hyper-V hosts.

.EXAMPLE PS> .\VM_Replication.ps1

#>

  

## Assigning values to variables.

$MyPath = Split-Path $MyInvocation.MyCommand.Path

$ExpDate = (Get-Date).AddYears(2).ToString("MM-dd-yyyy HH:MM:ss")

$SourceHCert = $null

$ReplicaHCert = $null

$SwitchInputH = $null

$FnSwitch = $null

$IsDomainHost = $null

$HostNum = $null

#requires –RunAsAdministrator

## Zeroing ErrorVariable.

$Error.Clear()

  

## Script exit() function.

function ExitPS

  {

        ## Verifying the necessity to remove temporary certificates.

        if ($SriptHName -ne $SourceHName -and $SriptHName -ne $ReplicaHName)

            {

                ## Removing temporary certificates.

                (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *"CN=Hyper-V Replica Root CA") | Remove-Item -Force -ErrorAction Continue

                (Get-ChildItem Cert:\LocalMachine\CA | Where -Prop Subject -like *"CN=Hyper-V Replica Root CA") | Remove-Item -Force -ErrorAction Continue

                (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *$SourceHName) | Remove-Item -Force -ErrorAction Continue

                (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *$ReplicaHName) | Remove-Item -Force -ErrorAction Continue

                    }  

                        else

                            {

                                ## Verifying the necessity to remove temporary certificates.

                                if ($SriptHName -eq $SourceHName)

                                    {

                                        ## Removing temporary certificates.

                                        (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *"CN=Hyper-V Replica Root CA") | Remove-Item -Force -ErrorAction Continue

                                        (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *$ReplicaHName) | Remove-Item -Force -ErrorAction Continue

                                            }

                                               else

                                                    {

                                                        ## Removing temporary certificates.                               

                                                        (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *"CN=Hyper-V Replica Root CA") | Remove-Item -Force -ErrorAction Continue

                                                        (Get-ChildItem Cert:\LocalMachine\My | Where -Prop Subject -like *$SourceHName) | Remove-Item -Force -ErrorAction Continue

                                                            }

                                    }

    ## Removing Powershell sessions.

    Get-PSSession | Remove-PSSession -Confirm:$false

    ## Verifying the necessity to restore Trustedhosts value.

    if ($CurTH -ne $null)

        {

            ## Resoting previous Trustedhosts values.

            Set-Item wsman:\localhost\Client\Trustedhosts $CurTH –Force -ErrorAction Stop

                }

    ## Checking the message flag.

    if ($Flag -eq 'Exit')

        {

            ## Message output.

            Write-Host "The script execution is completed successfully!" -ForegroundColor Green

                }

                   else

                         {

                            ## Message output.

                            Write-Host "The script execution has failed!" -ForegroundColor Red

                                }

    ## Removing variables.

    Remove-Variable -Name * -Force -ErrorAction SilentlyContinue | Out-Null

    ## Pause

    pause

    ## Clearing the screen.

    Clear-Host

    ## Exiting PowerShell.

    exit

        }

  

## Function for temporary access rights to any configurable hosts in Trustedhosts of the current PC.

function SetNewSettings

    {

        ## Assigning values to variables.

        $CurTH = (get-item wsman:\localhost\Client\TrustedHosts -Force ).value

        $NewTH = '*'

        ## Zeroing ErrorVariable.

        $Error.Clear()

        ## Exception handling.

        try

            {

               ## Setting new parameters in Trustedhosts.

               Set-Item wsman:\localhost\Client\Trustedhosts "$NewTH" –Force -ErrorAction Continue

               ## Checking for errors.

               if ($Error[0].Exception.Message -eq $null)

                    {

                        ## Message output.

                        Write-Host "Temporary access rights to any configurable hosts for the script execution have been added to Trustedhosts of the current machine!"  -ForegroundColor Green

                            }

                                else

                                    {

                                        ## Generating error.

                                        throw "Temporary access rights to any configurable hosts for the script execution have not been added to Trustedhosts of the current machine!"

                                        ## Calling script exit function.

                                        FunctionSwitching ($FnSwitch = '11')

                                            } 

                 }

                    catch

                            {

                                ## Checking for errors.

                                if ($Error[0].Exception.Message -ne $null)

                                    {

                                       ## Generating error.

                                       throw  "Configurable hosts data has not been added to Trustedhosts of the current machine!"

                                       ## Calling script exit function.

                                       FunctionSwitching ($FnSwitch = '11')

                                           }

                                       }

       ## Calling next function.

       FunctionSwitching ($FnSwitch = '1')

        }

  

## Input host availability check function.

function CheckInputH

    {

        ## Verifying the first stage of entering the host address.

        if ($SwitchInputH -eq $null -and $FnSwitch -ne '2')

            {

                ## Accepting user parameters.

                $SourceH = Read-Host "Enter the IP address of the replicated host"

                ## Verifying the IP address availability.

                if ($SourceH -notlike $null -and (Test-Connection -computer $SourceH -Quiet -ErrorAction SilentlyContinue) -eq $True)

                    {

                        ## Exception handling.

                        try

                            {

                                ## Assigning values to variables.

                                $User = Read-Host "Enter Username for the $SourceH"

                                $Password = Read-Host "Enter Password for the $SourceH" -AsSecureString

                                $SourceHCredential = New-Object System.Management.Automation.PSCredential -ArgumentList ($User,$Password)

                                ## Executing settings on a remote host.

                                $SourceHName =  Invoke-Command -ComputerName $SourceH -Credential $SourceHCredential -ErrorAction Stop -ScriptBlock {

                                                                                                                                                       ## Getting DNS host name.

                                                                                                                                                       [System.Net.Dns]::GetHostbyName("localhost").HostName

                                                                                                                                                          }

                                    }

                                      catch

                                            {

                                                ## Checking for errors.

                                                if ($Error[0].Exception.Message -ne $null)

                                                    {

                                                      ## Messages output.

                                                      Write-Host "Incorrect username or password to $SourceH host!"  -ForegroundColor red

                                                      ## Zeroing ErrorVariable.

                                                      $Error.Clear()

                                                      ## Calling a function.

                                                      FunctionSwitching ($FnSwitch = '1')

                                                               }

                                                    }

                        ## Message output.

                        Write-Host "$SourceH host is written and avaliable!" -ForegroundColor Green

                        ## Assigning value to variable.

                        $CurrentHost = $SourceH

                        ## Calling a function.

                        FunctionSwitching ($FnSwitch = '2')                 

                                }

                                    Else

                                            {

                                                ## Message output.

                                                Write-Host "$SourceH host is not available, incorrect IP address, or File and Printer Sharing (Echo Request - ICMPv4-In) firewall rule is disabled on $SourceH host!" -ForegroundColor Red

                                                ## Calling a function.

                                                FunctionSwitching ($FnSwitch = '1')

                                                    }

                        }

                            ## Verifying the 2 stage of entering host address.

                            elseif($FnSwitch -ne '2')

                                    {

                                         ## Getting parameters from the user.

                                         $ReplicaH = Read-Host "Enter IP address of the host with replica"                                         

                                         ## Verifying the entered addresses.

                                         If ($SourceH -eq $ReplicaH)

                                             {

                                                  ## Message output.

                                                  Write-Host "The addresses of the replicated host and the host with replica must differ!" -ForegroundColor Red

                                                  ## 2 seconds' delay before calling a function.

                                                  sleep 2

                                                  ## Zeroing ErrorVariable.

                                                  $Error.Clear()

                                                  ## Calling a function.

                                                  FunctionSwitching ($FnSwitch = '1')

                                                         }

                                                           else

                                                                 {

                                                                   ## Verifying if the entered address is available.

                                                                   if ($ReplicaH -notlike $null -and (Test-Connection -computer $ReplicaH -Quiet -ErrorAction SilentlyContinue) -eq $True)

                                                                         {

                                                                            ## Exception handling.

                                                                            try

                                                                                {

                                                                                    ## Assigning value to variable.

                                                                                    $User = Read-Host "Enter Username for the $ReplicaH"

                                                                                    $Password = Read-Host "Enter Password for the $ReplicaH" -AsSecureString

                                                                                    $ReplicaHCredential = New-Object System.Management.Automation.PSCredential -ArgumentList ($User,$Password)

                                                                                    ## Executing settings on a remote host.

                                                                                    $ReplicaHName =  Invoke-Command -ComputerName $ReplicaH -Credential $ReplicaHCredential -ErrorAction Stop -ScriptBlock {

                                                                                                                                                                                                                ## Getting DNS host name.

                                                                                                                                                                                                                [System.Net.Dns]::GetHostbyName("localhost").HostName

                                                                                                                                                                                                                    }

                                                                                        }

                                                                                            catch

                                                                                                    {

                                                                                                        ## Checking for errors.

                                                                                                        if ($Error[0].Exception.Message -ne $null)

                                                                                                            {

                                                                                                                ## Messages output.

                                                                                                                Write-Host "Incorrect username or password to $ReplicaH host!"  -ForegroundColor red

                                                                                                                ## Calling script exit function.

                                                                                                                FunctionSwitching ($FnSwitch = '1')

                                                                                                                    }

                                                                                                                }

                                                    ## Message output.

                                                    Write-Host "$ReplicaH host is written and avaliable!" -ForegroundColor Green

                                                    ## Assigning value to variable.

                                                    $CurrentHost = $ReplicaH

                                                    ## Calling a function.

                                                    FunctionSwitching ($FnSwitch = '2')

                                                        }

                                                            Else

                                                                   {

                                                                        ## Message output.

                                                                        Write-Host "$ReplicaH host is not available, incorrect IP address or File and Printer Sharing (Echo Request - ICMPv4-In) rule is disabled on $ReplicaH host!" -ForegroundColor Red

                                                                        ## Calling a function.

                                                                        FunctionSwitching ($FnSwitch = '1')

                                                                            }

                                                                     }

                                                }

                       }

## Host selection confirmation function.

function ConfirmHSelection

    {

       ## Getting parameters from the user.

       $HChoice = read-host "Attention! The host address has been entered: $CurrentHost! Do you want to continue configuring this host? (y/n)"

       ## Switching function mode.

       Switch($HChoice)

            {

              Y{

                   ## Verifying a stage of entering host address.

                   If ($SwitchInputH -eq $null)

                        {

                             ## Assigning values to variables.

                             $SwitchInputH = '1'

                             $HChoice = $null

                                }

                                     else

                                           {

                                               ## Calling next function.

                                               FunctionSwitching ($FnSwitch = '3')

                                                 }

                    ## Calling a function.

                    FunctionSwitching ($FnSwitch = '1')

                        }

              N{

                   ## Calling a function.

                   FunctionSwitching ($FnSwitch = '1')

                       }

  

        default{

                    ## Message output.

                    write-host "You have entered an invalid value! Please, try again!" -foregroundcolor red

                    ## 2 seconds' delay before calling a function.

                    sleep 2

                    ## Calling a function.

                    FunctionSwitching ($FnSwitch = '2')

                        }

                  }

         }

  

## Hosts configuration function.

function ConfigureHosts

    {

        ## Switching function mode.

        Switch($HostNum)

                    {

                         $null{

                                  ## Assigning values to variables.                                

                                  $HCredential = $SourceHCredential

                                  $H = $SourceH

                                  $HName = $SourceHName

                                  $HostNum = '1'

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $HCredential = $ReplicaHCredential

                                  $H = $ReplicaH

                                  $HName =$ReplicaHName

                                  $HostNum = '2'

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

        ## Verifying function execution mode.

        if ($HostNum -le '2')

            {

        ## Verifying host availability.

        if ((Get-WindowsFeature -Name 'Hyper-V' -ComputerName $H -Credential $HCredential -ErrorAction Stop -ErrorVariable Erorr).InstallState -eq 'Installed')

            {

                 ## Message output.

                 Write-Host "Hyper-V service on $H ($HName) host is available and working normally!" -ForegroundColor Green

                 ## Executing settings on a remote host.

                 $IsDomainHost = Invoke-Command -ComputerName $H -Credential $HCredential -ErrorAction Stop -ScriptBlock{

                                ## Exception handling.

                                try

                                    {

                                        ## Enabling the firewall rules necessary for replication.

                                        Enable-NetFirewallRule -displayname "Hyper-V Replica HTTP*” -ErrorAction Stop -ErrorVariable Erorr

                                        ## Checking for errors.

                                        if ($Error[0].Exception.Message -eq $null)

                                             {

                                                ## Message output.

                                                Write-Host "The necessary rules on $Using:HName host firewall are enabled!"  -ForegroundColor Green

                                                                    }

                                        ## Verifying the domain / workgroup presence.

                                        if ((Get-WmiObject -Class Win32_ComputerSystem).Workgroup -ne $null)

                                             {

                                        ## Checking for errors.

                                        if ($Error[0].Exception.Message -eq $null)

                                             {

                                                ## Message output.

                                                Write-Host "$Using:HName host is a "(Get-WmiObject -Class Win32_ComputerSystem).Workgroup" workgroup member, to set a trust relationship between the hosts, self-signed certificates will be created and applied!" -ForegroundColor Green

                                                    }

                                        ## Setting host configuration parameter.

                                        if ($Using:IsDomainHost -eq $null)

                                            {

                                                ## Assigning value to variable.

                                                $IsDomainHost = 1

                                                    }

                                                        else

                                                            {

                                                                 ## Assigning value to variable.

                                                                 $IsDomainHost = 2

                                                                    }

                                              }

                                                else

                                                    {

                                                        ## Verifying the further host configuration mode.

                                                        if($Using:IsDomainHost -notlike $null )

                                                            {

                                                                ## Checking for errors.

                                                                if ($Error[0].Exception.Message -eq $null)

                                                                     {

                                                                         ## Message output.

                                                                         Write-Host "$Using:HName host is a"(Get-WmiObject -Class Win32_ComputerSystem).Domain"domain member, to set a trust relationship between the hosts, self-signed certificates will be created and applied!" -ForegroundColor Green

                                                                    }

                                                                ## Assigning value to variable.

                                                                $IsDomainHost = 2

                                                            }

                                                                else

                                                                    {

                                                                        ## Checking for errors.

                                                                        if ($Error[0].Exception.Message -eq $null)

                                                                            {

                                                                                ## Message output.

                                                                                Write-Host "$Using:HName is a"(Get-WmiObject -Class Win32_ComputerSystem).Domain"domain member!" -ForegroundColor Green

                                                                                    }

                                                                        }

                                                                    }

                                                                }

                                                                    catch

                                                                        {

                                                                            ## Checking for errors.

                                                                            if ($Error[0].Exception.Message -ne $null)

                                                                                {

                                                                                    ## Messages output.

                                                                                    Write-Host "$Using:HName host configuration is not finished due to errors!" -ForegroundColor red

                                                                                    ## Calling script exit function.

                                                                                    FunctionSwitching ($FnSwitch = '11')

                                                                                        }

                                                                            }

                 ## Returning the variable value to the current session..

                 return($IsDomainHost)

                                                                        } -ErrorVariable Erorr

                                                                    }

                                                                        else

                                                                            {

                                                                                ## Message output.

                                                                                Write-Host "Hyper-V service on $H ($HName) host is not installed, is not working, or is not available! Install (start) the service, then run the script again!" -ForegroundColor Red

                                                                                ## Calling script exit function.

                                                                                FunctionSwitching ($FnSwitch = '11')

                                                                            }

        ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "Verifying $HName host configuration is successfully finished!" -ForegroundColor Green

                 ## Verifying the condition of calling the next functions.

                 if ($HostNum -eq '2')

                     {

                         if ($IsDomainHost -eq $null){

                                ## Calling next function.

                                FunctionSwitching ($FnSwitch = '7')

                                    }

                                         else

                                            {

                                                ## Calling next function.

                                                FunctionSwitching ($FnSwitch = '4')

                                                    }

                                         }

                                           else

                                                {

                                                    ## Calling a function again.

                                                    FunctionSwitching ($FnSwitch = '3')

                                                        }

                                            }

                                                else

                                                    {

                                                        ## Message output.

                                                        Write-Host "$HName host configuration cannot be finished!" -ForegroundColor Red

                                                        ## Calling script exit function.

                                                        FunctionSwitching ($FnSwitch = '11')

                                                    }

                                            }

                                        }

## Function of adding information about hosts to hosts file.

function AddtoEtcHosts

    {

        ## Switching function mode.

        Switch($EtcCount)

                    {

                         $null{

                                  ## Assigning values to variables.

                                  $H = $SourceH

                                  $H2 = $ReplicaH

                                  $HName = $SourceHName

                                  $HName2 = $ReplicaHName

                                  $HCredential = $SourceHCredential

                                  $EtcCount = 1

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $H = $ReplicaH

                                  $H2 = $SourceH

                                  $HName = $ReplicaHName

                                  $HName2 = $SourceHName

                                  $HCredential = $ReplicaHCredential

                                  $EtcCount = 2

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

        ## Verifying function execution mode.

        if ($EtcCount -le 2)

            {

        ## Exception handling.

        try

            {

                ## Invoking commands on the remote host via remote PowerShell session.

                $File = Invoke-Command -ComputerName $H -Credential $HCredential -ErrorAction Stop -ScriptBlock{

                                                                                                                        ## Assigning value to variable.

                                                                                                                        $File = "$env:windir\System32\drivers\etc\hosts"

                                                                                                                        ## Assigning value to variable.

                                                                                                                        $Hosts = Select-String -Path $File -Pattern $Using:H -SimpleMatch -ErrorAction Stop

                                                                                                                        ## Verifying the necessity of adding information about hosts to hosts file.

                                                                                                                        if ($Hosts -eq $null)

                                                                                                                            {

                                                                                                                                ## Exception handling.

                                                                                                                                try

                                                                                                                                    {

                                                                                                                                        ## Adding information about hosts to hosts file.

                                                                                                                                        ""| Add-Content -PassThru $File -ErrorAction Stop| Out-Null

                                                                                                                                        $Using:H+" "+$Using:HName | Add-Content -PassThru $File -ErrorAction Stop | Out-Null

                                                                                                                                            }

                                                                                                                                                catch

                                                                                                                                                    {

                                                                                                                                                        ## Checking for errors.

                                                                                                                                                        if ($Error[0].Exception.Message -ne $null -or $File -eq $null)

                                                                                                                                                            {

                                                                                                                                                                ## Message output.

                                                                                                                                                                write-error $error[0].Exception.Message

                                                                                                                                                            }

                                                                                                                                                    }

                                                                                                                            }

                                                                                                                        ## Assigning value to variable.

                                                                                                                        $Hosts = Select-String -Path $File -Pattern $Using:H2 -SimpleMatch -ErrorAction Stop

                                                                                                                        ## Verifying the necessity of adding information about hosts to hosts file.

                                                                                                                        if ($Hosts -eq $null)

                                                                                                                            {

                                                                                                                                ## Exception handling.

                                                                                                                                try

                                                                                                                                    {

                                                                                                                                         ## Adding information about hosts to hosts file.

                                                                                                                                        ""| Add-Content -PassThru $File -ErrorAction Stop| Out-Null

                                                                                                                                        $Using:H2+" "+$Using:HName2 | Add-Content -PassThru $File -ErrorAction Stop| Out-Null

                                                                                                                                            }

                                                                                                                                                catch

                                                                                                                                                    {

                                                                                                                                                        ## Checking for errors.

                                                                                                                                                        if ($Error[0].Exception.Message -ne $null -or $File -eq $null)

                                                                                                                                                            {

                                                                                                                                                                 ## Message output.

                                                                                                                                                                 write-error $error[0].Exception.Message

                                                                                                                                                            }

                                                                                                                                                    }

                                                                                                                            }

                                                                                                                    ## Returning the variable value to the current session.

                                                                                                                    return($File)

                                                                                                                        } -ErrorVariable Erorr

                                                                                                                    }

                                                                                                                        catch

                                                                                                                            {

                                                                                                                                ## Checking for errors.

                                                                                                                                if ($Error[0].Exception.Message -ne $null)

                                                                                                                                    {

                                                                                                                                         ## Messages output.

                                                                                                                                         Write-Host "The host file on $HName host cannot be modified!" -ForegroundColor Red

                                                                                                                                         ## Calling script exit function.

                                                                                                                                         FunctionSwitching ($FnSwitch = '11')

                                                                                                                                            }

                                                                                                                            }

                ## Checking for errors.

                if ($Error[0].Exception.Message -eq $null)

                    {

                         ## Message output.

                         Write-Host "The host file on $HName host has been successfully modified!" -ForegroundColor Green

                         ## Calling the function to configure the next host.

                         FunctionSwitching ($FnSwitch = '5')

                            }

                                else

                                    {

                                         ## Message output.

                                         Write-Host "The host file on $HName host cannot be modified!" -ForegroundColor Red

                                         ## Calling script exit function.

                                         FunctionSwitching ($FnSwitch = '11')

                                    }

                               }

                           }

## Function of creating and applying self-signed certificates.

function NewSelfSignedCertificate

    {

        ## Switching function mode.

        Switch($CertCount)

                    {

                         $null{

                                  ## Assigning values to variables.

                                  $H = $SourceH

                                  $HName = $SourceHName

                                  $HCredential = $SourceHCredential

                                  $CertCount = 1

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $H = $ReplicaH

                                  $HName = $ReplicaHName

                                  $HCredential = $ReplicaHCredential

                                  $CertCount = 2

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

         ## Verifying function execution mode.

         if ($CertCount -le 2)

            {

        ## Exception handling.

        try

            {

               ## Invoking commands on the remote host via remote PowerShell session.

               Invoke-Command -ComputerName $H -Credential $HCredential -ErrorAction Stop -ScriptBlock

                    {

                        ## Assigning values to variables.

                        $CertName = $Using:HName

                        $ExpDate = $Using:ExpDate

                         ## Creating self-signed certificates.

                        New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -Subject "CN=$CertName" -NotAfter $ExpDate

                        New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\CA -Subject "CN=Hyper-V Replica Root CA" -NotAfter $ExpDate -FriendlyName "Hyper-V Replica Root CA"

                    } -ErrorVariable Erorr

            }

                catch

                    {## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The certificates on $HName host cannot be created!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

        ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "The certificates on $HName host have been successfully created!" -ForegroundColor Green

                 ## Calling the function to configure the next host.

                 FunctionSwitching ($FnSwitch = '6')

                    }

                        else

                            {

                                 ## Message output.

                                 Write-Host "The certificates on $HName host cannot be created!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                            }

                }

            }

## Function of exporting self-signed certificates.

function ExportCert

    {

        ## Switching function mode.

        Switch($ExpCount)

                    {

                         $null{

                                  ## Assigning values to variables.

                                  $H = $SourceH

                                  $HName = $SourceHName

                                  $HCredential = $SourceHCredential

                                  $ExpCount = 1

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $H = $ReplicaH

                                  $HName = $ReplicaHName

                                  $HCredential = $ReplicaHCredential

                                  $ExpCount = 2

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

         ## Verifying function execution mode.

         if ($ExpCount -le 2)

            {

        ## Exception handling.

        try

            {

               ## Invoking commands on the remote host via remote PowerShell session.

               Invoke-Command -ComputerName $H -Credential $HCredential -ErrorAction Stop -ScriptBlock

                    {

                         ## Assigning values to variables.

                         $CertName = $Using:HName

                         ## Assigning values to variables.

                         $CertThumbprint = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -like "CN=$CertName"}).Thumbprint

                         ## Exporting certificates.

                         Export-Certificate -CertStoreLocation Cert:\LocalMachine\My -Type CERT -Thumbprint $CertThumbprint -FilePath "$CertName"

                    } -ErrorVariable Erorr

            }

                catch

                    {

                         ## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The certificates on $HName host cannot be exported!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

         ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "The certificates on $HName host have been successfully exported!" -ForegroundColor Green

                 ## Calling the function to configure the next host.

                 FunctionSwitching ($FnSwitch = '8')

                    }

                        else

                            {

                                 ## Message output.

                                 Write-Host "The certificates on $HName host cannot be exported!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                            }

                }

            }

## Function of copying self-signed certificates.

function CopyCert

    {

        ## Switching function mode.

        Switch($CopyCount)

                    {

                         $null{

                                  ## Assigning values to variables.

                                  $H = $SourceH

                                  $HName = $SourceHName

                                  $H2 = $ReplicaH

                                  $HName2 = $ReplicaHName

                                  $CopyCount = 1

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $H = $ReplicaH

                                  $HName = $ReplicaHName

                                  $H2 = $SourceH

                                  $HName2 = $SourceHName

                                  $CopyCount = 2

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

         ## Verifying function execution mode.

         if ($CopyCount -le 2)

            {

        ## Exception handling.

        try

            {

               ## Copying certificates.

               Copy-Item -Path "\\$H\c$\$HName.cer" -Destination "\\$H2\c$"

            }

                catch

                    {

                         ## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The certificates from $HName host cannot be copied to $H2 host!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

         ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "The certificates from $HName host have been successfully copied to $H2 host!" -ForegroundColor Green

                 ## Calling the function to configure the next host.

                 FunctionSwitching ($FnSwitch = '9')

                    }

                        else

                            {

                                 ## Message output.

                                  Write-Host "The certificates from $HName host cannot be copied to $H2 host!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                            }

                }

            }

## Function of importing self-signed certificates to the hosts store.

function ImportCert

    {

        ## Switching function mode.

        Switch($ImportCount)

                    {

                         $null{

                                  ## Assigning values to variables.

                                  $H = $SourceH

                                  $HName = $SourceHName

                                  $HCredential = $SourceHCredential

                                  $ImportCount = 1

                                    }

                            1 {

                                  ## Assigning values to variables.

                                  $H = $ReplicaH

                                  $HName = $ReplicaHName

                                  $HCredential = $ReplicaHCredential

                                  $ImportCount = 2

                                    }

                      default {

                                  ## Message output.

                                  write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                                  ## Calling script exit function.

                                  FunctionSwitching ($FnSwitch = '11')

                                            }

                                }

         ## Verifying function execution mode.

         if ($ImportCount -le 2)

            {

        ## Exception handling.

        try

            {

               ## Invoking commands on the remote host via remote PowerShell session.

               Invoke-Command -ComputerName $H -Credential $HCredential -ErrorAction Stop -ScriptBlock

                    {

                         ## Assigning values to variables.

                         $CertName = $Using:HName

                         ## Importing certificates.

                         Import-Certificate -FilePath "$CertName.cer" -CertStoreLocation Cert:\LocalMachine\Root

                    } -ErrorVariable Erorr

            }

                catch

                    {

                         ## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The certificates on $HName host cannot be imported!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

         ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "The certificates on $HName host have been successfully imported!" -ForegroundColor Green

                 ## Calling the function to configure the next host.

                 FunctionSwitching ($FnSwitch = '10')

                    }

                        else

                            {

                                 ## Message output.

                                 Write-Host "The certificates on $HName host cannot be imported!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                            }

                }

            }

## Function of configuring the hosts.

function ConfigureReplication

    {

        ## Exception handling.

        try

            {

               ## Configuring replication on the hosts.

               Invoke-Command -ComputerName $SourceH -Credential $SourceHCredential -ErrorAction Stop -ScriptBlock

                    {

                         ## Assigning values to variables.

                         $VMName = $Using:VMName

                         $ReplicaHName = $Using:ReplicaHName

                         $CertThumbprint = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -like "CN=$Using:SourceHName"}).Thumbprint

                         ## Enabling replication.

                         Enable-VMReplication -ComputerName $env:COMPUTERNAME -VMName $VMName -ReplicaServerName $ReplicaHName -ReplicaServerPort 443 -AuthenticationType Certificate -CertificateThumbprint $CertThumbprint -BypassFirewallRule:$true

                    } -ErrorVariable Erorr

            }

                catch

                    {

                         ## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The replication on $SourceHName host cannot be configured!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

        ## Exception handling.

        try

            {

               ## Configuring replication on the hosts.

               Invoke-Command -ComputerName $ReplicaH -Credential $ReplicaHCredential -ErrorAction Stop -ScriptBlock

                    {

                         ## Assigning values to variables.

                         $VMName = $Using:VMName

                         $ReplicaHName = $Using:ReplicaHName

                         $CertThumbprint = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -like "CN=$Using:ReplicaHName"}).Thumbprint

                         ## Enabling replication.

                         Enable-VMReplication -ComputerName $env:COMPUTERNAME -VMName $VMName -ReplicaServerName $ReplicaHName -ReplicaServerPort 443 -AuthenticationType Certificate -CertificateThumbprint $CertThumbprint -BypassFirewallRule:$true

                    } -ErrorVariable Erorr

            }

                catch

                    {

                         ## Checking for errors.

                         if ($Error[0].Exception.Message -ne $null)

                            {

                                 ## Messages output.

                                 Write-Host "The replication on $ReplicaHName host cannot be configured!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                                    }

                    }

         ## Checking for errors.

        if ($Error[0].Exception.Message -eq $null)

            {

                 ## Message output.

                 Write-Host "The replication has been successfully configured!" -ForegroundColor Green

                 ## Calling script exit function.

                 ExitPS ($Flag = 'Exit')

                    }

                        else

                            {

                                 ## Message output.

                                 Write-Host "The replication cannot be configured!" -ForegroundColor Red

                                 ## Calling script exit function.

                                 FunctionSwitching ($FnSwitch = '11')

                            }

    }

## Main script function.

function FunctionSwitching

    {

        ## Switching function mode.

        Switch($FnSwitch)

                    {

                         1{

                              ## Calling a function.

                              CheckInputH

                               }

                         2{

                              ## Calling a function.

                              ConfirmHSelection

                               }

                         3{

                              ## Calling a function.

                              ConfigureHosts

                               }

                         4{

                              ## Calling a function.

                              AddtoEtcHosts

                               }

                         5{

                              ## Calling a function.

                              NewSelfSignedCertificate

                               }

                         6{

                              ## Calling a function.

                              ExportCert

                               }

                         7{

                              ## Calling a function.

                              AddtoEtcHosts

                               }

                         8{

                              ## Calling a function.

                              CopyCert

                               }

                         9{

                              ## Calling a function.

                              ImportCert

                               }

                         10{

                              ## Calling a function.

                              ConfigureReplication

                               }

                         11{

                              ## Calling script exit function.

                              ExitPS ($Flag = 'Stop')

                               }

                      default{

                               ## Message output.

                               write-host "Fatal error encountered during the script execution! Reboot and continue the script!" -ForegroundColor Red

                               ## Calling script exit function.

                               ExitPS ($Flag = '11')

                                 }

                            }

    }

## Clearing the screen.

Clear-Host

## Calling main script function.

FunctionSwitching ($FnSwitch = '1')

Wrapping up

This PowerShell script can significantly simplify the configuration of Hyper-V Replica in diverse environments. By automating the process, it reduces the risk of errors, saves time, and helps ensure a robust disaster recovery solution. Try this script and adapt it to your specific needs. Your feedback and contributions are welcome!

Hey! Found Volodymyr’s insights useful? Looking for a cost-effective, high-performance, and easy-to-use hyperconverged platform?
Taras Shved
Taras Shved StarWind HCI Appliance Product Manager
Look no further! StarWind HCI Appliance (HCA) is a plug-and-play solution that combines compute, storage, networking, and virtualization software into a single easy-to-use hyperconverged platform. It's designed to significantly trim your IT costs and save valuable time. Interested in learning more? Book your StarWind HCA demo now to see it in action!