HyperVClusteredEnvBuildout.psm1

#Function = 1
<#
 
.SYNOPSIS
Create a new virtual machine with the help of the Hyper-V module
 
.DESCRIPTION
This script will allow you to create a new VM, add a new math for your .VHDX, add memory, add storage, and add a, existing vSwitch.
 
.EXAMPLE
New-HyperVM -Name 'MYNEWVM' -VMSwitch 'MyVMSwitch' -MemoryStartup 2GB -NewVHDSize 50GB -Path C:\HyperV\MYNEWVM -NewVHDPath C:\HyperV\MYNEWVM\MYNEWVM.vhdx
 
.NOTES
Please Note: This is using the Hyper-V module to create a new VM. This just adds error handing, function, IF statements, and tests.
 
#>

Function New-HyperVM {
    [cmdletbinding(DefaultParameterSetName = 'NewVM', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    Param (
        [Parameter(ParameterSetName = 'NewVM',
            Position = 0,
            Mandatory = $true,
            HelpMessage = 'Please type in a name for your Virtual Machine')]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(ParameterSetName = 'NewVM',
            Position = 1,
            HelpMessage = 'Please type in your vSwitch name')]
        [ValidateNotNullOrEmpty()]
        [string]$VMSwitch,

        [Parameter(ParameterSetName = 'NewVM',
            Position = 2,
            HelpMessage = 'Please type in specified memory startup size. For example: 2GB')]
        [ValidateNotNullOrEmpty()]
        [string]$MemoryStartup,

        [Parameter(ParameterSetName = 'NewVM',
            Position = 3,
            HelpMessage = 'Please type in new VHD size. Default is 50GB')]
        [ValidateNotNullOrEmpty()]
        [string]$NewVHDSize = 50GB,

        [Parameter(ParameterSetName = 'NewVM',
            Position = 4,
            HelpMessage = 'Please type in a path for your new VHD. Example: C:\hyperv\newvm.vhdx')]
        [ValidateNotNullOrEmpty()]
        [string]$NewVHDPath,

        [Parameter(ParameterSetName = 'NewVM',
            Position = 5,
            Mandatory = $true,
            HelpMessage = 'Please type a name for your NEW directory that will store your VM')]
        [ValidateNotNullOrEmpty()]
        [string]$Path
    )
    Begin {
        $hosts = @()
        $hosts += $Name
        Write-Output "Starting: $MyInvocation.MyCommand"

        Write-Verbose 'Creating directory for new VM'
        Foreach ($VMName in $Name) {
            New-Item -Path $Path -Name "$VMName" -ItemType Directory
        }
    }

    Process {
        Try {
            IF ($PSBoundParameters.ContainsKey('NewVHDPath')) {
                $NewVHD = New-VHD -Path $NewVHDPath -SizeBytes $NewVHDSize
                $NewVHDOBJECT = [pscustomobject] @{
                    'Hostname'  = $NewVHD.ComputerName
                    'VHDFormat' = $NewVHD.VhdFormat
                    'VHDXSize'  = $NewVHD.Size / 1GB
                }
                Write-Verbose 'Outputting results of new VHD'
                $NewVHDOBJECT
            }#IF

            IF (-Not($NewVHDPath)) {
                Write-Warning 'The VHDX path was not found. Please try again...'
                Pause               
            }

            ELSE {
                $NewVMParams = @{
                    'Name'               = $Name
                    'Path'               = "D:\hyperv\$Name"
                    'MemoryStartupBytes' = $MemoryStartup
                    'VHDPath'            = $NewVHDPath
                    'SwitchName'         = $VMSwitch
                    'Generation'         = '2'
                }
                $NewVM = New-VM @NewVMParams

                $NewVMOBJECT = [pscustomobject] @{
                    'VMName'         = $NewVM.Name
                    'VMCurrentState' = $NewVM.State
                    'VMStatus'       = $NewVM.Status
                    'VMGeneration'   = $NewVM.Generation                           
                }
                $NewVMOBJECT

            }#Else
        }#Try

        Catch {
            $HyperVServer
            $TestHyperVServerConnection = Test-Connection $HyperVServer
            $IPregex = ‘(?<Address>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))’
            IF ($TestHyperVServerConnection.IPv4Address.IPAddressToString[0] -match $IPregex ) {
                Write-Output 'Connection to Hyper-V Server: Successful. Moving on...'
            }

            Else {
                Write-Warning 'Connection to Hyper-V Server: UNSUCCESSFUL'
                Pause
                Break
            }

            $TestVHDXPath = Test-Path $NewVHDPath
            IF ($TestVHDXPath -like 'true') {
                Write-Output 'Path to VHDX: Successful. Moving on...'
            }

            Else {
                Write-Warning 'Connection to VHDX: UNSUCCESSFUL'
                Pause
                Break
            }

            Write-Warning 'Please review the errors below'
            $PSCmdlet.ThrowTerminatingError($_)                
        }#Catch
    }#Process
    End {}
}#Function

