Hyper-V 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

Manually Remove Hyper-V Host Cluster from SCVMM 2012 Database

Hello,

It’s been a long time, but not always does one create something that is worth sharing 🙂

I have been playing with System Center Virtual Machine Manager 2012 since the Beta was released, and I love it.

There were however some problems which are still inherited from the 2008 R2 version.

One problem was failure to remove a host, or a host cluster from the SCVMM database.

Unlike the 2008 R2 version, I searched around and i found no database scripts available which can help me clean the SCVMM 2012 database, so i decided to create my own.

Here is the explanation and some stuff you should know.

My Setup:

  • SCVMM 2012 RC
  • VMM Database is deployed to SQL Server 2008 R2
  • Running Hyper-V Cluster on 4 hosts Windows Server 2008 R2 Enterprise
  • EVA 4400 SAN Storage

How the script works:

Basically the script runs through the VMM Database, deleting all entries of the hosts and any data related to them.

The script also checks to see whether this host is the last host in the cluster, if this host is indeed the last one, it will also remove the cluster and all its related data.

The script however does not do the following:

  • Delete any Host Groups or Host Group settings
  • Delete any logical networks or logical network definitions

This is basically because you can manually delete them using the VMM console once it is up and running again.

How to use the script:

  1. Well the first step is always to back up your existing state, even if it is not working properly, so go ahead and back up your VMM Database before you start 🙂
  2. Disable the “System Center Virtual Machine Manager” Service on you VMM Server (make sure you disable it on all VMM Servers in case you have a highly available setup)
  3. Login to you SQL Server Database Server, and Start SQL Server Management Studio
  4. Click the “New Query” button, and paste the script below
  5. Replace the <Placeholders> at the start of the script according to your setup (Only the VMM Database Name and the GUID for the host you want to delete.
  6. In case you do not have the GUID for your host, you can get it by expanding your VMM Database and looking at the table “dbo.tbl_ADHC_Hosts”
  7. After you run the script, go ahead and restart your System Center Virtual Machine Manager Service

The Script:

USE ;

DECLARE @DeleteHostId GUID;
SET @DeleteHostId = ''

PRINT N'Deleting host with GUID ' + RTRIM(CAST(@DeleteHostID AS nvarchar(50)))

PRINT N'Getting host cluster GUID'

DECLARE @HostClusterID GUID;
SET @HostClusterID =
(
SELECT HostClusterID FROM [dbo].[tbl_ADHC_Host]
WHERE HostID = @DeleteHostId
)

IF (@HostClusterID IS NOT NULL)
PRINT N'Retreived host cluster GUID ' + RTRIM(CAST(@HostClusterID AS nvarchar(50)))
ELSE
PRINT N'This host does not belong to a cluster'

PRINT N'Deleteing physical objects'

DELETE FROM [dbo].[tbl_WLC_PhysicalObject]
WHERE HostId = @DeleteHostId

PRINT N'Deleteing virtual objects'

DELETE FROM [dbo].[tbl_WLC_VObject]
WHERE HostId = @DeleteHostId

PRINT N'Prepairing to delete host network adapters'

DECLARE @HostNetworkAdapterCursor CURSOR;
DECLARE @HostNetworkAdapterID GUID;
SET @HostNetworkAdapterCursor = CURSOR FOR
(SELECT NetworkAdapterID FROM [dbo].[tbl_ADHC_HostNetworkAdapter])

OPEN @HostNetworkAdapterCursor

FETCH NEXT FROM @HostNetworkAdapterCursor INTO @HostNetworkAdapterID

WHILE (@@FETCH_STATUS = 0)
BEGIN
PRINT N'Prepairing to delete host network adapter with GUID ' + RTRIM(CAST(@HostNetworkAdapterID AS nvarchar(50)))

PRINT N'Deleting logical network mapping for host network adapter with GUID ' + RTRIM(CAST(@HostNetworkAdapterID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_NetMan_HostNetworkAdapterToLogicalNetwork]
WHERE HostNetworkAdapterID = @HostNetworkAdapterID

PRINT N'Deleting IP subnet VLAN mapping for host network adapter with GUID ' + RTRIM(CAST(@HostNetworkAdapterID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_NetMan_HostNetworkAdapterToIPSubnetVLan]
WHERE HostNetworkAdapterID = @HostNetworkAdapterID

FETCH NEXT FROM @HostNetworkAdapterCursor INTO @HostNetworkAdapterID
END

CLOSE @HostNetworkAdapterCursor
DEALLOCATE @HostNetworkAdapterCursor

PRINT N'Completing host network adapters deletion'

DELETE FROM [dbo].[tbl_ADHC_HostNetworkAdapter]
WHERE HostID = @DeleteHostId

PRINT N'Deleting virtual networks'

DELETE FROM [dbo].[tbl_ADHC_VirtualNetwork]
WHERE HostID = @DeleteHostId

PRINT N’Deleting virtual switch extensions’

DELETE FROM [dbo].[tbl_NetMan_InstalledVirtualSwitchExtension]
WHERE HostID = @DeleteHostId

PRINT N'Deleting host volumes'

DELETE FROM [dbo].[tbl_ADHC_HostVolume]
WHERE HostID = @DeleteHostId

PRINT N’Deleting pass through disks’

DELETE FROM [dbo].[tbl_WLC_VDrive]
WHERE HostDiskId IN (SELECT DiskID FROM [dbo].[tbl_ADHC_HostDisk] WHERE HostID IN (SELECT HostID FROM [dbo].[tbl_ADHC_Host] WHERE HostID = @DeleteHostId))

PRINT N'Deleting host disks'

DELETE FROM [dbo].[tbl_ADHC_HostDisk]
WHERE HostID = @DeleteHostId

PRINT N'Prepairing to delete host bus adapters'

DECLARE @HostBusAdapterCursor CURSOR;
DECLARE @HostBusAdapterID GUID;
SET @HostBusAdapterCursor = CURSOR FOR
(SELECT HbaID FROM [dbo].[tbl_ADHC_HostBusAdapter])

OPEN @HostBusAdapterCursor

FETCH NEXT FROM @HostBusAdapterCursor INTO @HostBusAdapterID

WHILE (@@FETCH_STATUS = 0)
BEGIN

PRINT N'Prepairing to delete host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

PRINT N'Deleting fiber port mapping for host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DECLARE @FiberPortID GUID;
SET @FiberPortID =
(
SELECT PortID FROM [dbo].[tbl_ADHC_FCHbaToFibrePortMapping]
WHERE FCHbaID = @HostBusAdapterID
)

DELETE FROM [dbo].[tbl_ADHC_FCHbaToFibrePortMapping]
WHERE FCHbaID = @HostBusAdapterID

PRINT N'Deleting fiber port with GUID ' + RTRIM(CAST(@FiberPortID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_FibrePort]
WHERE PortID = @FiberPortID

PRINT N'Deleting fiber channel mapping for host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_HostFibreChannelHba]
WHERE FCHbaID = @HostBusAdapterID

PRINT N'Deleting any iSCSI entries for host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DECLARE @iSCSITargets TABLE
(
TargetID GUID
)
INSERT INTO @iSCSITargets (TargetID)
SELECT TargetID FROM [dbo].[tbl_ADHC_ISCSIHbaToTargetMapping]
WHERE ISCSIHbaID = @HostBusAdapterID

PRINT N'Deleting iSCSI host bus adapter to target mapping for mapping for host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_ISCSIHbaToTargetMapping]
WHERE ISCSIHbaID = @HostBusAdapterID

PRINT N'Deleting iSCSI host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_HostInternetSCSIHba]
WHERE ISCSIHbaID = @HostBusAdapterID

PRINT N'Deleting iSCSI targets for host bus adapter with GUID ' + RTRIM(CAST(@HostBusAdapterID AS nvarchar(50)))

DECLARE @iSCSITargetIDCursor CURSOR;
DECLARE @iSCSITargetID GUID;
SET @iSCSITargetIDCursor = CURSOR FOR
(SELECT TargetID FROM @iSCSITargets)

OPEN @iSCSITargetIDCursor

FETCH NEXT FROM @iSCSITargetIDCursor INTO @iSCSITargetID

WHILE (@@FETCH_STATUS = 0)
BEGIN

PRINT N'Deleting iSCSI targets with GUID ' + RTRIM(CAST(@iSCSITargetID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_ISCSITarget]
WHERE TargetID = @iSCSITargetID

FETCH NEXT FROM @iSCSITargetIDCursor INTO @iSCSITargetID
END

CLOSE @iSCSITargetIDCursor
DEALLOCATE @iSCSITargetIDCursor

FETCH NEXT FROM @HostBusAdapterCursor INTO @HostBusAdapterID
END

CLOSE @HostBusAdapterCursor
DEALLOCATE @HostBusAdapterCursor

PRINT N'Completing host bus adapters deletion'

DELETE FROM [dbo].[tbl_ADHC_HostBusAdapter]
WHERE HostID = @DeleteHostId

PRINT N'Prepairing to delete agent servers'

DECLARE @AgentServerID  GUID;
SET @AgentServerID =
(
SELECT AgentServerID FROM [dbo].[tbl_ADHC_AgentServerRelation]
WHERE HostLibraryServerID = @DeleteHostID
)

PRINT N'Deleting agent server relations'

DELETE FROM [dbo].[tbl_ADHC_AgentServerRelation]
WHERE HostLibraryServerID = @DeleteHostID

PRINT N'Deleting health monitor data for agent server with GUID ' + RTRIM(CAST(@AgentServerID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_HealthMonitor]
WHERE AgentServerID = @AgentServerID

PRINT N'Deleting agent server with GUID ' + RTRIM(CAST(@AgentServerID AS nvarchar(50)))

DELETE FROM [dbo].[tbl_ADHC_AgentServer]
WHERE AgentServerID = @AgentServerID

PRINT N'Deleting host GPUs'

DELETE FROM [dbo].[tbl_ADHC_HostGPU]
WHERE HostID = @DeleteHostId

PRINT N'Deleting host'

DELETE FROM [dbo].[tbl_ADHC_Host]
WHERE HostID = @DeleteHostId

IF (@HostClusterID IS NOT NULL)
BEGIN

PRINT N'Checking to see if any other hosts are joined to the same cluster'

DECLARE @HostCount INT;
SET @HostCount =
(
SELECT COUNT(*) FROM [dbo].[tbl_ADHC_Host]
WHERE HostClusterID = @HostClusterID
)

PRINT N'There are ' + RTRIM(CAST(@HostCount AS nvarchar(50))) + N' currently joined to the same cluster'

IF (@HostCount = 0)
BEGIN

PRINT N'Deleting cluster disks'

DELETE FROM [dbo].[tbl_ADHC_ClusterDisk]
WHERE ClusterID = @HostClusterID

PRINT N'Deleting cluster'

DELETE FROM [dbo].[tbl_ADHC_HostCluster]
WHERE ClusterID = @HostClusterID
END
ELSE
PRINT N'This host is not the last host in the cluster, the cluster will be deleted upon the deletion of the last host.'
END
ELSE
PRINT N'This host does not belong to a cluster, no clusters will be deleted'

GO

And that’s it 🙂

I have tried this script a couple of times and it has working well for me.

But please do not hesitate to leave a comment in case there is anything that’s not working for you.

Edit: Thanks to Yusuf Ozturk the script now also supports deleting Pass-through Disks and Virtual Switch Extensions in case they were used by the hosts.