create cluster

Automating Hyper-V Cluster Creation with Windows PowerShell

As an Infrastructure Consultant, I’ve dealt a lot with the activity of creating a Hyper-V cluster. Since the release of Windows Server 2012, it has been possible to automate every task in the creation of a cluster. So i have created this script that would help me do that, and i thought I’d share it with you.

Introduction

Creating a Hyper-V Cluster comes with a lot of tips and best practices that you should follow, in order to create the best Hyper-V cluster there is. As the activity of creating the cluster itself might not be that difficult, however all these best practices take a long time to do on all the servers.

Let me shed the light of what this script covers:

  • Prepares Network Adapters on the Cluster Nodes, in terms of renaming, IP Address, Sunbet, Gateway, DNS Servers
  • Prepares the cluster nodes, by installing required roles and features
  • Creates Hyper-V cluster (no storage)
  • Renaming cluster networks and applying metrics

I know there are still a lot of things to add, so feel free to write in the comments any additions you can contribute.

#region Functions

function Prepare-NetAdapter
{
    [CmdletBinding()]
    [OutputType([String])]
    Param
    (
        # NetAdapterOldName help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [String]
        $NetAdapterOldName,

        # NetAdapterNewName help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String]
        $NetAdapterNewName,

        # NetAdapterIPAddress help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [String]
        $NetAdapterIPAddress,

        # NetAdapterPrefixLength help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [int]
        $NetAdapterPrefixLength,

        # NetAdapterDefaultGateway help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=4)]
        [String]
        $NetAdapterDefaultGateway,

        # NetAdapterDNSServerAddresses help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=5)]
        [String]
        $NetAdapterDNSServerAddresses
    )

    Write-Output "Getting Network Adapter"

    $__NetAdapter = Get-NetAdapter -Name $NetAdapterOldName;

    Write-Output "Renaming Network Adapter"

    $__NetAdapter | Rename-NetAdapter -NewName $NetAdapterNewName;

    if (($NetAdapterIPAddress) -and ($NetAdapterPrefixLength) -and ($NetAdapterDefaultGateway))
    {
        Write-Output "Setting Network Adapter IP Address"

        $__NetAdapter = Get-NetAdapter -Name $NetAdapterNewName
        $__NetAdapter | Set-NetIPInterface -Dhcp Disabled
        $__NetAdapter | New-NetIPAddress -IPAddress $NetAdapterIPAddress -PrefixLength $NetAdapterPrefixLength
    }

    if ($NetAdapterDNSServerAddresses)
    {
        Write-Output "Setting Network Adapter DNS Addresses"
        Set-DnsClientServerAddress -InterfaceAlias $NetAdapterNewName -ServerAddresses $NetAdapterDNSServerAddresses
    }
}

function Prepare-ClusterNode
{
    Write-Output "Adding Windows Features"

    Add-WindowsFeature Multipath-IO
    Add-WindowsFeature Hyper-V -IncludeManagementTools
    Add-WindowsFeature Failover-Clustering -IncludeManagementTools
    Add-WindowsFeature RSAT-Clustering –IncludeAllSubFeature

    Write-Output "Restarting Server"

    Restart-Computer -Force
}

