HVTools.psm1

<#
.SYNOPSIS
    PowerShell module for managing Hyper-V virtual machines across multiple hosts.
 
.DESCRIPTION
    Posh-HVTools provides a comprehensive set of functions for creating, managing,
    and monitoring Hyper-V virtual machines across multiple Hyper-V hosts.
     
    The module simplifies common Hyper-V administration tasks such as:
    - Creating new virtual machines
    - Retrieving detailed VM information
    - Managing VM state (start, stop, restart)
    - Configuring boot devices
    - Managing virtual disks
    - Cleaning up VMs and disks
 
.NOTES
    File Name : HVTools.psm1
    Prerequisite : PowerShell 5.1 or later
                     Hyper-V PowerShell module installed
                     Administrative rights on Hyper-V hosts
    Configuration : Requires HVparam.xml file in the same directory
 
.EXAMPLE
    # List all VMs matching a pattern across all Hyper-V hosts
    Get-HVMachineInfo -MachineName "Web*"
 
.EXAMPLE
    # Create a new virtual machine
    New-HVMachine -MachineName "WebServer01" -HVServer "HyperVHost1" -RAM 8GB -NumCPU 4
 
.LINK
    https://github.com/yourusername/Posh-HVTools
#>


<#
.SYNOPSIS
    Creates a new Hyper-V virtual machine.
 
.DESCRIPTION
    Creates a new Generation 2 Hyper-V virtual machine with specified configuration parameters.
    The function handles virtual machine creation, processor assignment, memory allocation,
    virtual hard disk creation, and network configuration.
 
.PARAMETER MachineName
    The name of the virtual machine to create.
 
.PARAMETER HVServer
    The Hyper-V server to create the virtual machine on.
 
.PARAMETER RAM
    The amount of RAM to allocate to the virtual machine. Default is 4 Gigabyte.
    The parameter is written in how many GB the machine should have. I.e. 8 = 8 Gigabyte
 
.PARAMETER Switchname
    The name of the virtual switch to connect the virtual machine to.
    If not specified, uses the default switch name from the configuration.
 
.PARAMETER NumCPU
    The number of virtual processors to assign to the virtual machine. Default is 2.
 
.PARAMETER NotDynamicRam
    If specified, enables dynamic memory for the virtual machine.
 
.PARAMETER MemoryMinimum
    The minimum memory for the virtual machine when using dynamic memory. Default is 1468006400 bytes.
 
.PARAMETER HDSize
    The size of the virtual hard disk. Default is 32GB.
 
.PARAMETER HDPath
    The path where the virtual hard disk will be stored.
    If not specified, uses the default disk path from the configuration.
 
.PARAMETER AutomaticCheckPoints
    If specified, enables automatic checkpoints for the virtual machine.
 
.PARAMETER VlanId
    The VLAN ID to assign to the virtual machine's network adapter.
 
.EXAMPLE
    New-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Creates a new virtual machine named "TestVM" on the Hyper-V server "HyperVHost1" with default settings.
 
.EXAMPLE
    New-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1" -RAM 8GB -NumCPU 4 -VlanId 10
 
    Creates a new virtual machine with 8GB RAM, 4 virtual processors, and VLAN ID 10.
 
.NOTES
    This function requires administrative privileges and the Hyper-V role installed.
#>

Function New-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [System.Int64]
        $RAM = 4,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $Switchname,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int]
        $NumCPU = 2,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]
        $NotDynamicRam,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int]
        $MemoryMinimum = 1468006400,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HDSize = [System.UInt64]32GB,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HDPath,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]
        $AutomaticCheckPoints,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int]
        $VlanId
    )
    begin {}
    process {
        $xml = Get-XMLConfig
        if (!($Switchname)) {
            $Switchname = $xml.config.Switchname
        }
        if (!($HDPath)) {
            $HDPath = $xml.config.Diskpaths.Diskpath[0]
        }
        $ProgressValue = 20
        Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        New-VM -Name $MachineName -Generation 2 -MemoryStartupBytes ([system.int64]$RAM*1024*1024*1024) -SwitchName $Switchname -ComputerName $HVServer | Out-Null
        $ProgressValue = 40
        Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        Set-VMProcessor -VMName $MachineName -Count $NumCPU -ComputerName $HVServer
        if ($NotDynamicRam) {
            Set-VM -VMName $MachineName -DynamicMemory -ComputerName $HVServer
        }
        $ProgressValue = 60
        Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        set-vm -VMName $MachineName -MemoryMinimumBytes $MemoryMinimum -ComputerName $HVServer

        $VHD = Join-Path -Path $HDPath -ChildPath "$MachineName.vhdx"
        New-VHD -Path $VHD -SizeBytes $HDSize -Dynamic -ComputerName $HVServer | Out-Null
        $ProgressValue = 80
        Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        Add-VMHardDiskDrive -Path $VHD -VMName $MachineName -ComputerName $HVServer
        $ProgressValue = 95
        Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        if (!($AutomaticCheckPoints)) {
            set-vm -VMName $MachineName -AutomaticCheckpointsEnabled:$false -ComputerName $HVServer
        }
        if ($VlanId) {
            get-vmnetworkAdapter -VMname $MachineName -ComputerName $HVServer | set-VMNetworkAdapterVlan -Access -VlanId $VlanId
        }
        Start-VM -VMName $MachineName -ComputerName $hvserver
        Start-Sleep -Seconds 2
        stop-VM -VMName $MachineName -ComputerName $hvserver -Force

        Get-HVMachineInfo -MachineName $MachineName -HVServer $HVServer
    }
    end {}
}

