mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-22 16:01:43 -04:00
The export process now exports the VM directly from the build directory into the output directory. There are no intermediate steps or copying of files involved. This means that there is no longer any benefit in having a separate directory to house the VHD files - see #5206 for the reasoning behind the introduction of this feature. If a user wishes to house the build files on a separate disk from the output directory (perhaps for performance reasons or due to disk space limitations) they can still do so through the use of `temp_path`.
1339 lines
42 KiB
Go
1339 lines
42 KiB
Go
package hyperv
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/packer/common/powershell"
|
|
)
|
|
|
|
func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
|
|
var script = `
|
|
param([string]$switchName, [int]$addressIndex)
|
|
|
|
$HostVMAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName
|
|
if ($HostVMAdapter){
|
|
$HostNetAdapter = Get-NetAdapter | ?{ $_.DeviceID -eq $HostVMAdapter.DeviceId }
|
|
if ($HostNetAdapter){
|
|
$HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)")
|
|
if ($HostNetAdapterConfiguration){
|
|
return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex]
|
|
}
|
|
}
|
|
}
|
|
return $false
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, switchName, "0")
|
|
|
|
return cmdOut, err
|
|
}
|
|
|
|
func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) {
|
|
|
|
var script = `
|
|
param([string]$vmName, [int]$addressIndex)
|
|
try {
|
|
$adapter = Hyper-V\Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
|
|
if ($adapter.IPAddresses) {
|
|
$ip = $adapter.IPAddresses[$addressIndex]
|
|
} else {
|
|
$vm = Get-CimInstance -ClassName Msvm_ComputerSystem -Namespace root\virtualization\v2 -Filter "ElementName='$vmName'"
|
|
$ip_details = (Get-CimAssociatedInstance -InputObject $vm -ResultClassName Msvm_KvpExchangeComponent).GuestIntrinsicExchangeItems | %{ [xml]$_ } | ?{ $_.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text()='NetworkAddressIPv4']") }
|
|
|
|
if ($null -eq $ip_details) {
|
|
return $false
|
|
}
|
|
|
|
$ip_addresses = $ip_details.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
|
|
$ip = ($ip_addresses -split ";")[0]
|
|
}
|
|
} catch {
|
|
return $false
|
|
}
|
|
$ip
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName, "0")
|
|
|
|
return cmdOut, err
|
|
}
|
|
|
|
func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) {
|
|
var ps powershell.PowerShellCmd
|
|
var script string
|
|
|
|
script = `
|
|
param([string]$vmName, [string]$isoPath)
|
|
$dvdController = Hyper-V\Add-VMDvdDrive -VMName $vmName -path $isoPath -Passthru
|
|
$dvdController | Hyper-V\Set-VMDvdDrive -path $null
|
|
$result = "$($dvdController.ControllerNumber),$($dvdController.ControllerLocation)"
|
|
$result
|
|
`
|
|
|
|
cmdOut, err := ps.Output(script, vmName, isoPath)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
cmdOutArray := strings.Split(cmdOut, ",")
|
|
if len(cmdOutArray) != 2 {
|
|
return 0, 0, errors.New("Did not return controller number and controller location")
|
|
}
|
|
|
|
controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[0]), 10, 64)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
controllerNumber := uint(controllerNumberTemp)
|
|
|
|
controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[1]), 10, 64)
|
|
if err != nil {
|
|
return controllerNumber, 0, err
|
|
}
|
|
controllerLocation := uint(controllerLocationTemp)
|
|
|
|
return controllerNumber, controllerLocation, err
|
|
}
|
|
|
|
func MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)
|
|
$vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
|
|
if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
|
|
Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $path
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
|
|
return err
|
|
}
|
|
|
|
func UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
|
var script = `
|
|
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
|
$vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
|
|
if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
|
|
Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $null
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
|
|
return err
|
|
}
|
|
|
|
func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error {
|
|
|
|
if generation < 2 {
|
|
script := `
|
|
param([string]$vmName)
|
|
Hyper-V\Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","Floppy")
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
} else {
|
|
script := `
|
|
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
|
$vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
|
|
if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
|
|
Hyper-V\Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
|
|
return err
|
|
}
|
|
}
|
|
|
|
func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
|
|
var script = `
|
|
param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
|
|
$vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
|
|
if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
|
|
Hyper-V\Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10))
|
|
return err
|
|
}
|
|
|
|
func DeleteAllDvdDrives(vmName string) error {
|
|
var script = `
|
|
param([string]$vmName)
|
|
Hyper-V\Get-VMDvdDrive -VMName $vmName | Hyper-V\Remove-VMDvdDrive
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func MountFloppyDrive(vmName string, path string) error {
|
|
var script = `
|
|
param([string]$vmName, [string]$path)
|
|
Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $path
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, path)
|
|
return err
|
|
}
|
|
|
|
func UnmountFloppyDrive(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $null
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool) error {
|
|
|
|
if generation == 2 {
|
|
var script = `
|
|
param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks)
|
|
$vhdx = $vmName + '.vhdx'
|
|
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
|
|
if ($harddrivePath){
|
|
if($diffDisks -eq "true"){
|
|
New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
|
|
} else {
|
|
Copy-Item -Path $harddrivePath -Destination $vhdPath
|
|
}
|
|
Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation
|
|
} else {
|
|
Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes
|
|
Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation
|
|
}
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatInt(int64(generation), 10), strconv.FormatBool(diffDisks)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return DisableAutomaticCheckpoints(vmName)
|
|
} else {
|
|
var script = `
|
|
param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [string]$diffDisks, [string]$fixedVHD)
|
|
if($fixedVHD -eq "true"){
|
|
$vhdx = $vmName + '.vhd'
|
|
}
|
|
else{
|
|
$vhdx = $vmName + '.vhdx'
|
|
}
|
|
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
|
|
if ($harddrivePath){
|
|
if($diffDisks -eq "true"){
|
|
New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
|
|
}
|
|
else{
|
|
Copy-Item -Path $harddrivePath -Destination $vhdPath
|
|
}
|
|
Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName
|
|
} else {
|
|
if($fixedVHD -eq "true"){
|
|
Hyper-V\New-VHD -Path $vhdPath -Fixed -SizeBytes $newVHDSizeBytes
|
|
}
|
|
else {
|
|
Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes
|
|
}
|
|
Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName
|
|
}
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatBool(diffDisks), strconv.FormatBool(fixedVHD)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := DisableAutomaticCheckpoints(vmName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return DeleteAllDvdDrives(vmName)
|
|
}
|
|
}
|
|
|
|
func DisableAutomaticCheckpoints(vmName string) error {
|
|
var script = `
|
|
param([string]$vmName)
|
|
if ((Get-Command Hyper-V\Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
|
|
Hyper-V\Set-Vm -Name $vmName -AutomaticCheckpointsEnabled $false }
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func ExportVmxcVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
|
|
var script = `
|
|
param([string]$exportPath, [string]$vmName, [string]$snapshotName, [string]$allSnapshotsString)
|
|
|
|
$WorkingPath = Join-Path $exportPath $vmName
|
|
|
|
if (Test-Path $WorkingPath) {
|
|
throw "Export path working directory: $WorkingPath already exists!"
|
|
}
|
|
|
|
$allSnapshots = [System.Boolean]::Parse($allSnapshotsString)
|
|
|
|
if ($snapshotName) {
|
|
$snapshot = Hyper-V\Get-VMSnapshot -VMName $vmName -Name $snapshotName
|
|
Hyper-V\Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
|
|
} else {
|
|
if (!$allSnapshots) {
|
|
#Use last snapshot if one was not specified
|
|
$snapshot = Hyper-V\Get-VMSnapshot -VMName $vmName | Select -Last 1
|
|
} else {
|
|
$snapshot = $null
|
|
}
|
|
|
|
if (!$snapshot) {
|
|
#No snapshot clone
|
|
Hyper-V\Export-VM -Name $vmName -Path $exportPath -ErrorAction Stop
|
|
} else {
|
|
#Snapshot clone
|
|
Hyper-V\Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
$result = Get-ChildItem -Path $WorkingPath | Move-Item -Destination $exportPath -Force
|
|
$result = Remove-Item -Path $WorkingPath
|
|
`
|
|
|
|
allSnapshotsString := "False"
|
|
if allSnapshots {
|
|
allSnapshotsString = "True"
|
|
}
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString)
|
|
|
|
return err
|
|
}
|
|
|
|
func CopyVmxcVirtualMachine(exportPath string, cloneFromVmxcPath string) error {
|
|
var script = `
|
|
param([string]$exportPath, [string]$cloneFromVmxcPath)
|
|
if (!(Test-Path $cloneFromVmxcPath)){
|
|
throw "Clone from vmxc directory: $cloneFromVmxcPath does not exist!"
|
|
}
|
|
|
|
if (!(Test-Path $exportPath)){
|
|
New-Item -ItemType Directory -Force -Path $exportPath
|
|
}
|
|
$cloneFromVmxcPath = Join-Path $cloneFromVmxcPath '\*'
|
|
Copy-Item $cloneFromVmxcPath $exportPath -Recurse -Force
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, exportPath, cloneFromVmxcPath)
|
|
|
|
return err
|
|
}
|
|
|
|
func SetVmNetworkAdapterMacAddress(vmName string, mac string) error {
|
|
var script = `
|
|
param([string]$vmName, [string]$mac)
|
|
Hyper-V\Set-VMNetworkAdapter $vmName -staticmacaddress $mac
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, mac)
|
|
|
|
return err
|
|
}
|
|
|
|
func ImportVmxcVirtualMachine(importPath string, vmName string, harddrivePath string, ram int64, switchName string) error {
|
|
var script = `
|
|
param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName)
|
|
|
|
$VirtualHarddisksPath = Join-Path -Path $importPath -ChildPath 'Virtual Hard Disks'
|
|
if (!(Test-Path $VirtualHarddisksPath)) {
|
|
New-Item -ItemType Directory -Force -Path $VirtualHarddisksPath
|
|
}
|
|
|
|
$vhdPath = ""
|
|
if ($harddrivePath){
|
|
$vhdx = $vmName + '.vhdx'
|
|
$vhdPath = Join-Path -Path $VirtualHarddisksPath -ChildPath $vhdx
|
|
}
|
|
|
|
$VirtualMachinesPath = Join-Path $importPath 'Virtual Machines'
|
|
if (!(Test-Path $VirtualMachinesPath)) {
|
|
New-Item -ItemType Directory -Force -Path $VirtualMachinesPath
|
|
}
|
|
|
|
$VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.vmcx -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
|
|
if (!$VirtualMachinePath){
|
|
$VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
|
|
}
|
|
if (!$VirtualMachinePath){
|
|
$VirtualMachinePath = Get-ChildItem -Path $importPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
|
|
}
|
|
|
|
$compatibilityReport = Hyper-V\Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $importPath -SmartPagingFilePath $importPath -SnapshotFilePath $importPath -VhdDestinationPath $VirtualHarddisksPath -GenerateNewId -Copy:$false
|
|
if ($vhdPath){
|
|
Copy-Item -Path $harddrivePath -Destination $vhdPath
|
|
$existingFirstHarddrive = $compatibilityReport.VM.HardDrives | Select -First 1
|
|
if ($existingFirstHarddrive) {
|
|
$existingFirstHarddrive | Hyper-V\Set-VMHardDiskDrive -Path $vhdPath
|
|
} else {
|
|
Hyper-V\Add-VMHardDiskDrive -VM $compatibilityReport.VM -Path $vhdPath
|
|
}
|
|
}
|
|
Hyper-V\Set-VMMemory -VM $compatibilityReport.VM -StartupBytes $memoryStartupBytes
|
|
$networkAdaptor = $compatibilityReport.VM.NetworkAdapters | Select -First 1
|
|
Hyper-V\Disconnect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor
|
|
Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor -SwitchName $switchName
|
|
$vm = Hyper-V\Import-VM -CompatibilityReport $compatibilityReport
|
|
|
|
if ($vm) {
|
|
$result = Hyper-V\Rename-VM -VM $vm -NewName $VMName
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, importPath, vmName, harddrivePath, strconv.FormatInt(ram, 10), switchName)
|
|
|
|
return err
|
|
}
|
|
|
|
func CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
|
|
if cloneFromVmName != "" {
|
|
if err := ExportVmxcVirtualMachine(path, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if cloneFromVmxcPath != "" {
|
|
if err := CopyVmxcVirtualMachine(path, cloneFromVmxcPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := ImportVmxcVirtualMachine(path, vmName, harddrivePath, ram, switchName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return DeleteAllDvdDrives(vmName)
|
|
}
|
|
|
|
func GetVirtualMachineGeneration(vmName string) (uint, error) {
|
|
var script = `
|
|
param([string]$vmName)
|
|
$generation = Hyper-V\Get-Vm -Name $vmName | %{$_.Generation}
|
|
if (!$generation){
|
|
$generation = 1
|
|
}
|
|
return $generation
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName)
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32)
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
generation := uint(generationUint32)
|
|
|
|
return generation, err
|
|
}
|
|
|
|
func SetVirtualMachineCpuCount(vmName string, cpu uint) error {
|
|
|
|
var script = `
|
|
param([string]$vmName, [int]$cpu)
|
|
Hyper-V\Set-VMProcessor -VMName $vmName -Count $cpu
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10))
|
|
return err
|
|
}
|
|
|
|
func SetVirtualMachineVirtualizationExtensions(vmName string, enableVirtualizationExtensions bool) error {
|
|
|
|
var script = `
|
|
param([string]$vmName, [string]$exposeVirtualizationExtensionsString)
|
|
$exposeVirtualizationExtensions = [System.Boolean]::Parse($exposeVirtualizationExtensionsString)
|
|
Hyper-V\Set-VMProcessor -VMName $vmName -ExposeVirtualizationExtensions $exposeVirtualizationExtensions
|
|
`
|
|
exposeVirtualizationExtensionsString := "False"
|
|
if enableVirtualizationExtensions {
|
|
exposeVirtualizationExtensionsString = "True"
|
|
}
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, exposeVirtualizationExtensionsString)
|
|
return err
|
|
}
|
|
|
|
func SetVirtualMachineDynamicMemory(vmName string, enableDynamicMemory bool) error {
|
|
|
|
var script = `
|
|
param([string]$vmName, [string]$enableDynamicMemoryString)
|
|
$enableDynamicMemory = [System.Boolean]::Parse($enableDynamicMemoryString)
|
|
Hyper-V\Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $enableDynamicMemory
|
|
`
|
|
enableDynamicMemoryString := "False"
|
|
if enableDynamicMemory {
|
|
enableDynamicMemoryString = "True"
|
|
}
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, enableDynamicMemoryString)
|
|
return err
|
|
}
|
|
|
|
func SetVirtualMachineMacSpoofing(vmName string, enableMacSpoofing bool) error {
|
|
var script = `
|
|
param([string]$vmName, $enableMacSpoofing)
|
|
Hyper-V\Set-VMNetworkAdapter -VMName $vmName -MacAddressSpoofing $enableMacSpoofing
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
|
|
enableMacSpoofingString := "Off"
|
|
if enableMacSpoofing {
|
|
enableMacSpoofingString = "On"
|
|
}
|
|
|
|
err := ps.Run(script, vmName, enableMacSpoofingString)
|
|
return err
|
|
}
|
|
|
|
func SetVirtualMachineSecureBoot(vmName string, enableSecureBoot bool, templateName string) error {
|
|
var script = `
|
|
param([string]$vmName, [string]$enableSecureBootString, [string]$templateName)
|
|
$cmdlet = Get-Command Hyper-V\Set-VMFirmware
|
|
# The SecureBootTemplate parameter is only available in later versions
|
|
if ($cmdlet.Parameters.SecureBootTemplate) {
|
|
Hyper-V\Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBootString -SecureBootTemplate $templateName
|
|
} else {
|
|
Hyper-V\Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBootString
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
|
|
enableSecureBootString := "Off"
|
|
if enableSecureBoot {
|
|
enableSecureBootString = "On"
|
|
}
|
|
|
|
if templateName == "" {
|
|
templateName = "MicrosoftWindows"
|
|
}
|
|
|
|
err := ps.Run(script, vmName, enableSecureBootString, templateName)
|
|
return err
|
|
}
|
|
|
|
func DeleteVirtualMachine(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
|
|
$vm = Hyper-V\Get-VM -Name $vmName
|
|
if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) {
|
|
Hyper-V\Stop-VM -VM $vm -TurnOff -Force -Confirm:$false
|
|
}
|
|
|
|
Hyper-V\Remove-VM -Name $vmName -Force -Confirm:$false
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func ExportVirtualMachine(vmName string, path string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName, [string]$path)
|
|
Hyper-V\Export-VM -Name $vmName -Path $path
|
|
|
|
if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.VMCX')))
|
|
{
|
|
$vm = Hyper-V\Get-VM -Name $vmName
|
|
$vm_adapter = Hyper-V\Get-VMNetworkAdapter -VM $vm | Select -First 1
|
|
|
|
$config = [xml]@"
|
|
<?xml version="1.0" ?>
|
|
<configuration>
|
|
<properties>
|
|
<subtype type="integer">$($vm.Generation - 1)</subtype>
|
|
<name type="string">$($vm.Name)</name>
|
|
</properties>
|
|
<settings>
|
|
<processors>
|
|
<count type="integer">$($vm.ProcessorCount)</count>
|
|
</processors>
|
|
<memory>
|
|
<bank>
|
|
<dynamic_memory_enabled type="bool">$($vm.DynamicMemoryEnabled)</dynamic_memory_enabled>
|
|
<limit type="integer">$($vm.MemoryMaximum / 1MB)</limit>
|
|
<reservation type="integer">$($vm.MemoryMinimum / 1MB)</reservation>
|
|
<size type="integer">$($vm.MemoryStartup / 1MB)</size>
|
|
</bank>
|
|
</memory>
|
|
</settings>
|
|
<AltSwitchName type="string">$($vm_adapter.SwitchName)</AltSwitchName>
|
|
<boot>
|
|
<device0 type="string">Optical</device0>
|
|
</boot>
|
|
<secure_boot_enabled type="bool">False</secure_boot_enabled>
|
|
<notes type="string">$($vm.Notes)</notes>
|
|
<vm-controllers/>
|
|
</configuration>
|
|
"@
|
|
|
|
if ($vm.Generation -eq 1)
|
|
{
|
|
$vm_controllers = Hyper-V\Get-VMIdeController -VM $vm
|
|
$controller_type = $config.SelectSingleNode('/configuration/vm-controllers')
|
|
# IDE controllers are not stored in a special XML container
|
|
}
|
|
else
|
|
{
|
|
$vm_controllers = Hyper-V\Get-VMScsiController -VM $vm
|
|
$controller_type = $config.CreateElement('scsi')
|
|
$controller_type.SetAttribute('ChannelInstanceGuid', 'x')
|
|
# SCSI controllers are stored in the scsi XML container
|
|
if ((Hyper-V\Get-VMFirmware -VM $vm).SecureBoot -eq [Microsoft.HyperV.PowerShell.OnOffState]::On)
|
|
{
|
|
$config.configuration.secure_boot_enabled.'#text' = 'True'
|
|
}
|
|
else
|
|
{
|
|
$config.configuration.secure_boot_enabled.'#text' = 'False'
|
|
}
|
|
}
|
|
|
|
$vm_controllers | ForEach {
|
|
$controller = $config.CreateElement('controller' + $_.ControllerNumber)
|
|
$_.Drives | ForEach {
|
|
$drive = $config.CreateElement('drive' + ($_.DiskNumber + 0))
|
|
$drive_path = $config.CreateElement('pathname')
|
|
$drive_path.SetAttribute('type', 'string')
|
|
$drive_path.AppendChild($config.CreateTextNode($_.Path))
|
|
$drive_type = $config.CreateElement('type')
|
|
$drive_type.SetAttribute('type', 'string')
|
|
if ($_ -is [Microsoft.HyperV.PowerShell.HardDiskDrive])
|
|
{
|
|
$drive_type.AppendChild($config.CreateTextNode('VHD'))
|
|
}
|
|
elseif ($_ -is [Microsoft.HyperV.PowerShell.DvdDrive])
|
|
{
|
|
$drive_type.AppendChild($config.CreateTextNode('ISO'))
|
|
}
|
|
else
|
|
{
|
|
$drive_type.AppendChild($config.CreateTextNode('NONE'))
|
|
}
|
|
$drive.AppendChild($drive_path)
|
|
$drive.AppendChild($drive_type)
|
|
$controller.AppendChild($drive)
|
|
}
|
|
$controller_type.AppendChild($controller)
|
|
}
|
|
if ($controller_type.Name -ne 'vm-controllers')
|
|
{
|
|
$config.SelectSingleNode('/configuration/vm-controllers').AppendChild($controller_type)
|
|
}
|
|
|
|
$config.Save([IO.Path]::Combine($path, $vm.Name, 'Virtual Machines', 'box.xml'))
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, path)
|
|
return err
|
|
}
|
|
|
|
func PreserveLegacyExportBehaviour(srcPath, dstPath string) error {
|
|
|
|
var script = `
|
|
param([string]$srcPath, [string]$dstPath)
|
|
|
|
# Validate the paths returning an error if they are empty or don't exist
|
|
$srcPath, $dstPath | % {
|
|
if ($_) {
|
|
if (! (Test-Path $_)) {
|
|
[System.Console]::Error.WriteLine("Path $_ does not exist")
|
|
exit
|
|
}
|
|
} else {
|
|
[System.Console]::Error.WriteLine("A supplied path is empty")
|
|
exit
|
|
}
|
|
}
|
|
|
|
# Export-VM should just create directories at the root of the export path
|
|
# but, just in case, move all files as well...
|
|
Move-Item -Path (Join-Path (Get-Item $srcPath).FullName "*.*") -Destination (Get-Item $dstPath).FullName
|
|
|
|
# Move directories with content; Delete empty directories
|
|
$dirObj = Get-ChildItem $srcPath -Directory | % {
|
|
New-Object PSObject -Property @{
|
|
FullName=$_.FullName;
|
|
HasContent=$(if ($_.GetFileSystemInfos().Count -gt 0) {$true} else {$false})
|
|
}
|
|
}
|
|
foreach ($directory in $dirObj) {
|
|
if ($directory.HasContent) {
|
|
Move-Item -Path $directory.FullName -Destination (Get-Item $dstPath).FullName
|
|
} else {
|
|
Remove-Item -Path $directory.FullName
|
|
}
|
|
}
|
|
|
|
# Only remove the source directory if it is now empty
|
|
if ( $((Get-Item $srcPath).GetFileSystemInfos().Count) -eq 0 ) {
|
|
Remove-Item -Path $srcPath
|
|
} else {
|
|
# 'Return' an error message to PowerShellCmd as the directory should
|
|
# always be empty at the end of the script. The check is here to stop
|
|
# the Remove-Item command from doing any damage if some unforeseen
|
|
# error has occured
|
|
[System.Console]::Error.WriteLine("Refusing to remove $srcPath as it is not empty")
|
|
exit
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, srcPath, dstPath)
|
|
|
|
return err
|
|
}
|
|
|
|
func CompactDisks(path string) (result string, err error) {
|
|
var script = `
|
|
param([string]$srcPath)
|
|
|
|
$disks = Get-ChildItem -Path $srcPath -Recurse -Filter *.vhd* -ErrorAction SilentlyContinue | % { $_.FullName }
|
|
# Failure to find any disks is treated as a 'soft' error. Simply print out
|
|
# a warning and exit
|
|
if ($disks.Length -eq 0) {
|
|
Write-Output "WARNING: No disks found under $srcPath"
|
|
exit
|
|
}
|
|
|
|
foreach ($disk in $disks) {
|
|
Write-Output "Compacting disk: $(Split-Path $disk -leaf)"
|
|
|
|
$sizeBefore = $disk.Length
|
|
Optimize-VHD -Path $disk -Mode Full
|
|
$sizeAfter = $disk.Length
|
|
|
|
# Calculate the percentage change in disk size
|
|
if ($sizeAfter -gt 0) { # Protect against division by zero
|
|
$percentChange = ( ( $sizeAfter / $sizeBefore ) * 100 ) - 100
|
|
switch($percentChange) {
|
|
{$_ -lt 0} {Write-Output "Disk size reduced by: $(([math]::Abs($_)).ToString("#.#"))%"}
|
|
{$_ -eq 0} {Write-Output "Disk size is unchanged"}
|
|
{$_ -gt 0} {Write-Output "WARNING: Disk size increased by: $($_.ToString("#.#"))%"}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
result, err = ps.Output(script, path)
|
|
return
|
|
}
|
|
|
|
func CreateVirtualSwitch(switchName string, switchType string) (bool, error) {
|
|
|
|
var script = `
|
|
param([string]$switchName,[string]$switchType)
|
|
$switches = Hyper-V\Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
|
|
if ($switches.Count -eq 0) {
|
|
Hyper-V\New-VMSwitch -Name $switchName -SwitchType $switchType
|
|
return $true
|
|
}
|
|
return $false
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, switchName, switchType)
|
|
var created = strings.TrimSpace(cmdOut) == "True"
|
|
return created, err
|
|
}
|
|
|
|
func DeleteVirtualSwitch(switchName string) error {
|
|
|
|
var script = `
|
|
param([string]$switchName)
|
|
$switch = Hyper-V\Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
|
|
if ($switch -ne $null) {
|
|
$switch | Hyper-V\Remove-VMSwitch -Force -Confirm:$false
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, switchName)
|
|
return err
|
|
}
|
|
|
|
func StartVirtualMachine(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
|
|
Hyper-V\Start-VM -Name $vmName -Confirm:$false
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func RestartVirtualMachine(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
Hyper-V\Restart-VM $vmName -Force -Confirm:$false
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func StopVirtualMachine(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName
|
|
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
|
Hyper-V\Stop-VM -VM $vm -Force -Confirm:$false
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error {
|
|
|
|
integrationServiceId := ""
|
|
switch integrationServiceName {
|
|
case "Time Synchronization":
|
|
integrationServiceId = "2497F4DE-E9FA-4204-80E4-4B75C46419C0"
|
|
case "Heartbeat":
|
|
integrationServiceId = "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47"
|
|
case "Key-Value Pair Exchange":
|
|
integrationServiceId = "2A34B1C2-FD73-4043-8A5B-DD2159BC743F"
|
|
case "Shutdown":
|
|
integrationServiceId = "9F8233AC-BE49-4C79-8EE3-E7E1985B2077"
|
|
case "VSS":
|
|
integrationServiceId = "5CED1297-4598-4915-A5FC-AD21BB4D02A4"
|
|
case "Guest Service Interface":
|
|
integrationServiceId = "6C09BB55-D683-4DA0-8931-C9BF705F6480"
|
|
default:
|
|
panic("unrecognized Integration Service Name")
|
|
}
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$integrationServiceId)
|
|
Hyper-V\Get-VMIntegrationService -VmName $vmName | ?{$_.Id -match $integrationServiceId} | Hyper-V\Enable-VMIntegrationService
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, integrationServiceId)
|
|
return err
|
|
}
|
|
|
|
func SetNetworkAdapterVlanId(switchName string, vlanId string) error {
|
|
|
|
var script = `
|
|
param([string]$networkAdapterName,[string]$vlanId)
|
|
Hyper-V\Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $networkAdapterName -Access -VlanId $vlanId
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, switchName, vlanId)
|
|
return err
|
|
}
|
|
|
|
func SetVirtualMachineVlanId(vmName string, vlanId string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$vlanId)
|
|
Hyper-V\Set-VMNetworkAdapterVlan -VMName $vmName -Access -VlanId $vlanId
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, vlanId)
|
|
return err
|
|
}
|
|
|
|
func GetExternalOnlineVirtualSwitch() (string, error) {
|
|
|
|
var script = `
|
|
$adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | Sort-Object -Descending -Property Speed
|
|
foreach ($adapter in $adapters) {
|
|
$switch = Hyper-V\Get-VMSwitch -SwitchType External | Where-Object { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
|
|
|
|
if ($switch -ne $null) {
|
|
$switch.Name
|
|
break
|
|
}
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var switchName = strings.TrimSpace(cmdOut)
|
|
return switchName, nil
|
|
}
|
|
|
|
func CreateExternalVirtualSwitch(vmName string, switchName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$switchName)
|
|
$switch = $null
|
|
$names = @('ethernet','wi-fi','lan')
|
|
$adapters = foreach ($name in $names) {
|
|
Get-NetAdapter -Physical -Name $name -ErrorAction SilentlyContinue | where status -eq 'up'
|
|
}
|
|
|
|
foreach ($adapter in $adapters) {
|
|
$switch = Hyper-V\Get-VMSwitch -SwitchType External | where { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
|
|
|
|
if ($switch -eq $null) {
|
|
$switch = Hyper-V\New-VMSwitch -Name $switchName -NetAdapterName $adapter.Name -AllowManagementOS $true -Notes 'Parent OS, VMs, WiFi'
|
|
}
|
|
|
|
if ($switch -ne $null) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if($switch -ne $null) {
|
|
Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter -VMSwitch $switch
|
|
} else {
|
|
Write-Error 'No internet adapters found'
|
|
}
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, switchName)
|
|
return err
|
|
}
|
|
|
|
func GetVirtualMachineSwitchName(vmName string) (string, error) {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
(Hyper-V\Get-VMNetworkAdapter -VMName $vmName).SwitchName
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(cmdOut), nil
|
|
}
|
|
|
|
func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$switchName)
|
|
Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter -SwitchName $switchName
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, switchName)
|
|
return err
|
|
}
|
|
|
|
func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64, vhdBlockSize int64, controllerType string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes, [string]$vhdBlockSizeInByte, [string]$controllerType)
|
|
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdName
|
|
Hyper-V\New-VHD -path $vhdPath -SizeBytes $vhdSizeInBytes -BlockSizeBytes $vhdBlockSizeInByte
|
|
Hyper-V\Add-VMHardDiskDrive -VMName $vmName -path $vhdPath -controllerType $controllerType
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, vhdRoot, vhdName, strconv.FormatInt(vhdSizeBytes, 10), strconv.FormatInt(vhdBlockSize, 10), controllerType)
|
|
return err
|
|
}
|
|
|
|
func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName,[string]$switchName)
|
|
Hyper-V\Set-VMNetworkAdapterVlan -VMName $vmName -Untagged
|
|
Hyper-V\Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $switchName -Untagged
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, switchName)
|
|
return err
|
|
}
|
|
|
|
func IsRunning(vmName string) (bool, error) {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
var isRunning = strings.TrimSpace(cmdOut) == "True"
|
|
return isRunning, err
|
|
}
|
|
|
|
func IsOff(vmName string) (bool, error) {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
var isRunning = strings.TrimSpace(cmdOut) == "True"
|
|
return isRunning, err
|
|
}
|
|
|
|
func Uptime(vmName string) (uint64, error) {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
$vm.Uptime.TotalSeconds
|
|
`
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName)
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
uptime, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64)
|
|
|
|
return uptime, err
|
|
}
|
|
|
|
func Mac(vmName string) (string, error) {
|
|
var script = `
|
|
param([string]$vmName, [int]$adapterIndex)
|
|
try {
|
|
$adapter = Hyper-V\Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
|
|
$mac = $adapter[$adapterIndex].MacAddress
|
|
if($mac -eq $null) {
|
|
return ""
|
|
}
|
|
} catch {
|
|
return ""
|
|
}
|
|
$mac
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, vmName, "0")
|
|
|
|
return cmdOut, err
|
|
}
|
|
|
|
func IpAddress(mac string) (string, error) {
|
|
var script = `
|
|
param([string]$mac, [int]$addressIndex)
|
|
try {
|
|
$vm = Hyper-V\Get-VM | ?{$_.NetworkAdapters.MacAddress -eq $mac}
|
|
if ($vm.NetworkAdapters.IpAddresses) {
|
|
$ipAddresses = $vm.NetworkAdapters.IPAddresses
|
|
if ($ipAddresses -isnot [array]) {
|
|
$ipAddresses = @($ipAddresses)
|
|
}
|
|
$ip = $ipAddresses[$addressIndex]
|
|
} else {
|
|
$vm_info = Get-CimInstance -ClassName Msvm_ComputerSystem -Namespace root\virtualization\v2 -Filter "ElementName='$($vm.Name)'"
|
|
$ip_details = (Get-CimAssociatedInstance -InputObject $vm_info -ResultClassName Msvm_KvpExchangeComponent).GuestIntrinsicExchangeItems | %{ [xml]$_ } | ?{ $_.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text()='NetworkAddressIPv4']") }
|
|
|
|
if ($null -eq $ip_details) {
|
|
return ""
|
|
}
|
|
|
|
$ip_addresses = $ip_details.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
|
|
$ip = ($ip_addresses -split ";")[0]
|
|
}
|
|
} catch {
|
|
return ""
|
|
}
|
|
$ip
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
cmdOut, err := ps.Output(script, mac, "0")
|
|
|
|
return cmdOut, err
|
|
}
|
|
|
|
func TurnOff(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
|
Hyper-V\Stop-VM -Name $vmName -TurnOff -Force -Confirm:$false
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func ShutDown(vmName string) error {
|
|
|
|
var script = `
|
|
param([string]$vmName)
|
|
$vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
|
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
|
Hyper-V\Stop-VM -Name $vmName -Force -Confirm:$false
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName)
|
|
return err
|
|
}
|
|
|
|
func TypeScanCodes(vmName string, scanCodes string) error {
|
|
if len(scanCodes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var script = `
|
|
param([string]$vmName, [string]$scanCodes)
|
|
#Requires -Version 3
|
|
|
|
function Hyper-V\Get-VMConsole
|
|
{
|
|
[CmdletBinding()]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string] $VMName
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$vm = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1
|
|
if ($vm -eq $null){
|
|
Write-Error ("VirtualMachine({0}) is not found!" -f $VMName)
|
|
}
|
|
|
|
$vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false
|
|
|
|
if ($vmKeyboard -eq $null) {
|
|
$vmKeyboard = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1
|
|
}
|
|
|
|
if ($vmKeyboard -eq $null) {
|
|
$vmKeyboard = Get-CimInstance -Namespace "root\virtualization" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1
|
|
}
|
|
|
|
if ($vmKeyboard -eq $null){
|
|
Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName)
|
|
}
|
|
|
|
#TODO: It may be better using New-Module -AsCustomObject to return console object?
|
|
|
|
#Console object to return
|
|
$console = [pscustomobject] @{
|
|
Msvm_ComputerSystem = $vm
|
|
Msvm_Keyboard = $vmKeyboard
|
|
}
|
|
|
|
#Need to import assembly to use System.Windows.Input.Key
|
|
Add-Type -AssemblyName WindowsBase
|
|
|
|
#region Add Console Members
|
|
$console | Add-Member -MemberType ScriptMethod -Name TypeText -Value {
|
|
[OutputType([bool])]
|
|
param (
|
|
[ValidateNotNullOrEmpty()]
|
|
[Parameter(Mandatory)]
|
|
[string] $AsciiText
|
|
)
|
|
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeText" -Arguments @{ asciiText = $AsciiText }
|
|
return (0 -eq $result.ReturnValue)
|
|
}
|
|
|
|
#Define method:TypeCtrlAltDel
|
|
$console | Add-Member -MemberType ScriptMethod -Name TypeCtrlAltDel -Value {
|
|
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeCtrlAltDel"
|
|
return (0 -eq $result.ReturnValue)
|
|
}
|
|
|
|
#Define method:TypeKey
|
|
$console | Add-Member -MemberType ScriptMethod -Name TypeKey -Value {
|
|
[OutputType([bool])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[Windows.Input.Key] $Key,
|
|
[Windows.Input.ModifierKeys] $ModifierKey = [Windows.Input.ModifierKeys]::None
|
|
)
|
|
|
|
$keyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey($Key)
|
|
|
|
switch ($ModifierKey)
|
|
{
|
|
([Windows.Input.ModifierKeys]::Control){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftCtrl)}
|
|
([Windows.Input.ModifierKeys]::Alt){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftAlt)}
|
|
([Windows.Input.ModifierKeys]::Shift){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftShift)}
|
|
([Windows.Input.ModifierKeys]::Windows){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LWin)}
|
|
}
|
|
|
|
if ($ModifierKey -eq [Windows.Input.ModifierKeys]::None)
|
|
{
|
|
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
|
|
}
|
|
else
|
|
{
|
|
$this.Msvm_Keyboard | Invoke-CimMethod -MethodName "PressKey" -Arguments @{ keyCode = $modifierKeyCode }
|
|
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
|
|
$this.Msvm_Keyboard | Invoke-CimMethod -MethodName "ReleaseKey" -Arguments @{ keyCode = $modifierKeyCode }
|
|
}
|
|
$result = return (0 -eq $result.ReturnValue)
|
|
}
|
|
|
|
#Define method:Scancodes
|
|
$console | Add-Member -MemberType ScriptMethod -Name TypeScancodes -Value {
|
|
[OutputType([bool])]
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[byte[]] $ScanCodes
|
|
)
|
|
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeScancodes" -Arguments @{ ScanCodes = $ScanCodes }
|
|
return (0 -eq $result.ReturnValue)
|
|
}
|
|
|
|
#Define method:ExecCommand
|
|
$console | Add-Member -MemberType ScriptMethod -Name ExecCommand -Value {
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[string] $Command
|
|
)
|
|
if ([String]::IsNullOrEmpty($Command)){
|
|
return
|
|
}
|
|
|
|
$console.TypeText($Command) > $null
|
|
$console.TypeKey([Windows.Input.Key]::Enter) > $null
|
|
#sleep -Milliseconds 100
|
|
}
|
|
|
|
#Define method:Dispose
|
|
$console | Add-Member -MemberType ScriptMethod -Name Dispose -Value {
|
|
$this.Msvm_ComputerSystem.Dispose()
|
|
$this.Msvm_Keyboard.Dispose()
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
return $console
|
|
}
|
|
|
|
$vmConsole = Hyper-V\Get-VMConsole -VMName $vmName
|
|
$scanCodesToSend = ''
|
|
$scanCodes.Split(' ') | %{
|
|
$scanCode = $_
|
|
|
|
if ($scanCode.StartsWith('wait')){
|
|
$timeToWait = $scanCode.Substring(4)
|
|
if (!$timeToWait){
|
|
$timeToWait = "1"
|
|
}
|
|
|
|
if ($scanCodesToSend){
|
|
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
|
|
|
|
$scanCodesToSendByteArray | %{
|
|
$vmConsole.TypeScancodes($_)
|
|
}
|
|
}
|
|
|
|
write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point."
|
|
Start-Sleep -s $timeToWait
|
|
|
|
$scanCodesToSend = ''
|
|
} else {
|
|
if ($scanCodesToSend){
|
|
write-host "Sending special code '$scanCodesToSend' '$scanCode'"
|
|
$scanCodesToSend = "$scanCodesToSend $scanCode"
|
|
} else {
|
|
write-host "Sending char '$scanCode'"
|
|
$scanCodesToSend = "$scanCode"
|
|
}
|
|
}
|
|
}
|
|
if ($scanCodesToSend){
|
|
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
|
|
|
|
$scanCodesToSendByteArray | %{
|
|
$vmConsole.TypeScancodes($_)
|
|
}
|
|
}
|
|
`
|
|
|
|
var ps powershell.PowerShellCmd
|
|
err := ps.Run(script, vmName, scanCodes)
|
|
return err
|
|
}
|
|
|
|
func ConnectVirtualMachine(vmName string) (context.CancelFunc, error) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cmd := exec.CommandContext(ctx, "vmconnect.exe", "localhost", vmName)
|
|
err := cmd.Start()
|
|
if err != nil {
|
|
// Failed to start so cancel function not required
|
|
cancel = nil
|
|
}
|
|
return cancel, err
|
|
}
|
|
|
|
func DisconnectVirtualMachine(cancel context.CancelFunc) {
|
|
cancel()
|
|
}
|