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.json 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" -MemoryStartup 8 -CPUCount 4
 
.LINK
    https://github.com/yourusername/Posh-HVTools
#>


Function New-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]$MachineName,

        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]$HVServer,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int64]$MemoryStartup = 4,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string[]]$Switchname = @(),

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int]$CPUCount = 2,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]$NotDynamicRam,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int64]$MemoryMinimum = 1468006400,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int64[]]$HardDiskSize = @(48),

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]$HDPath,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]$AutomaticCheckPoints,

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [int[]]$Vlan = @(),

        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [string]$jsonFilePath
    )
    begin {}
    process {
        if ($jsonFilePath) {
            if (Test-Path -Path $jsonFilePath) {
                Get-HVConfigFromJson -jsonFilePath $jsonFilePath
            } else {
                Write-Error "Configuration file $jsonFilePath not found"
                return
            }
        } else {
            $config = Get-Config
            if ($Switchname.Length -eq 0) {
                $Switchname += $config.config.Switchname
            }
            if (!($HDPath)) {
                $HDPath = $config.config.diskpaths.diskpath[0]
            }
            $HDSizeGB = ConvertTo-Bytes $HardDiskSize[0]

            $VlanId = $Vlan[0]
            $Switchname = $Switchname[0]
            $MemoryStartupBytes = ConvertTo-Bytes -RAM $MemoryStartup

            $ProgressValue = 20
            Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            New-VM -Name $MachineName -Generation 2 -MemoryStartupBytes ([system.int64]$MemoryStartupBytes) -SwitchName $Switchname[0] -ComputerName $HVServer | Out-Null
            $ProgressValue = 40
            Write-Progress -Activity "Creating" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            Set-VMProcessor -VMName $MachineName -Count $CPUCount -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 $HDSizeGB -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 {}
}

function Get-HVConfigFromJson {
    param(
        [Parameter(Mandatory = $true)]
        [string]$jsonFilePath
    )
    $jsonData = Get-Content -Path $jsonFilePath -Raw | ConvertFrom-Json
    if ($jsonData -is [Array]) {
        $machines = $jsonData
    } else {
        $machines = @($jsonData)
    }

    foreach ($machine in $machines) {
        try {
            $params = @{
                MachineName = $machine.MachineName
                HVServer = $machine.HVServer
                MemoryStartup = $machine.MemoryStartup
                CPUCount = $machine.CPUCount
                Switchname = $machine.SwitchName
                Vlan = $machine.Vlan
                HardDiskSize = $machine.HardDiskSize[0]
            }
           
            New-HVMachine @params
        }
        catch {
            Write-Error "Failed to create VM $($machine.MachineName): $_"
        }
    }
}

function ConvertTo-Bytes {
    param(
        [Parameter(Mandatory=$true)]
        $RAM
    )
    
    if ($RAM -is [string] -and $RAM -match '^(\d+)(GB|MB|KB)?$') {
        $value = [int64]$matches[1]
        $unit = $matches[2]
        
        switch ($unit) {
            'GB' { return $value * 1GB }
            'MB' { return $value * 1MB }
            'KB' { return $value * 1KB }
            default { return $value * 1GB }  # Default to GB if no unit
        }
    } elseif ($RAM -is [int64] -or $RAM -is [int]) {
        # Numeric input: small = GB, large = bytes
        if ($RAM -lt 1024) {
            return $RAM * 1GB
        } else {
            return $RAM
        }
    }
}

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

    process {
        if (!($HVServers)) {
            $config = Get-Config
            $HVServers = $config.config.servers.server
        }

        [int]$HVServerCount = $HVServers.count 
        [int]$HVServerProgressCount = 0
        $MachineInfo = New-Object 'System.Collections.Generic.List[HVMachineInformation]'
        foreach ($HVServer in $HVServers) {
            $AllHV = get-vm -ComputerName $HVServer | Where-Object {$_.VMName -match $MachineName}
            [int]$HVCount = $AllHV.count

            if ($HVCount -ne 0) {
                [int]$Progresscount = 1
                foreach ($HVName in ($AllHV).Name) {
                    $ProgressValue = [math]::round($Progresscount * 100 / $HVCount / $HVServerCount + $HVServerProgressCount * 100 / $HVServerCount,1)

                    Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
                    $Progresscount++

                    $NIC = get-vmnetworkAdapter -VMname $HVName -ComputerName $HVServer
                    $HVMachineInfo = get-vm -Name $HVName -ComputerName $HVServer | Select-Object Name, State, CPUUsage, ProcessorCount, MemoryStartup ,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
                    [int64[]]$HardDiskSize = @()
                    foreach ($EachHardDisk in $HardDisk) {
                        $VHD = Get-VHD -Path $EachHardDisk -ComputerName $HVServer
                        if ($VHD.ParentPath) {
                            $HasCheckPoint = $true
                        }
                        $HardDiskSize += $VHD.Size
                    }
                    
                    $Machine = [HVMachineInformation]::new(
                        $HVMachineInfo.Name,
                        $HVMachineInfo.State,
                        $HVMachineInfo.CPUUsage,
                        $HVMachineInfo.ProcessorCount,
                        $HVMachineInfo.MemoryStartup,
                        $HVMachineInfo.MemoryAssigned,
                        $NIC.SwitchName,
                        $NIC.VlanSetting.AccessVlanId,
                        $NIC.IPaddresses,
                        (Get-MACFormatted -Mac $NIC.macaddress),
                        $HardDisk,
                        $HardDiskSize,
                        $HasCheckPoint,
                        $BootOrder,
                        $HVMachineInfo.Uptime,
                        $HVMachineInfo.Status,
                        $HVMachineInfo.Version,
                        $Gen,
                        $HVHostInfo.ComputerName,
                        $HVHostInfo.LogicalProcessorCount,
                        $HVHostInfo.MemoryCapacity
                    )
                    $MachineInfo.Add($Machine)
                }
                $HVServerProgressCount++
            }
        }
        if ($ExportAsJsonFilePath) {
            $MachineInfo | ConvertTo-Json -Depth 10 | Out-File -FilePath $ExportAsJsonFilePath -Encoding utf8
            Write-Host "Machine information exported to: $ExportAsJsonFilePath" -ForegroundColor Green
        }
        return $MachineInfo
    }
    end {}
}

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

    process {
        if (!($HVServers)) {
            $config = Get-Config
            $HVServers = $config.config.servers.server
        }
        [int]$HVServerCount = $HVServers.count 
        [int]$HVServerProgressCount = 0
        $NetworkInfo = New-Object 'System.Collections.Generic.List[HVNetworkInfo]'
        foreach ($HVServer in $HVServers) {
            $AllHV = get-vm -ComputerName $HVServer | Where-Object {$_.VMName -match $MachineName}
            [int]$HVCount = $AllHV.count
            if ($HVCount -ne 0) {
                [int]$Progresscount = 1
                foreach ($HVName in ($AllHV).Name) {
                    $ProgressValue = [math]::round($Progresscount * 100 / $HVCount / $HVServerCount + $HVServerProgressCount * 100 / $HVServerCount,1)
                    Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
                    $Progresscount++
                    $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
                }
            }
            $HVServerProgressCount++
       }
        return $NetworkInfo
    }
    end {}
}

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 {}
}

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 {}
}

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

    process {
        $config = Get-Config
        if (!($HVServers)) {
            $HVServers = $config.config.servers.server
        }
        if (!($DiskDrivePath)) {
            $DiskDrivePaths = $config.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
                        if ($AllDiskCount -ne 0) {
                            [int]$ProgressPerDisk = $ProgressPerHVServer / $AllDiskCount / $DiskDriveCount
                        } else {
                            [int]$ProgressPerDisk = $ProgressPerHVServer / $DiskDriveCount
                            $ProgressValue += $ProgressPerDisk
                            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue # redundant but needed when vhd = 0 LA 250822
                        }
                        foreach ($HardDrive in $HardDrives) {
<#
                            "AllDiskCount: $AllDiskCount"
                            "diskDiskDriveCount: $DiskDriveCount"
                            "ProgressPerHVServer: $ProgressPerHVServer"
                            "progressperdisk: $ProgressPerDisk"
                            "progressvalue: $ProgressValue"
#>
                            
                            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 {}
}

function Get-Config {
    $jsonFilePath = Join-Path -Path $PSScriptRoot -ChildPath "HVparam.json"
    Return (Get-Content -Path $jsonFilePath | ConvertFrom-Json)
}

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-HVServer -MachineName $MachineName -ExactMatch
    }
    return $HVServers
}