<#
.SYNOPSIS
    Retrieves detailed information about Hyper-V virtual machines.
 
.DESCRIPTION
    Gets comprehensive information about one or more Hyper-V virtual machines, including
    hardware configuration, network settings, disk information, and Hyper-V host details.
 
.PARAMETER MachineName
    The name or pattern of the virtual machine(s) to retrieve information for.
    If empty, retrieves information for all virtual machines.
 
.PARAMETER HVServers
    An array of Hyper-V server names to query. If not specified, uses servers from the configuration.
 
.EXAMPLE
    Get-HVMachineInfo -MachineName "TestVM"
 
    Retrieves information about the virtual machine named "TestVM" from all configured Hyper-V servers.
 
.EXAMPLE
    Get-HVMachineInfo -MachineName "Test" -HVServers @("HyperVHost1", "HyperVHost2")
 
    Retrieves information about all virtual machines with "Test" in their name from the specified Hyper-V servers.
 
.NOTES
    Returns a collection of HVMachineInfo objects containing detailed virtual machine information.
#>

function Get-HVMachineInfo {
    Param(
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName = '',
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        if (!($HVServers)) {
            $xml = Get-XMLConfig
            $HVServers = $xml.config.Servers.Server
        }
        [int]$ProgressValue = 0
        [int]$ProgressPerHVServer = 100 / $HVServers.Count
        [int]$HVServerCount = 0
        $MachineInfo = New-Object 'System.Collections.Generic.List[HVMachineInformation]'
        foreach ($HVServer in $HVServers) {
            $AllHV = get-vm -ComputerName $HVServer | Where-Object {$_.VMName -match $MachineName}
            [int]$AllHVCount = $AllHV.count
            [int]$HVMachineCount = 1
            
            if ($AllHVCount -ne 0) {
                [int]$ProgressPerHV = $ProgressPerHVServer / $AllHVCount
                foreach ($HVName in ($AllHV).Name) {
                    [float]$HVProgressValue = $HVServerCount * 100 / $HVServers.Count + $HVMachineCount * 100 / $HVServers.Count / $AllHVCount
                    "procent: $HVProgressValue"
                    Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
                    $ProgressValue += $ProgressPerHV
                    $HVMachineCount++
                    $NIC = get-vmnetworkAdapter -VMname $HVName -ComputerName $HVServer
                    $HVMachineInfo = get-vm -Name $HVName -ComputerName $HVServer | Select-Object Name, State, CPUUsage, ProcessorCount, MemoryAssigned, Uptime, Status, Version, Generation
                    $HVHostInfo = get-vmhost -ComputerName $HVServer | Select-Object ComputerName, LogicalProcessorCount, MemoryCapacity
                    $Gen = $HVMachineInfo.Generation
                    switch ($Gen) {
                        "1" {
                            $BootOrder = (Get-VMBios -VMName $HVName -ComputerName $HVServer).StartupOrder
                        }
                        "2" {
                            $BootOrder = (get-vmfirmware -VMName $HVName -ComputerName $HVServer).bootorder.BootType
                        }
                    }
                    $HardDisk = (get-vmhardDiskDrive -VMName $HVName -ComputerName $HVServer).Path
                    [bool]$HasCheckPoint = $false
                    foreach ($EachHardDisk in $HardDisk) {
                        if ((Get-VHD -Path $EachHardDisk -ComputerName $HVServer).ParentPath) {
                            $HasCheckPoint = $true
                        }
                    }
                    
                    $Machine = [HVMachineInformation]::new(
                        $HVMachineInfo.Name,
                        $HVMachineInfo.State,
                        $HVMachineInfo.CPUUsage,
                        $HVMachineInfo.ProcessorCount,
                        $HVMachineInfo.MemoryAssigned,
                        $NIC.SwitchName,
                        $NIC.VlanSetting.AccessVlanId,
                        $NIC.IPaddresses,
                        (Get-MACFormatted -Mac $NIC.macaddress),
                        $HardDisk,
                        $HasCheckPoint,
                        $BootOrder,
                        $HVMachineInfo.Uptime,
                        $HVMachineInfo.Status,
                        $HVMachineInfo.Version,
                        $Gen,
                        $HVHostInfo.ComputerName,
                        $HVHostInfo.LogicalProcessorCount,
                        $HVHostInfo.MemoryCapacity
                    )
                    $MachineInfo.Add($Machine)
                }
            }
            $HVServerCount++
        }
        return $MachineInfo
    }
    end {}
}