function Create-HyperVCluster
{
    [CmdletBinding()]
    [OutputType([String])]
    Param
    (
        # ClusterName help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [String]
        $ClusterName,

        # ClusterNodes help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String[]]
        $ClusterNodes,

        # ClusterIPAddress help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [String]
        $ClusterIPAddress,

        # HeartbeatNetworkSubnet help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [String]
        $HeartbeatNetworkSubnet,

        # HeartbeatNetworkName help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=4)]
        [String]
        $HeartbeatNetworkName,

        # CSVNetworkSubnet help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=5)]
        [String]
        $CSVNetworkSubnet,

        # CSVNetworkName help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=6)]
        [String]
        $CSVNetworkName,

        # LiveMigrationNetworkSubnet help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=7)]
        [String]
        $LiveMigrationNetworkSubnet,

        # LiveMigrationNetworkName help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=8)]
        [String]
        $LiveMigrationNetworkName,

        # ManagementNetworkSubnet help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=9)]
        [String]
        $ManagementNetworkSubnet,

        # ManagementNetworkName help description
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=10)]
        [String]
        $ManagementNetworkName,

        # DiskOnly help description
        [Parameter(Mandatory=$false,
                   PositionalBinding=$false)]
        [String]
        $DiskOnly,

        # NodeAndDiskMajority help description
        [Parameter(Mandatory=$false,
                   PositionalBinding=$false)]
        [String]
        $NodeAndDiskMajority,

        # NodeAndFileShareMajority help description
        [Parameter(Mandatory=$false,
                   PositionalBinding=$false)]
        [String]
        $NodeAndFileShareMajority,

        # ClusterQuorum help description
        [Parameter(Mandatory=$false,
                   PositionalBinding=$false)]
        [SwitchParameter]
        $NodeMajority
    )
    Write-Output "Importing Fail-over Cluster module"

    Import-Module FailoverClusters;

    Write-Output "Creating new cluster"

    New-Cluster -Name $ClusterName -Node $ClusterNodes–StaticAddress $ClusterIPAddress -NoStorage

    Write-Output "Waiting 10 seconds for the cluster to initialize"

    Start-Sleep -s 10

    Write-Output "Configuring quorum"

    if ($DiskOnly)
    {
        Set-ClusterQuorum -DiskOnly $DiskOnly
    }
    elseif ($NodeAndDiskMajority)
    {
        Set-ClusterQuorum -NodeAndDiskMajority $NodeAndDiskMajority
    }
    elseif ($NodeAndFileShareMajority)
    {
        Set-ClusterQuorum -NodeAndFileShareMajority $NodeAndFileShareMajority
    }
    else
    {
        Set-ClusterQuorum -NodeMajority
    }

    Write-Output "Renaming the cluster networks"

    (Get-ClusterNetwork | where-object {$_.Address -eq $HeartbeatNetworkSubnet}).Name = $HeartbeatNetworkName
    (Get-ClusterNetwork | where-object {$_.Address -eq $CSVNetworkSubnet}).Name = $CSVNetworkName
    (Get-ClusterNetwork | where-object {$_.Address -eq $LiveMigrationNetworkSubnet}).Name = $LiveMigrationNetworkName
    (Get-ClusterNetwork | where-object {$_.Address -eq $ManagementNetworkSubnet}).Name = $ManagementNetworkName

    Write-Output "Setting metric for CSV cluster network"

    (Get-ClusterNetwork $CSVNetworkName).Metric=900;
}

#endregion

$Credential = Get-Credential
$ClusterNodes = Import-Csv ClusterNodes.csv
$NetAdapters = Import-Csv NetAdapters.csv
$Servers = ($ClusterNodes | Select-Object Name | Foreach-Object { $_.Name })

foreach ($Node in $ClusterNodes)
{
    $NodeNetAdapters = $NetAdapters | Where-Object { $_.Node -eq $Node.Name }

    foreach ($NetAdapter in $NodeNetAdapters)
    {
        Invoke-Command -ComputerName $Node.Name -Credential $Credential -ScriptBlock ${function:Prepare-NetAdapter} -ArgumentList $NetAdapter.NetAdapterOldName,$NetAdapter.NetAdapterNewName,$NetAdapter.NetAdapterIPAddress,$NetAdapter.NetAdapterPrefixLength
    }
}

Invoke-Command -ComputerName $Servers -Credential $Credential -ScriptBlock ${function:Prepare-ClusterNode}

Start-Sleep -Seconds 360

Invoke-Command -ComputerName $Servers -Credential $Credential -ScriptBlock ${function:Create-HyperVCluster} -ArgumentList "Servers-Cluster",$Servers,"10.40.30.230","222.222.222.0","Heartbeat","222.222.223.0","CSV","222.222.224.0","LiveMigration","10.40.30.0","Management"

How to use

  • This script must run from another machine, other that the cluster nodes
  • Don’t forget to Set-ExecutionPolicy RemoteSigned on this machine and the cluster nodes
  • Copy the attached files (Create-HyperVCluster.ps1, ClusterNodes.csv, NetAdapters.csv) to C:\Scripts\
  • Modify the two csv files as per your environment
  • Modify the last line of the script Create-HyperVCluster.ps1 as per your environment

And you’re good to go, let me know how it works out.

Advertisements