##########################################################################################################################################################################

#Function = 2
<#
 
.SYNOPSIS
Create a new iSCSI config utilizing the iSCSI initiator to connect to your iSCSI LUN
 
.DESCRIPTION
This function will allow you to connect your Hyper-V servers to an iSCSI LUN of your choosing utilizing the iSCSI initiator
 
.EXAMPLE
New-iSCSIConfig -ComputerName localhost -LogPath 'C:\Logs\LogName.txt' -iSCSITargetIPAddress 192.168.1.20 -NodeAddress iqn.1991-05.com.contoso:deepcore.contoso.com -ErroLog 'C:\Errors\Errorlog.txt'
 
.NOTES
Please Note: You must have an iSCSI LUN for the initiator to connect to prior to this function
 
#>

Function New-iSCSIConfig {
    [cmdletbinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    Param (
        [Parameter(Position = 0,
            Mandatory=$true,
            ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [psobject]$ComputerName = 'localhost',
        
        [Parameter(HelpMessage = 'Please specify a location for your logs')]
        [string]$LogPath,
        
        [Parameter(HelpMessage = 'Please specify an IP address for your iSCSI target')]
        [psobject]$iSCSITargetIPAddress,
        
        [string]$NodeAddress,
        
        [string]$ErrorLog
    )
    Begin {Write-Output "Starting: $MyInvocation.MyCommand"
                                   $MyInvocation.BoundParameters}

    Process {
        TRY {
            $InstalliSCSIRolePARAMS = @{
                'ComputerName' = $ComputerName
                'LogPath'      = $LogPath
                'Name'         = 'FS-ISCSITARGET-Server'
                'Verbose'      = $true
            }

            Write-Verbose 'Installing iSCSI Server role'
            $InstalliSCSIController = Install-WindowsFeature @InstalliSCSIRolePARAMS

            $iSCSIControllerObject = [PSCustomObject] @{
                'Hostname' = $ComputerName
                'Success'  = $InstalliSCSIController.Success
                'Restart?' = $InstalliSCSIController.RestartNeeded

            }
            $iSCSIControllerObject

            Write-Verbose 'Confirming iSCSI role was installed successfully'
            IF ($InstalliSCSIController.Success -like 'True') {
                Write-Output 'Installation of iSCSI Windows Feature was successful. We will now continue.'
            }

            ELSE {
                Write-Warning 'Configuration/Installation Error. Please review your parameters and try again. Exiting configuration'
                $Error[0]
                Pause
                Break
            }

            Write-Verbose 'Testing connection to iSCSI target'
            $TestConnection = Test-Connection $iSCSITargetIPAddress
            IF ($TestConnection.PSComputerName -notmatch "\w+") {
                Write-Warning 'The IP address was not reachable. Please try re-running your cmdlet and re-entering a new IP address into your parameter'
                Pause
                Break
            }

            ELSE {
                Write-Verbose 'Setting up switch for ShouldProcess to run'
                [switch]$Continue = $true
            }
            IF ($PSCmdlet.ShouldProcess($Continue)) {
                FOREACH ($Computer in $ComputerName) {

                    Write-Verbose 'Starting iSCSI service'
                    Start-Service -Name MSiSCSI
                    Write-Verbose 'Confirming iSCSI start is started automatically'
                    Set-Service -Name MSiSCSI -StartupType Automatic

                    Write-Verbose 'Creating new iSCSI target portal'
                    $NewiSCSITargetPortal = New-IscsiTargetPortal -TargetPortalAddress $iSCSITargetIPAddress

                    Write-Verbose 'Connecting iSCSI target'
                    $ConnectiISCSITargetPortal = Connect-IscsiTarget -NodeAddress $NodeAddress
                    $ConnectiISCSITargetPortal 

                    $iSCSIConfig = [pscustomobject] @{
                        'TargetPortalAddress' = $NewiSCSITargetPortal.TargetPortalAddress
                        'TargetPortalNumber'  = $NewiSCSITargetPortal.TargetPortalPortNumber
                        'NodeAddress'         = $NodeAddress
                    }
                    $iSCSIConfig | Format-Table
        
                }#FOREACH
            }#ShouldProcessIF
        }#TRY
        CATCH {
            Write-Warning 'An error has occured. Please check the error logs in your specific file share'
            $_ | Out-File $ErrorLog
            Throw
        }#CATCH
    }#Process
    End {}
}#Function

####################################################################################################################################################################
#Function = 3
<#
 
.SYNOPSIS
Create a new server cluster with your Hyper-V hosts
 
.DESCRIPTION
This function will allow you to connect your Hyper-V servers together to create a cluster
 
.EXAMPLE
New-ServerCluster -Node 'SERVER1','SERVER2' -ClusterName 'MyCluster1'
 
.EXAMPLE
'SERVER1','SERVER2' | New-ServerCluster -ClusterName 'MyCluster1'
 
.NOTES
Please Note: You must have an iSCSI LUN for the initiator to connect to prior to this function
 
#>

#1, Cluster service cannot be started due to machines not currently being in a cluster. If the machines are in a cluster, then they can check the cluster available disks
Function New-ServerCluster {
    [cmdletbinding(DefaultParameterSetName='ClusterConfig',SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    Param (
        [Parameter(ParameterSetName='ClusterConfig',
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Please enter your nodes that you are connecting to your new cluster')]
        [ValidateNotNull()]
        [Alias('ComputerName', 'NodeName')]
        [psobject[]]$Node,

        [Parameter(ParameterSetName='ClusterConfig',
            Position = 1,
            Mandatory = $true,
            HelpMessage = 'Please enter a name for your cluster')]
        [ValidateNotNull()]
        [Alias('Cluster', 'Name', 'HAClusterName')]
        [string]$ClusterName
    
    )
    Begin {
        Write-Output 'We will now begin cluster testing to ensure network, storage, connection to AD, and roles are set up properly.' 
        Write-Output 'Please Note: A cluster does not have to be set up prior. Only the role needs to be installed'
        Write-Output 'Please ensure you put all servers that you want to cluster and test in as comma-separated'
        
        $TestConnection = Test-Connection $Node
        IF (-Not($TestConnection)) {
            Write-Warning 'No connection was established to the specified nodes. Please try again...'
            Pause
            Exit
        }

        ELSE {
            Write-Output 'Connection to servers established. We will now proceed...'
            Pause
        }
    }    
    Process { 
        Try {
            IF ($PSBoundParameters.ContainsKey('Node')) {   
                ## ** Run a test on the cluster after it's set up as well **
                $TestCluster = Test-Cluster -Node $Node -Verbose
                $TestClusterOBJECTS = [pscustomobject] @{
                    'LastWriteDateandTime' = $TestCluster.LastWriteTime
                    'NameOfReport'         = $TestCluster.Name
                }
                $TestClusterOBJECTS
    
                $Input = Read-Host 'The test has completed. If passed, please press 1. If not, please press 2 to exit and fix issues'
                switch ($Input) {
    
                    '1' {
                        Write-Verbose 'Creating new cluster'
                        $NewClusterPARAMS = @{
                            'Name'    = $ClusterName
                            'Node'    = $Node
                            'Verbose' = $true
                        }
                        New-Cluster @NewClusterPARAMS           
                    }#1
            
                    '2' {
                        Pause
                        Exit
                    }#2
                }#Switch
            }
############################################################################### Storage Portion Below ####################################################################

            $NewClusterDiskQuestion = Read-Host 'Would you like to get your available disk clusters and add them to your cluster now? Y for yes or N to exit'
            IF ($NewClusterDiskQuestion -like 'y') {
                #Get available cluster disks
                Write-Output 'Please ensure you have your shared disk configured on BOTH clustered hosts.'

                Write-Verbose 'Setting Cluster Service to startup type: Automation'
                Set-Service -Name ClusSvc -StartupType Automatic

                Write-Verbose 'Starting Cluster Service'
                Start-Service -Name ClusSvc

                $GetDisk = Get-ClusterAvailableDisk

                FOREACH ($ClustDisk in $GetDisk) {
                    $ClusterDiskObject = [pscustomobject] @{
                        'Cluster' = $ClustDisk.Cluster
                        'Name'    = $ClustDisk.Name
                        'Size'    = $ClustDisk.Size
                    }
                    $ClusterDiskObject | Format-Table

                }#FOREACH
        
                #Connect clustered storage
                Pause
                Get-ClusterAvailableDisk | Add-ClusterDisk        
            }
    
            ELSE {
                Pause
                Exit
            } #ELSE
        }#TRY
        CATCH {
            $ClusteredDisks = Get-ClusterAvailableDisk
            $ClusteredDisksObject = [pscustomobject] @{
                                                       'Name' = $ClusteredDisks.Name
                                                      }
            $ClusteredDisksObject

            Write-Warning 'An error has occursed. Please review the logs in your specified ErrorLog location'
            $_ | Out-File $ErrorLog
            #Throw error to host
            $_
            Throw                                            
        }
    }#Process
    End {Write-Verbose 'The function has completed'}
}#Function