<#
.SYNOPSIS
    Retrieves network information about Hyper-V virtual machines.
 
.DESCRIPTION
    Gets detailed network configuration information for one or more Hyper-V virtual machines,
    including switch names, IP addresses, MAC addresses, VLAN settings, and operation modes.
 
.PARAMETER MachineName
    The name or pattern of the virtual machine(s) to retrieve network information for.
    If empty, retrieves network information for all virtual machines.
 
.PARAMETER HVServers
    An array of Hyper-V server names to query. If not specified, uses servers from the configuration.
 
.EXAMPLE
    Get-HVMachineNetworkInfo -MachineName "TestVM"
 
    Retrieves network information for the virtual machine named "TestVM" from all configured Hyper-V servers.
 
.EXAMPLE
    Get-HVMachineNetworkInfo -MachineName "Web" -HVServers @("HyperVHost1", "HyperVHost2")
 
    Retrieves network information for all virtual machines with "Web" in their name from the specified Hyper-V servers.
 
.NOTES
    Returns a collection of HVNetworkInfo objects containing network configuration details.
#>

function Get-HVMachineNetworkInfo {
    Param(
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName = '',
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        if (!($HVServers)) {
            $xml = Get-XMLConfig
            $HVServers = $xml.config.Servers.Server
        }
        [int]$ProgressValue = 0
        [int]$ProgressPerHVServer = 100 / $HVServers.Count
        $NetworkInfo = New-Object 'System.Collections.Generic.List[HVNetworkInfo]'
        foreach ($HVServer in $HVServers) {
            $AllHV = get-vm -ComputerName $HVServer | Where-Object {$_.VMName -match $MachineName}
            [int]$AllHVCount = $AllHV.count
            if ($AllHVCount -ne 0) {
                [int]$ProgressPerHV =$ProgressPerHVServer / $AllHVCount
                foreach ($HVName in ($AllHV).Name) {
                    Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
                    $AllNics = get-vmnetworkAdapter -VMname $HVName -ComputerName $HVServer
                    foreach ($Nic in $AllNics) {
                        $Network = [HVNetworkInfo]::new(
                            $HVName,
                            $NIC.SwitchName,
                            $NIC.IPaddresses,
                            (Get-MACFormatted -Mac $NIC.macaddress),
                            $NIC.VlanSetting.OperationMode,
                            $NIC.VlanSetting.AccessVlanId
                        )
                        $NetworkInfo.Add($Network)
                    }
                    $ProgressValue += $ProgressPerHV
                }
            }
       }
        return $NetworkInfo
    }
    end {}
}

<#
.SYNOPSIS
    Configures the boot device for a Hyper-V virtual machine.
 
.DESCRIPTION
    Sets the boot device and boot order for a Generation 1 or Generation 2 Hyper-V virtual machine.
    The function supports configuring network, DVD, hard drive, file, or floppy boot devices.
 
.PARAMETER MachineName
    The name of the virtual machine to configure.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER HVServers
    An array of Hyper-V servers to search for the virtual machine.
 
.PARAMETER nic
    If specified, sets the network adapter as the primary boot device.
 
.PARAMETER dvd
    If specified, sets the DVD drive as the primary boot device.
 
.PARAMETER File
    If specified, sets the file as the primary boot device (Generation 2 VMs only).
 
.PARAMETER Floppy
    If specified, sets the floppy drive as the primary boot device (Generation 1 VMs only).
 
.PARAMETER Drive
    If specified, sets the hard drive as the primary boot device (Generation 2 VMs only).
 
.EXAMPLE
    Set-HVMachineBootDevice -MachineName "TestVM" -HVServer "HyperVHost1" -nic
 
    Configures the virtual machine "TestVM" to boot from the network adapter.
 
.EXAMPLE
    Set-HVMachineBootDevice -MachineName "TestVM" -HVServer "HyperVHost1" -dvd
 
    Configures the virtual machine "TestVM" to boot from the DVD drive.
 