Function Find-HVServer {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true)]
        [string]
        $MachineName,
        [Parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName = $true)]
        [switch]
        $ExactMatch
    )
    $config = Get-Config
    $HVServers = $config.config.servers.server
    $ReturnHVServers = @()
    [int]$ProgressCount = 0
    [bool]$FoundMachine = $false
    foreach ($HVServer in $HVServers) {
        $progressvalue = $ProgressCount * 100 / $HVServers.count
        Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
        $ProgressCount++
        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
        }
    }
    return $ReturnHVServers
}

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

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 {}
}

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 {}
}

function Restart-HVMachine {
    Param(
        [Parameter(Mandatory = $true,
        ValueFromPipelineByPropertyName = $true,
        ValueFromPipeline = $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 {}
}
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)) {
            $config = Get-Config
            $HVServers = $config.config.servers.server
        } else {
            $HVServers = $HVServer
        }
        $HVServerInfo = New-Object 'System.Collections.Generic.List[HVServerInformation]'
        [int]$progresscount = 0
        foreach ($HVServer in $HVServers) {
            $progressvalue = [math]::round($progresscount * 100 / $HVServers.count,1)
            Write-Progress -Activity "Processing" -Status "$ProgressValue% complete" -PercentComplete $ProgressValue
            $progresscount ++

            $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 * 1KB,
                $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
    [int64]$MemoryStartup
    [int64]$MemoryAssigned
    [array]$SwitchName
    [array]$Vlan
    [array]$IPaddresses
    [array]$MACAddress
    [array]$HardDisk
    [array]$HardDiskSize
    [bool]$Checkpoint
    [array]$BootInfo
    [timespan]$Uptime
    [string]$Status
    [string]$Version
    [string]$Generation
    [string]$HVServer
    [int]$LogicalProcessorCount
    [int64]$MemoryCapacity

    HVMachineInformation (
        [string]$MachineName,
        [string]$State,
        [int]$CPUUsage,
        [int]$CPUCount,
        [int64]$MemoryStartup,
        [int64]$MemoryAssigned,
        [array]$SwitchName,
        [array]$VLan,
        [array]$IPaddresses,
        [array]$MACAddress,
        [array]$HardDisk,
        [array]$HardDiskSize,
        [bool]$Checkpoint,
        [array]$BootInfo,
        [timespan]$Uptime,
        [string]$Status,
        [string]$Version,
        [string]$Generation,
        [string]$HVServer,
        [int]$LogicalProcessorCount,
        [int64]$MemoryCapacity
    )
    {
        $this.MachineName = $MachineName
        $this.State = $State
        $this.CPUUsage = $CPUUsage
        $this.CPUCount = $CPUCount
        $this.MemoryStartup = $MemoryStartup
        $this.MemoryAssigned = $MemoryAssigned
        $this.SwitchName = $SwitchName
        $this.Vlan = $VLan
        $this.IPaddresses = $IPaddresses
        $this.MacAddress = $MACAddress
        $this.HardDisk = $HardDisk
        $this.HardDiskSize = $HardDiskSize
        $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
    [int64]$MemoryCapacity
    [int64]$FreePhysicalMemory
    [datetime]$LastBootUpTime
    [timespan]$Uptime
    HVServerInformation (
        [string]$HVServer,
        [array]$HardDisk,
        [array]$HardDiskSize,
        [array]$HardDiskFree,
        [int]$LogicalProcessorCount,
        [int64]$MemoryCapacity,
        [int64]$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
    }
}