.NOTES
    The function automatically determines the VM generation and applies the appropriate boot configuration.
#>

function Set-HVMachineBootDevice {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true,
        ParameterSetName = 'NIC')]
        [switch]
        $nic,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true,
        ParameterSetName = 'DVD')]
        [switch]
        $dvd,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true,
        ParameterSetName = 'File')]
        [switch]
        $File,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true,
        ParameterSetName = 'Floppy')]
        [switch]
        $Floppy,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true,
        ParameterSetName = 'Drive')]
        [switch]
        $Drive
    )
    begin{}

    process{
        $HVServers = Get-HVServers -MachineName $MachineName -HVServer $HVServer -HVServers $HVServers
        foreach ($HVServer in $HVServers) {
            foreach ($Machine in (get-vm -ComputerName $HVServer | Where-Object {$_.Name -eq $MachineName})) {
                $FoundMachineName = $Machine.VMName
                switch ($machine.generation) {
                    "1" {
                        $StartupOrder = ''
                        if ($nic) {
                            $StartupOrder = @("LegacyNetworkAdapter", "Floppy", "CD", "IDE")
                        }
                        if ($dvd) {
                            $StartupOrder = @("CD", "LegacyNetworkAdapter", "Floppy", "IDE")
                        }
                        if ($File) {
                            $StartupOrder = @("IDE","LegacyNetworkAdapter", "Floppy", "CD")
                        }
                        if ($Floppy) {
                            $StartupOrder = @("Floppy", "LegacyNetworkAdapter", "CD", "IDE")
                        }
                        Set-VMBios -VMName $FoundMachineName -ComputerName $HVServer -StartupOrder $StartupOrder
                        Get-VMBios -VMName $FoundMachineName -ComputerName $HVServer
                    }
                    "2"{
                        if ($nic) {
                            $BootDevice = Get-VMNetworkAdapter -VMName $FoundMachineName -ComputerName $HVServer
                        }
                        if ($dvd) {
                            $BootDevice = Get-VMDvdDrive -VMName $FoundMachineName -ComputerName $HVServer
                        }
                        if ($File) {
                            $BootDevice = (get-vmfirmware -VMName $FoundMachineName -ComputerName $HVServer).BootOrder | Where-Object {$_.BootType -eq 'File'}
                        }
                        if ($Drive) {
                            $BootDevice = (get-vmfirmware -VMName $FoundMachineName -ComputerName $HVServer).BootOrder | Where-Object {$_.BootType -eq 'Drive'}
                        }
                        Set-VMFirmware -VMName $FoundMachineName -FirstBootDevice ($BootDevice) -ComputerName $HVServer
                        get-vmfirmware -VMName $FoundMachineName -ComputerName $HVServer | Select-Object -ExpandProperty BootOrder
                    }
                }
            }
        }
    }
    end {}
}

<#
.SYNOPSIS
    Removes a Hyper-V virtual machine and its associated virtual hard disks.
 
.DESCRIPTION
    Removes a Hyper-V virtual machine and all associated virtual hard disks, including
    any differencing disks or checkpoints. The function first stops the virtual machine,
    identifies all disk files, removes them, and then deletes the virtual machine.
 
.PARAMETER MachineName
    The name of the virtual machine to remove.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER HVServers
    An array of Hyper-V servers to search for the virtual machine.
 
.EXAMPLE
    Remove-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Removes the virtual machine "TestVM" and its associated disks from the Hyper-V server "HyperVHost1".
 
.NOTES
    This operation is destructive and cannot be undone. All virtual hard disks associated with the
    virtual machine will be permanently deleted.
#>

function Remove-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        $HVServers = Get-HVServers -MachineName $MachineName -HVServer $HVServer -HVServers $HVServers
        [int]$ProgressValue = 0
        Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        foreach ($HVServer in $HVServers) {
            [int]$ProgressValue = 25
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            Stop-VM -Name  $MachineName -ComputerName $HVServer -ErrorAction Ignore -WarningAction SilentlyContinue -Force | Out-Null
            $Disks = Get-VMHardDiskDrive -VMName $MachineName -ComputerName $HVServer
            $DisksToRemove = @()
            [int]$ProgressValue = 50
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            foreach ($disk in $disks.path) {
                $DisksToRemove += $disk
                $vhd = (Get-VHD -Path $disk -ComputerName $HVServer).ParentPath
                    if ($vhd) {
                        $DisksToRemove += $vhd
                        do {
                            $vhd = (Get-VHD -Path $vhd -ComputerName $HVServer).ParentPath
                            if ($vhd) {
                                $DisksToRemove += $vhd
                            }
                    } while ($vhd)
                }
            }
            [int]$ProgressValue = 60
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            foreach ($disk in $DisksToRemove) {
                $DiskSplit = $disk.split(':')
                $UNCPath = "\\$HVServer\$($DiskSplit[0])`$$($DiskSplit[1])"
                Remove-Item -Path $UNCPath -Force
            }
            [int]$ProgressValue = 80
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            remove-vm -Name $MachineName -ComputerName $HVServer -Force
        }
    }
    end {}
}

<#
.SYNOPSIS
    Identifies unassigned virtual hard disks on Hyper-V servers.
 
.DESCRIPTION
    Scans the specified Hyper-V servers and disk paths to identify virtual hard disks
    and their association with virtual machines. This helps identify orphaned or
    unassigned disk files that may be candidates for cleanup.
 
.PARAMETER DiskDrivePath
    The path to search for virtual hard disks. If not specified, uses paths from the configuration.
 
.PARAMETER HVServers
    An array of Hyper-V servers to scan. If not specified, uses servers from the configuration.
 
.EXAMPLE
    Get-HVUnassignedDiskDrive
 
    Scans all configured Hyper-V servers and disk paths for unassigned virtual hard disks.
 
.EXAMPLE
    Get-HVUnassignedDiskDrive -HVServers @("HyperVHost1") -DiskDrivePath "D:\VirtualMachines"
 
    Scans the specified server and disk path for unassigned virtual hard disks.
 
.NOTES
    Returns a collection of HVDiskInfo objects containing disk paths and their association status.
#>

function Get-HVUnassignedDiskDrive {
    Param(
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $DiskDrivePath,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        $xml = Get-XMLConfig
        if (!($HVServers)) {
            $HVServers = $xml.config.Servers.Server
        }
        if (!($DiskDrivePath)) {
            $DiskDrivePaths = $xml.config.Diskpaths.Diskpath
        }
        [int]$ProgressValue = 0
        [int]$ProgressPerHVServer = 100 / $HVServers.Count
        $DiskInfo = New-Object 'System.Collections.Generic.List[HVDiskInfo]'
        foreach ($HVServer in $HVServers) {
            [int]$DiskDriveCount = 0
            foreach ($DiskDrivePath in $DiskDrivePaths) {
                $DiskSplit = $DiskDrivePath.split(':')
                $UNCPath = "\\$HVServer\$($DiskSplit[0])`$$($DiskSplit[1])"
                if (Test-Path -Path $UNCPath) {
                    $DiskDriveCount += 1
                }
            }
            if ($DiskDriveCount -gt 0) {
                foreach ($DiskDrivePath in $DiskDrivePaths) {
                    $DiskSplit = $DiskDrivePath.split(':')
                    $UNCPath = "\\$HVServer\$($DiskSplit[0])`$$($DiskSplit[1])"
                    if (Test-Path -Path $UNCPath) {
                        $HardDrives = (get-childitem -Path $UNCPath).FullName
                        $AllHV = get-vm -ComputerName $HVServer
                        [int]$AllDiskCount = $HardDrives.count
                        [int]$ProgressPerDisk = $ProgressPerHVServer / $AllDiskCount / $DiskDriveCount
                        foreach ($HardDrive in $HardDrives) {
                            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
                            $UNCDiskSplit = $HardDrive.split('$')
                            $LocalDiskPath = "$($UNCDiskSplit[0][-1]):$($UNCDiskSplit[1])"
                            $Outcome = "Not Assigned"
                            [bool]$Match = $false
                            foreach ($vm in $AllHV) {
                                foreach ($VMDisk in (Get-VMHardDiskDrive -VMName $vm.name -ComputerName $HVServer).Path) {
                                    if ($VMDisk -eq $LocalDiskPath -and !($Match)) {
                                        $Outcome = $vm.Name
                                        $Match = $true
                                    }
                                }
                            }
                            $ProgressValue += $ProgressPerDisk
                            $Disk = [HVDiskInfo]::new(
                                $HVServer,
                                $LocalDiskPath,
                                $Outcome
                            )
                            $DiskInfo.add($Disk)
                        }
                    }
                }
            }
        }
        Return $DiskInfo
    }
    end {}
}

<#
.SYNOPSIS
    Loads the module configuration from the XML file.
 
.DESCRIPTION
    Retrieves the configuration settings from the HVparam.xml file located in the
    same directory as the module. This configuration contains Hyper-V server names,
    default disk paths, and other module settings.
 
.EXAMPLE
    $config = Get-XMLConfig
 
    Loads the module configuration.
 
.NOTES
    Internal helper function used by other module functions.
#>

function Get-XMLConfig {
    $xmlFilePath = Join-Path -Path $PSScriptRoot -ChildPath "HVparam.xml"
    Return [xml](Get-Content -Path $xmlFilePath)
}

<#
.SYNOPSIS
    Resolves the Hyper-V servers for a virtual machine operation.
 
.DESCRIPTION
    Determines which Hyper-V servers to use for a virtual machine operation based on
    the provided parameters. Returns a server name or an array of server names.
 
.PARAMETER MachineName
    The name of the virtual machine to operate on.
 
.PARAMETER HVServer
    A specific Hyper-V server to use.
 
.PARAMETER HVServers
    An array of Hyper-V servers to use.
 
.EXAMPLE
    $servers = Get-HVServers -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Returns the specified Hyper-V server.
 
.NOTES
    Internal helper function used by other module functions.
#>

function Get-HVServers {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    if ($HVServer) {
        $HVServers = $HVServer
    }
    if (!($HVServers)) {
        $HVServers  = Find-HVServers -MachineName $MachineName -ExactMatch
    }
    return $HVServers
}

<#
.SYNOPSIS
    Finds Hyper-V servers hosting a specific virtual machine.
 
.DESCRIPTION
    Searches through configured Hyper-V servers to find those hosting a virtual machine
    with the specified name or matching a pattern.
 
.PARAMETER MachineName
    The name or pattern of the virtual machine to find.
 
.PARAMETER ExactMatch
    If specified, requires an exact match of the virtual machine name.
    Otherwise, uses a pattern match.
 
.EXAMPLE
    Find-HVServers -MachineName "TestVM" -ExactMatch
 
    Finds Hyper-V servers hosting a virtual machine named exactly "TestVM".
 
.NOTES
    Internal helper function used by other module functions.
#>

Function Find-HVServers {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]
        $ExactMatch
    )
    $xml = Get-XMLConfig
    $HVServers = $xml.config.Servers.Server
    $ReturnHVServers = @()
    [int]$ProgressValue = 0
    [int]$ProgressPerHV = 100 / $HVServers.count
    [bool]$FoundMachine = $false
    foreach ($HVServer in $HVServers) {
        Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        if ($ExactMatch) {
            $FoundMachine = get-vm -ComputerName $HVServer | Where-Object {$_.Name -eq $MachineName}
        } else {
            $FoundMachine = get-vm -ComputerName $HVServer | Where-Object {$_.Name -match $MachineName}
        }
        if ($FoundMachine) {
            $ReturnHVServers += $HVServer
        }
        $ProgressValue += $ProgressPerHV
    }
    return $ReturnHVServers
}

<#
.SYNOPSIS
    Formats a MAC address with colon separators.
 
.DESCRIPTION
    Converts a MAC address to the standard format with colon separators between each octet.
 
.PARAMETER Mac
    The MAC address to format.
 
.EXAMPLE
    Get-MACFormatted -Mac "0123456789AB"
 
    Returns "01:23:45:67:89:AB"
 
.NOTES
    Internal helper function used by other module functions.
#>

Function Get-MACFormatted {
    Param(
        [Parameter(Mandatory = $true)]
        [array]
        $Mac
    )
    return $Mac -replace '(.{2})(?!$)', '$1:'
}

<#
.SYNOPSIS
    Changes the state of a Hyper-V virtual machine.
 
.DESCRIPTION
    Performs a state change operation (start, stop, or restart) on a virtual machine.
 
.PARAMETER MachineName
    The name of the virtual machine to operate on.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER Action
    The action to perform: "Start", "Stop", or "Restart".
 
.EXAMPLE
    Set-HVMachineState -MachineName "TestVM" -HVServer "HyperVHost1" -Action "Start"
 
    Starts the virtual machine "TestVM" on the Hyper-V server "HyperVHost1".
 
.NOTES
    Internal helper function used by the state management functions.
#>

Function Set-HVMachineState {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [ValidateSet ("Stop", "Restart", "Start")]
        [string]
        $Action
    )
    begin{}

    process {
        switch ($Action) {
            "Stop" {
                Stop-VM -ComputerName $HVServer -Name $MachineName -Force
            }
            "Restart" {
                Restart-VM -ComputerName $HVServer -Name $MachineName -Force
            }
            "Start" {
                Start-VM -ComputerName $HVServer -Name $MachineName
            }
        }
    }
    end {}
}

<#
.SYNOPSIS
    Stops a Hyper-V virtual machine.
 
.DESCRIPTION
    Forcibly stops a virtual machine on one or more Hyper-V servers.
 
.PARAMETER MachineName
    The name of the virtual machine to stop.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER HVServers
    An array of Hyper-V servers to search for the virtual machine.
 
.EXAMPLE
    Stop-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Stops the virtual machine "TestVM" on the Hyper-V server "HyperVHost1".
 
.NOTES
    This is a forceful shutdown equivalent to pulling the power cord.
#>

function Stop-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        $HVServers = Get-HVServers -MachineName $MachineName -HVServer $HVServer -HVServers $HVServers
        foreach ($HVServer in $HVServers) {
            Set-HVMachineState -MachineName $MachineName -HVServer $HVServer -Action Stop
        }
    }
    end {}
}

<#
.SYNOPSIS
    Restarts a Hyper-V virtual machine.
 
.DESCRIPTION
    Forcibly restarts a virtual machine on one or more Hyper-V servers.
 
.PARAMETER MachineName
    The name of the virtual machine to restart.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER HVServers
    An array of Hyper-V servers to search for the virtual machine.
 
.EXAMPLE
    Restart-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Restarts the virtual machine "TestVM" on the Hyper-V server "HyperVHost1".
 
.NOTES
    This is a forceful restart.
#>

function Restart-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin{}

    process {
        $HVServers = Get-HVServers -MachineName $MachineName -HVServer $HVServer -HVServers $HVServers
        foreach ($HVServer in $HVServers) {
            Set-HVMachineState -MachineName $MachineName -HVServer $HVServer -Action Restart
        }
    }
    end {}
}

<#
.SYNOPSIS
    Starts a Hyper-V virtual machine.
 
.DESCRIPTION
    Starts a virtual machine on one or more Hyper-V servers.
 
.PARAMETER MachineName
    The name of the virtual machine to start.
 
.PARAMETER HVServer
    The Hyper-V server where the virtual machine is located.
 
.PARAMETER HVServers
    An array of Hyper-V servers to search for the virtual machine.
 
.EXAMPLE
    Start-HVMachine -MachineName "TestVM" -HVServer "HyperVHost1"
 
    Starts the virtual machine "TestVM" on the Hyper-V server "HyperVHost1".
#>

function Start-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [array]
        $HVServers
    )
    begin {}

    process {
        $HVServers = Get-HVServers -MachineName $MachineName -HVServer $HVServer -HVServers $HVServers
        foreach ($HVServer in $HVServers) {
            Set-HVMachineState -MachineName $MachineName -HVServer $HVServer -Action Start
        }
    }
    end {}
}

function Get-HVServerInfo {
    Param(
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $HVServer
    )
    begin{}
    process {
        if (!($HVServer)) {
            $xml = Get-XMLConfig
            $HVServers = $xml.config.Servers.Server
        } else {
            $HVServers = $HVServer
        }
        $HVServerInfo = New-Object 'System.Collections.Generic.List[HVServerInformation]'
        [float]$progressvalue = 0
        [int]$progresscount = 0
        foreach ($HVServer in $HVServers) {
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            $progresscount ++
            $progressvalue = [math]::round($progresscount * 100 / $HVServers.count,1)

            $SrvInfo1 = get-vmhost -ComputerName $HVServer
            $SrvInfo2 = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $HVServer | Select-Object PSComputerName, LastBootUpTime, @{Name='Uptime'; Expression={(Get-Date) - $_.LastBootUpTime}}, FreePhysicalMemory
            $SrvInfo3 = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $HVServer -Filter "DriveType=3" | Select-Object DeviceID,
                          @{Name='Size'; Expression={[math]::Round($_.Size/1GB, 2)}},
                          @{Name='FreeSpace'; Expression={[math]::Round($_.FreeSpace/1GB, 2)}}
            $Machine = [HVServerInformation]::new(
                $SrvInfo1.ComputerName,
                $SrvInfo3.DeviceID,
                $SrvInfo3.Size,
                $SrvInfo3.FreeSpace,
                $SrvInfo1.LogicalProcessorCount,
                $SrvInfo1.MemoryCapacity,
                $SrvInfo2.FreePhysicalMemory,
                $SrvInfo2.LastBootUpTime,
                $SrvInfo2.Uptime
            )
            $HVServerInfo.Add($Machine)
        }
        return $HVServerInfo




    }
    end {}


}

# Class Definitions

<#
.SYNOPSIS
    Class for storing detailed Hyper-V virtual machine information.
 
.DESCRIPTION
    Represents comprehensive information about a Hyper-V virtual machine,
    including its configuration, state, network settings, and host information.
 
.NOTES
    Used by Get-HVMachineInfo to return structured information about virtual machines.
#>

Class HVMachineInformation {
    [string]$MachineName
    [string]$State
    [int]$CPUUsage
    [int]$CPUCount
    [UInt64]$MemoryAssigned
    [array]$SwitchName
    [array]$Vlan
    [array]$IPaddresses
    [array]$MACAddress
    [array]$HardDisk
    [bool]$Checkpoint
    [array]$BootInfo
    [timespan]$Uptime
    [string]$Status
    [string]$Version
    [string]$Generation
    [string]$HVServer
    [int]$LogicalProcessorCount
    [UInt64]$MemoryCapacity

    HVMachineInformation (
        [string]$MachineName,
        [string]$State,
        [int]$CPUUsage,
        [int]$CPUCount,
        [UInt64]$MemoryAssigned,
        [array]$SwitchName,
        [array]$VLan,
        [array]$IPaddresses,
        [array]$MACAddress,
        [array]$HardDisk,
        [bool]$Checkpoint,
        [array]$BootInfo,
        [timespan]$Uptime,
        [string]$Status,
        [string]$Version,
        [string]$Generation,
        [string]$HVServer,
        [int]$LogicalProcessorCount,
        [UInt64]$MemoryCapacity
    )
    {
        $this.MachineName = $MachineName
        $this.State = $State
        $this.CPUUsage = $CPUUsage
        $this.CPUCount = $CPUCount
        $this.MemoryAssigned = $MemoryAssigned
        $this.SwitchName = $SwitchName
        $this.Vlan = $VLan
        $this.IPaddresses = $IPaddresses
        $this.MacAddress = $MACAddress
        $this.HardDisk = $HardDisk
        $this.Checkpoint = $Checkpoint
        $this.BootInfo = $BootInfo
        $this.Uptime = $Uptime
        $this.Status = $Status
        $this.Version = $Version
        $this.Generation = $Generation
        $this.HVServer = $HVServer
        $this.LogicalProcessorCount = $LogicalProcessorCount
        $this.MemoryCapacity = $MemoryCapacity
    }
}
Class HVServerInformation {
    [string]$HVServer
    [array]$HardDisk
    [array]$HardDiskSize
    [array]$HardDiskFree
    [int]$LogicalProcessorCount
    [UInt64]$MemoryCapacity
    [UInt64]$FreePhysicalMemory
    [datetime]$LastBootUpTime
    [timespan]$Uptime
    HVServerInformation (
        [string]$HVServer,
        [array]$HardDisk,
        [array]$HardDiskSize,
        [array]$HardDiskFree,
        [int]$LogicalProcessorCount,
        [UInt64]$MemoryCapacity,
        [UInt64]$FreePhysicalMemory,
        [datetime]$LastBootUpTime,
        [timespan]$Uptime
    )
    {
        $this.HVServer = $HVServer
        $this.HardDisk = $HardDisk
        $this.HardDiskSize = $HardDiskSize
        $this.HardDiskFree = $HardDiskFree
        $this.LogicalProcessorCount = $LogicalProcessorCount
        $this.MemoryCapacity = $MemoryCapacity
        $this.FreePhysicalMemory = $FreePhysicalMemory
        $this.LastBootUpTime = $LastBootUpTime
        $this.Uptime = $Uptime
    }
}

<#
.SYNOPSIS
    Class for storing Hyper-V disk information.
 
.DESCRIPTION
    Represents information about a virtual hard disk file, including its
    location and associated virtual machine (if any).
 
.NOTES
    Used by Get-HVUnassignedDiskDrive to return structured information about virtual disks.
#>

Class HVDiskInfo {
    [string]$HVServer
    [string]$Diskname
    [string]$MachineName

    HVDiskInfo (
        [string]$HVServer,
        [string]$Diskname,
        [string]$MachineName
    )
    {
        $this.HVServer = $HVServer
        $this.Diskname = $Diskname
        $this.MachineName = $MachineName
    }
}

<#
.SYNOPSIS
    Class for storing Hyper-V network adapter information.
 
.DESCRIPTION
    Represents detailed network configuration information for a
    virtual machine network adapter, including IP, MAC, and VLAN settings.
 
.NOTES
    Used by Get-HVMachineNetworkInfo to return structured information about network adapters.
#>

Class HVNetworkInfo {
    [string]$MachineName
    [string]$SwitchName
    [array]$IPaddresses
    [string]$MacAddress
    [string]$OperationMode
    [int]$Vlan

    HVNetworkInfo (
        [string]$MachineName,
        [string]$SwitchName,
        [array]$IPaddresses,
        [string]$MacAddress,   
        [string]$OperationMode,
        [int]$Vlan
    )
    {
        $this.MachineName = $MachineName
        $this.SwitchName = $SwitchName
        $this.IPaddresses = $IPaddresses
        $this.MacAddress = $MacAddress
        $this.OperationMode = $OperationMode
        $this.Vlan = $Vlan
    }
}