github.com/codecheq/hashicorp-packer@v1.3.2/common/powershell/hyperv/hyperv.go (about)

     1  package hyperv
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"os/exec"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/packer/common/powershell"
    11  )
    12  
    13  func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
    14  	var script = `
    15  param([string]$switchName, [int]$addressIndex)
    16  
    17  $HostVMAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName
    18  if ($HostVMAdapter){
    19      $HostNetAdapter = Get-NetAdapter | ?{ $_.DeviceID -eq $HostVMAdapter.DeviceId }
    20      if ($HostNetAdapter){
    21          $HostNetAdapterConfiguration =  @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)")
    22          if ($HostNetAdapterConfiguration){
    23              return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex]
    24          }
    25      }
    26  }
    27  return $false
    28  `
    29  
    30  	var ps powershell.PowerShellCmd
    31  	cmdOut, err := ps.Output(script, switchName, "0")
    32  
    33  	return cmdOut, err
    34  }
    35  
    36  func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) {
    37  
    38  	var script = `
    39  param([string]$vmName, [int]$addressIndex)
    40  try {
    41    $adapter = Hyper-V\Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
    42    if ($adapter.IPAddresses) {
    43      $ip = $adapter.IPAddresses[$addressIndex]
    44    } else {
    45      $vm = Get-CimInstance -ClassName Msvm_ComputerSystem -Namespace root\virtualization\v2 -Filter "ElementName='$vmName'"
    46      $ip_details = (Get-CimAssociatedInstance -InputObject $vm -ResultClassName Msvm_KvpExchangeComponent).GuestIntrinsicExchangeItems | %{ [xml]$_ } | ?{ $_.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text()='NetworkAddressIPv4']") }
    47  
    48      if ($null -eq $ip_details) {
    49        return $false
    50      }
    51  
    52      $ip_addresses = $ip_details.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
    53      $ip = ($ip_addresses -split ";")[0]
    54    }
    55  } catch {
    56    return $false
    57  }
    58  $ip
    59  `
    60  
    61  	var ps powershell.PowerShellCmd
    62  	cmdOut, err := ps.Output(script, vmName, "0")
    63  
    64  	return cmdOut, err
    65  }
    66  
    67  func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) {
    68  	var ps powershell.PowerShellCmd
    69  	var script string
    70  
    71  	script = `
    72  param([string]$vmName, [string]$isoPath)
    73  $dvdController = Hyper-V\Add-VMDvdDrive -VMName $vmName -path $isoPath -Passthru
    74  $dvdController | Hyper-V\Set-VMDvdDrive -path $null
    75  $result = "$($dvdController.ControllerNumber),$($dvdController.ControllerLocation)"
    76  $result
    77  `
    78  
    79  	cmdOut, err := ps.Output(script, vmName, isoPath)
    80  	if err != nil {
    81  		return 0, 0, err
    82  	}
    83  
    84  	cmdOutArray := strings.Split(cmdOut, ",")
    85  	if len(cmdOutArray) != 2 {
    86  		return 0, 0, errors.New("Did not return controller number and controller location")
    87  	}
    88  
    89  	controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[0]), 10, 64)
    90  	if err != nil {
    91  		return 0, 0, err
    92  	}
    93  	controllerNumber := uint(controllerNumberTemp)
    94  
    95  	controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[1]), 10, 64)
    96  	if err != nil {
    97  		return controllerNumber, 0, err
    98  	}
    99  	controllerLocation := uint(controllerLocationTemp)
   100  
   101  	return controllerNumber, controllerLocation, err
   102  }
   103  
   104  func MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error {
   105  
   106  	var script = `
   107  param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)
   108  $vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
   109  if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
   110  Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $path
   111  `
   112  
   113  	var ps powershell.PowerShellCmd
   114  	err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10),
   115  		strconv.FormatInt(int64(controllerLocation), 10))
   116  	return err
   117  }
   118  
   119  func UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
   120  	var script = `
   121  param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
   122  $vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
   123  if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
   124  Hyper-V\Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $null
   125  `
   126  
   127  	var ps powershell.PowerShellCmd
   128  	err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
   129  		strconv.FormatInt(int64(controllerLocation), 10))
   130  	return err
   131  }
   132  
   133  func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error {
   134  
   135  	if generation < 2 {
   136  		script := `
   137  param([string]$vmName)
   138  Hyper-V\Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","Floppy")
   139  `
   140  		var ps powershell.PowerShellCmd
   141  		err := ps.Run(script, vmName)
   142  		return err
   143  	} else {
   144  		script := `
   145  param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
   146  $vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
   147  if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
   148  Hyper-V\Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue
   149  `
   150  		var ps powershell.PowerShellCmd
   151  		err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
   152  			strconv.FormatInt(int64(controllerLocation), 10))
   153  		return err
   154  	}
   155  }
   156  
   157  func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error {
   158  	var script = `
   159  param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)
   160  $vmDvdDrive = Hyper-V\Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
   161  if (!$vmDvdDrive) {throw 'unable to find dvd drive'}
   162  Hyper-V\Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation
   163  `
   164  
   165  	var ps powershell.PowerShellCmd
   166  	err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10),
   167  		strconv.FormatInt(int64(controllerLocation), 10))
   168  	return err
   169  }
   170  
   171  func DeleteAllDvdDrives(vmName string) error {
   172  	var script = `
   173  param([string]$vmName)
   174  Hyper-V\Get-VMDvdDrive -VMName $vmName | Hyper-V\Remove-VMDvdDrive
   175  `
   176  
   177  	var ps powershell.PowerShellCmd
   178  	err := ps.Run(script, vmName)
   179  	return err
   180  }
   181  
   182  func MountFloppyDrive(vmName string, path string) error {
   183  	var script = `
   184  param([string]$vmName, [string]$path)
   185  Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $path
   186  `
   187  
   188  	var ps powershell.PowerShellCmd
   189  	err := ps.Run(script, vmName, path)
   190  	return err
   191  }
   192  
   193  func UnmountFloppyDrive(vmName string) error {
   194  
   195  	var script = `
   196  param([string]$vmName)
   197  Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $null
   198  `
   199  
   200  	var ps powershell.PowerShellCmd
   201  	err := ps.Run(script, vmName)
   202  	return err
   203  }
   204  
   205  func CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64,
   206  	diskSize int64, diskBlockSize int64, switchName string, generation uint,
   207  	diffDisks bool, fixedVHD bool) error {
   208  
   209  	if generation == 2 {
   210  		var script = `
   211  param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks)
   212  $vhdx = $vmName + '.vhdx'
   213  $vhdPath = Join-Path -Path $path -ChildPath $vhdx
   214  if ($harddrivePath){
   215  	if($diffDisks -eq "true"){
   216  		New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
   217  	} else {
   218  		Copy-Item -Path $harddrivePath -Destination $vhdPath
   219  	}
   220  	Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation
   221  } else {
   222  	Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes
   223  	Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation
   224  }
   225  `
   226  		var ps powershell.PowerShellCmd
   227  		if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10),
   228  			strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10),
   229  			switchName, strconv.FormatInt(int64(generation), 10),
   230  			strconv.FormatBool(diffDisks)); err != nil {
   231  			return err
   232  		}
   233  
   234  		return DisableAutomaticCheckpoints(vmName)
   235  	} else {
   236  		var script = `
   237  param([string]$vmName, [string]$path, [string]$harddrivePath, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [string]$diffDisks, [string]$fixedVHD)
   238  if($fixedVHD -eq "true"){
   239  	$vhdx = $vmName + '.vhd'
   240  }
   241  else{
   242  	$vhdx = $vmName + '.vhdx'
   243  }
   244  $vhdPath = Join-Path -Path $path -ChildPath $vhdx
   245  if ($harddrivePath){
   246  	if($diffDisks -eq "true"){
   247  		New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes
   248  	}
   249  	else{
   250  		Copy-Item -Path $harddrivePath -Destination $vhdPath
   251  	}
   252  	Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName
   253  } else {
   254  	if($fixedVHD -eq "true"){
   255  		Hyper-V\New-VHD -Path $vhdPath -Fixed -SizeBytes $newVHDSizeBytes
   256  	}
   257  	else {
   258  		Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes
   259  	}
   260  	Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName
   261  }
   262  `
   263  		var ps powershell.PowerShellCmd
   264  		if err := ps.Run(script, vmName, path, harddrivePath, strconv.FormatInt(ram, 10),
   265  			strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10),
   266  			switchName, strconv.FormatBool(diffDisks), strconv.FormatBool(fixedVHD)); err != nil {
   267  			return err
   268  		}
   269  
   270  		if err := DisableAutomaticCheckpoints(vmName); err != nil {
   271  			return err
   272  		}
   273  
   274  		return DeleteAllDvdDrives(vmName)
   275  	}
   276  }
   277  
   278  func DisableAutomaticCheckpoints(vmName string) error {
   279  	var script = `
   280  param([string]$vmName)
   281  if ((Get-Command Hyper-V\Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
   282  	Hyper-V\Set-Vm -Name $vmName -AutomaticCheckpointsEnabled $false }
   283  `
   284  	var ps powershell.PowerShellCmd
   285  	err := ps.Run(script, vmName)
   286  	return err
   287  }
   288  
   289  func ExportVmcxVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
   290  	var script = `
   291  param([string]$exportPath, [string]$vmName, [string]$snapshotName, [string]$allSnapshotsString)
   292  
   293  $WorkingPath = Join-Path $exportPath $vmName
   294  
   295  if (Test-Path $WorkingPath) {
   296  	throw "Export path working directory: $WorkingPath already exists!"
   297  }
   298  
   299  $allSnapshots = [System.Boolean]::Parse($allSnapshotsString)
   300  
   301  if ($snapshotName) {
   302      $snapshot = Hyper-V\Get-VMSnapshot -VMName $vmName -Name $snapshotName
   303      Hyper-V\Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
   304  } else {
   305      if (!$allSnapshots) {
   306          #Use last snapshot if one was not specified
   307          $snapshot = Hyper-V\Get-VMSnapshot -VMName $vmName | Select -Last 1
   308      } else {
   309          $snapshot = $null
   310      }
   311  
   312      if (!$snapshot) {
   313          #No snapshot clone
   314          Hyper-V\Export-VM -Name $vmName -Path $exportPath -ErrorAction Stop
   315      } else {
   316          #Snapshot clone
   317          Hyper-V\Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
   318      }
   319  }
   320  
   321  $result = Get-ChildItem -Path $WorkingPath | Move-Item -Destination $exportPath -Force
   322  $result = Remove-Item -Path $WorkingPath
   323  	`
   324  
   325  	allSnapshotsString := "False"
   326  	if allSnapshots {
   327  		allSnapshotsString = "True"
   328  	}
   329  
   330  	var ps powershell.PowerShellCmd
   331  	err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString)
   332  
   333  	return err
   334  }
   335  
   336  func CopyVmcxVirtualMachine(exportPath string, cloneFromVmcxPath string) error {
   337  	var script = `
   338  param([string]$exportPath, [string]$cloneFromVmcxPath)
   339  if (!(Test-Path $cloneFromVmcxPath)){
   340  	throw "Clone from vmcx directory: $cloneFromVmcxPath does not exist!"
   341  }
   342  
   343  if (!(Test-Path $exportPath)){
   344  	New-Item -ItemType Directory -Force -Path $exportPath
   345  }
   346  $cloneFromVmcxPath = Join-Path $cloneFromVmcxPath '\*'
   347  Copy-Item $cloneFromVmcxPath $exportPath -Recurse -Force
   348  	`
   349  
   350  	var ps powershell.PowerShellCmd
   351  	err := ps.Run(script, exportPath, cloneFromVmcxPath)
   352  
   353  	return err
   354  }
   355  
   356  func SetVmNetworkAdapterMacAddress(vmName string, mac string) error {
   357  	var script = `
   358  param([string]$vmName, [string]$mac)
   359  Hyper-V\Set-VMNetworkAdapter $vmName -staticmacaddress $mac
   360  	`
   361  
   362  	var ps powershell.PowerShellCmd
   363  	err := ps.Run(script, vmName, mac)
   364  
   365  	return err
   366  }
   367  
   368  func ImportVmcxVirtualMachine(importPath string, vmName string, harddrivePath string,
   369  	ram int64, switchName string) error {
   370  
   371  	var script = `
   372  param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName)
   373  
   374  $VirtualHarddisksPath = Join-Path -Path $importPath -ChildPath 'Virtual Hard Disks'
   375  if (!(Test-Path $VirtualHarddisksPath)) {
   376  	New-Item -ItemType Directory -Force -Path $VirtualHarddisksPath
   377  }
   378  
   379  $vhdPath = ""
   380  if ($harddrivePath){
   381  	$vhdx = $vmName + '.vhdx'
   382  	$vhdPath = Join-Path -Path $VirtualHarddisksPath -ChildPath $vhdx
   383  }
   384  
   385  $VirtualMachinesPath = Join-Path $importPath 'Virtual Machines'
   386  if (!(Test-Path $VirtualMachinesPath)) {
   387  	New-Item -ItemType Directory -Force -Path $VirtualMachinesPath
   388  }
   389  
   390  $VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.vmcx -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
   391  if (!$VirtualMachinePath){
   392      $VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
   393  }
   394  if (!$VirtualMachinePath){
   395      $VirtualMachinePath = Get-ChildItem -Path $importPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
   396  }
   397  
   398  $compatibilityReport = Hyper-V\Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $importPath -SmartPagingFilePath $importPath -SnapshotFilePath $importPath -VhdDestinationPath $VirtualHarddisksPath -GenerateNewId -Copy:$false
   399  if ($vhdPath){
   400  	Copy-Item -Path $harddrivePath -Destination $vhdPath
   401  	$existingFirstHarddrive = $compatibilityReport.VM.HardDrives | Select -First 1
   402  	if ($existingFirstHarddrive) {
   403  		$existingFirstHarddrive | Hyper-V\Set-VMHardDiskDrive -Path $vhdPath
   404  	} else {
   405  		Hyper-V\Add-VMHardDiskDrive -VM $compatibilityReport.VM -Path $vhdPath
   406  	}
   407  }
   408  Hyper-V\Set-VMMemory -VM $compatibilityReport.VM -StartupBytes $memoryStartupBytes
   409  $networkAdaptor = $compatibilityReport.VM.NetworkAdapters | Select -First 1
   410  Hyper-V\Disconnect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor
   411  Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor -SwitchName $switchName
   412  $vm = Hyper-V\Import-VM -CompatibilityReport $compatibilityReport
   413  
   414  if ($vm) {
   415      $result = Hyper-V\Rename-VM -VM $vm -NewName $VMName
   416  }
   417  	`
   418  
   419  	var ps powershell.PowerShellCmd
   420  	err := ps.Run(script, importPath, vmName, harddrivePath, strconv.FormatInt(ram, 10), switchName)
   421  
   422  	return err
   423  }
   424  
   425  func CloneVirtualMachine(cloneFromVmcxPath string, cloneFromVmName string,
   426  	cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string,
   427  	path string, harddrivePath string, ram int64, switchName string) error {
   428  
   429  	if cloneFromVmName != "" {
   430  		if err := ExportVmcxVirtualMachine(path, cloneFromVmName,
   431  			cloneFromSnapshotName, cloneAllSnapshots); err != nil {
   432  			return err
   433  		}
   434  	}
   435  
   436  	if cloneFromVmcxPath != "" {
   437  		if err := CopyVmcxVirtualMachine(path, cloneFromVmcxPath); err != nil {
   438  			return err
   439  		}
   440  	}
   441  
   442  	if err := ImportVmcxVirtualMachine(path, vmName, harddrivePath, ram, switchName); err != nil {
   443  		return err
   444  	}
   445  
   446  	return DeleteAllDvdDrives(vmName)
   447  }
   448  
   449  func GetVirtualMachineGeneration(vmName string) (uint, error) {
   450  	var script = `
   451  param([string]$vmName)
   452  $generation = Hyper-V\Get-Vm -Name $vmName | %{$_.Generation}
   453  if (!$generation){
   454      $generation = 1
   455  }
   456  return $generation
   457  `
   458  	var ps powershell.PowerShellCmd
   459  	cmdOut, err := ps.Output(script, vmName)
   460  
   461  	if err != nil {
   462  		return 0, err
   463  	}
   464  
   465  	generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32)
   466  
   467  	if err != nil {
   468  		return 0, err
   469  	}
   470  
   471  	generation := uint(generationUint32)
   472  
   473  	return generation, err
   474  }
   475  
   476  func SetVirtualMachineCpuCount(vmName string, cpu uint) error {
   477  
   478  	var script = `
   479  param([string]$vmName, [int]$cpu)
   480  Hyper-V\Set-VMProcessor -VMName $vmName -Count $cpu
   481  `
   482  	var ps powershell.PowerShellCmd
   483  	err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10))
   484  	return err
   485  }
   486  
   487  func SetVirtualMachineVirtualizationExtensions(vmName string, enableVirtualizationExtensions bool) error {
   488  
   489  	var script = `
   490  param([string]$vmName, [string]$exposeVirtualizationExtensionsString)
   491  $exposeVirtualizationExtensions = [System.Boolean]::Parse($exposeVirtualizationExtensionsString)
   492  Hyper-V\Set-VMProcessor -VMName $vmName -ExposeVirtualizationExtensions $exposeVirtualizationExtensions
   493  `
   494  	exposeVirtualizationExtensionsString := "False"
   495  	if enableVirtualizationExtensions {
   496  		exposeVirtualizationExtensionsString = "True"
   497  	}
   498  	var ps powershell.PowerShellCmd
   499  	err := ps.Run(script, vmName, exposeVirtualizationExtensionsString)
   500  	return err
   501  }
   502  
   503  func SetVirtualMachineDynamicMemory(vmName string, enableDynamicMemory bool) error {
   504  
   505  	var script = `
   506  param([string]$vmName, [string]$enableDynamicMemoryString)
   507  $enableDynamicMemory = [System.Boolean]::Parse($enableDynamicMemoryString)
   508  Hyper-V\Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $enableDynamicMemory
   509  `
   510  	enableDynamicMemoryString := "False"
   511  	if enableDynamicMemory {
   512  		enableDynamicMemoryString = "True"
   513  	}
   514  	var ps powershell.PowerShellCmd
   515  	err := ps.Run(script, vmName, enableDynamicMemoryString)
   516  	return err
   517  }
   518  
   519  func SetVirtualMachineMacSpoofing(vmName string, enableMacSpoofing bool) error {
   520  	var script = `
   521  param([string]$vmName, $enableMacSpoofing)
   522  Hyper-V\Set-VMNetworkAdapter -VMName $vmName -MacAddressSpoofing $enableMacSpoofing
   523  `
   524  
   525  	var ps powershell.PowerShellCmd
   526  
   527  	enableMacSpoofingString := "Off"
   528  	if enableMacSpoofing {
   529  		enableMacSpoofingString = "On"
   530  	}
   531  
   532  	err := ps.Run(script, vmName, enableMacSpoofingString)
   533  	return err
   534  }
   535  
   536  func SetVirtualMachineSecureBoot(vmName string, enableSecureBoot bool, templateName string) error {
   537  	var script = `
   538  param([string]$vmName, [string]$enableSecureBootString, [string]$templateName)
   539  $cmdlet = Get-Command Hyper-V\Set-VMFirmware
   540  # The SecureBootTemplate parameter is only available in later versions
   541  if ($cmdlet.Parameters.SecureBootTemplate) {
   542  	Hyper-V\Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBootString -SecureBootTemplate $templateName
   543  } else {
   544  	Hyper-V\Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBootString
   545  }
   546  `
   547  
   548  	var ps powershell.PowerShellCmd
   549  
   550  	enableSecureBootString := "Off"
   551  	if enableSecureBoot {
   552  		enableSecureBootString = "On"
   553  	}
   554  
   555  	if templateName == "" {
   556  		templateName = "MicrosoftWindows"
   557  	}
   558  
   559  	err := ps.Run(script, vmName, enableSecureBootString, templateName)
   560  	return err
   561  }
   562  
   563  func DeleteVirtualMachine(vmName string) error {
   564  
   565  	var script = `
   566  param([string]$vmName)
   567  
   568  $vm = Hyper-V\Get-VM -Name $vmName
   569  if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) {
   570      Hyper-V\Stop-VM -VM $vm -TurnOff -Force -Confirm:$false
   571  }
   572  
   573  Hyper-V\Remove-VM -Name $vmName -Force -Confirm:$false
   574  `
   575  
   576  	var ps powershell.PowerShellCmd
   577  	err := ps.Run(script, vmName)
   578  	return err
   579  }
   580  
   581  func ExportVirtualMachine(vmName string, path string) error {
   582  
   583  	var script = `
   584  param([string]$vmName, [string]$path)
   585  Hyper-V\Export-VM -Name $vmName -Path $path
   586  
   587  if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.VMCX')))
   588  {
   589    $vm = Hyper-V\Get-VM -Name $vmName
   590    $vm_adapter = Hyper-V\Get-VMNetworkAdapter -VM $vm | Select -First 1
   591  
   592    $config = [xml]@"
   593  <?xml version="1.0" ?>
   594  <configuration>
   595    <properties>
   596      <subtype type="integer">$($vm.Generation - 1)</subtype>
   597      <name type="string">$($vm.Name)</name>
   598    </properties>
   599    <settings>
   600      <processors>
   601        <count type="integer">$($vm.ProcessorCount)</count>
   602      </processors>
   603      <memory>
   604        <bank>
   605          <dynamic_memory_enabled type="bool">$($vm.DynamicMemoryEnabled)</dynamic_memory_enabled>
   606          <limit type="integer">$($vm.MemoryMaximum / 1MB)</limit>
   607          <reservation type="integer">$($vm.MemoryMinimum / 1MB)</reservation>
   608          <size type="integer">$($vm.MemoryStartup / 1MB)</size>
   609        </bank>
   610      </memory>
   611    </settings>
   612    <AltSwitchName type="string">$($vm_adapter.SwitchName)</AltSwitchName>
   613    <boot>
   614      <device0 type="string">Optical</device0>
   615    </boot>
   616    <secure_boot_enabled type="bool">False</secure_boot_enabled>
   617    <notes type="string">$($vm.Notes)</notes>
   618    <vm-controllers/>
   619  </configuration>
   620  "@
   621  
   622    if ($vm.Generation -eq 1)
   623    {
   624      $vm_controllers  = Hyper-V\Get-VMIdeController -VM $vm
   625      $controller_type = $config.SelectSingleNode('/configuration/vm-controllers')
   626      # IDE controllers are not stored in a special XML container
   627    }
   628    else
   629    {
   630      $vm_controllers  = Hyper-V\Get-VMScsiController -VM $vm
   631      $controller_type = $config.CreateElement('scsi')
   632      $controller_type.SetAttribute('ChannelInstanceGuid', 'x')
   633      # SCSI controllers are stored in the scsi XML container
   634      if ((Hyper-V\Get-VMFirmware -VM $vm).SecureBoot -eq [Microsoft.HyperV.PowerShell.OnOffState]::On)
   635      {
   636  	  $config.configuration.secure_boot_enabled.'#text' = 'True'
   637  	}
   638      else
   639      {
   640        $config.configuration.secure_boot_enabled.'#text' = 'False'
   641  	}
   642    }
   643  
   644    $vm_controllers | ForEach {
   645      $controller = $config.CreateElement('controller' + $_.ControllerNumber)
   646      $_.Drives | ForEach {
   647        $drive = $config.CreateElement('drive' + ($_.DiskNumber + 0))
   648        $drive_path = $config.CreateElement('pathname')
   649        $drive_path.SetAttribute('type', 'string')
   650        $drive_path.AppendChild($config.CreateTextNode($_.Path))
   651        $drive_type = $config.CreateElement('type')
   652        $drive_type.SetAttribute('type', 'string')
   653        if ($_ -is [Microsoft.HyperV.PowerShell.HardDiskDrive])
   654        {
   655          $drive_type.AppendChild($config.CreateTextNode('VHD'))
   656        }
   657        elseif ($_ -is [Microsoft.HyperV.PowerShell.DvdDrive])
   658        {
   659          $drive_type.AppendChild($config.CreateTextNode('ISO'))
   660        }
   661        else
   662        {
   663          $drive_type.AppendChild($config.CreateTextNode('NONE'))
   664        }
   665        $drive.AppendChild($drive_path)
   666        $drive.AppendChild($drive_type)
   667        $controller.AppendChild($drive)
   668      }
   669      $controller_type.AppendChild($controller)
   670    }
   671    if ($controller_type.Name -ne 'vm-controllers')
   672    {
   673      $config.SelectSingleNode('/configuration/vm-controllers').AppendChild($controller_type)
   674    }
   675  
   676    $config.Save([IO.Path]::Combine($path, $vm.Name, 'Virtual Machines', 'box.xml'))
   677  }
   678  `
   679  
   680  	var ps powershell.PowerShellCmd
   681  	err := ps.Run(script, vmName, path)
   682  	return err
   683  }
   684  
   685  func PreserveLegacyExportBehaviour(srcPath, dstPath string) error {
   686  
   687  	var script = `
   688  param([string]$srcPath, [string]$dstPath)
   689  
   690  # Validate the paths returning an error if they are empty or don't exist
   691  $srcPath, $dstPath | % {
   692      if ($_) {
   693          if (! (Test-Path $_)) {
   694              [System.Console]::Error.WriteLine("Path $_ does not exist")
   695              exit
   696          }
   697      } else {
   698          [System.Console]::Error.WriteLine("A supplied path is empty")
   699          exit
   700      }
   701  }
   702  
   703  # Export-VM should just create directories at the root of the export path
   704  # but, just in case, move all files as well...
   705  Move-Item -Path (Join-Path (Get-Item $srcPath).FullName "*.*") -Destination (Get-Item $dstPath).FullName
   706  
   707  # Move directories with content; Delete empty directories
   708  $dirObj = Get-ChildItem $srcPath -Directory | % {
   709      New-Object PSObject -Property @{
   710          FullName=$_.FullName;
   711          HasContent=$(if ($_.GetFileSystemInfos().Count -gt 0) {$true} else {$false})
   712      }
   713  }
   714  foreach ($directory in $dirObj) {
   715      if ($directory.HasContent) {
   716          Move-Item -Path $directory.FullName -Destination (Get-Item $dstPath).FullName
   717      } else {
   718          Remove-Item -Path $directory.FullName
   719      }
   720  }
   721  
   722  # Only remove the source directory if it is now empty
   723  if ( $((Get-Item $srcPath).GetFileSystemInfos().Count) -eq 0 ) {
   724      Remove-Item -Path $srcPath
   725  } else {
   726      # 'Return' an error message to PowerShellCmd as the directory should
   727      # always be empty at the end of the script. The check is here to stop
   728      # the Remove-Item command from doing any damage if some unforeseen
   729      # error has occured
   730      [System.Console]::Error.WriteLine("Refusing to remove $srcPath as it is not empty")
   731      exit
   732  }
   733  `
   734  
   735  	var ps powershell.PowerShellCmd
   736  	err := ps.Run(script, srcPath, dstPath)
   737  
   738  	return err
   739  }
   740  
   741  func MoveCreatedVHDsToOutputDir(srcPath, dstPath string) error {
   742  
   743  	var script = `
   744  param([string]$srcPath, [string]$dstPath)
   745  
   746  # Validate the paths returning an error if the supplied path is empty
   747  # or if the paths don't exist
   748  $srcPath, $dstPath | % {
   749      if ($_) {
   750          if (! (Test-Path $_)) {
   751              [System.Console]::Error.WriteLine("Path $_ does not exist")
   752              exit
   753          }
   754      } else {
   755          [System.Console]::Error.WriteLine("A supplied path is empty")
   756          exit
   757      }
   758  }
   759  
   760  # Convert to absolute paths if required
   761  $srcPathAbs = (Get-Item($srcPath)).FullName
   762  $dstPathAbs = (Get-Item($dstPath)).FullName
   763  
   764  # Get the full path to all disks under the directory or exit if none are found
   765  $disks = Get-ChildItem -Path $srcPathAbs -Recurse -Filter *.vhd* -ErrorAction SilentlyContinue | % { $_.FullName }
   766  if ($disks.Length -eq 0) {
   767      [System.Console]::Error.WriteLine("No disks found under $srcPathAbs")
   768      exit
   769  }
   770  
   771  # Set up directory for VHDs in the destination directory
   772  $vhdDstDir = Join-Path -Path $dstPathAbs -ChildPath 'Virtual Hard Disks'
   773  if (! (Test-Path $vhdDstDir)) {
   774  	New-Item -ItemType Directory -Force -Path $vhdDstDir
   775  }
   776  
   777  # Move the disks
   778  foreach ($disk in $disks) {
   779  	Move-Item -Path $disk -Destination $vhdDstDir
   780  }
   781  `
   782  
   783  	var ps powershell.PowerShellCmd
   784  	err := ps.Run(script, srcPath, dstPath)
   785  
   786  	return err
   787  }
   788  
   789  func CompactDisks(path string) (result string, err error) {
   790  	var script = `
   791  param([string]$srcPath)
   792  
   793  $disks = Get-ChildItem -Path $srcPath -Recurse -Filter *.vhd* -ErrorAction SilentlyContinue | % { $_.FullName }
   794  # Failure to find any disks is treated as a 'soft' error. Simply print out
   795  # a warning and exit
   796  if ($disks.Length -eq 0) {
   797      Write-Output "WARNING: No disks found under $srcPath"
   798      exit
   799  }
   800  
   801  foreach ($disk in $disks) {
   802      Write-Output "Compacting disk: $(Split-Path $disk -leaf)"
   803  
   804      $sizeBefore = $disk.Length
   805      Optimize-VHD -Path $disk -Mode Full
   806      $sizeAfter = $disk.Length
   807  
   808      # Calculate the percentage change in disk size
   809      if ($sizeAfter -gt 0) { # Protect against division by zero
   810          $percentChange = ( ( $sizeAfter / $sizeBefore ) * 100 ) - 100
   811          switch($percentChange) {
   812              {$_ -lt 0} {Write-Output "Disk size reduced by: $(([math]::Abs($_)).ToString("#.#"))%"}
   813              {$_ -eq 0} {Write-Output "Disk size is unchanged"}
   814              {$_ -gt 0} {Write-Output "WARNING: Disk size increased by: $($_.ToString("#.#"))%"}
   815          }
   816      }
   817  }
   818  `
   819  
   820  	var ps powershell.PowerShellCmd
   821  	result, err = ps.Output(script, path)
   822  	return
   823  }
   824  
   825  func CreateVirtualSwitch(switchName string, switchType string) (bool, error) {
   826  
   827  	var script = `
   828  param([string]$switchName,[string]$switchType)
   829  $switches = Hyper-V\Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
   830  if ($switches.Count -eq 0) {
   831    Hyper-V\New-VMSwitch -Name $switchName -SwitchType $switchType
   832    return $true
   833  }
   834  return $false
   835  `
   836  
   837  	var ps powershell.PowerShellCmd
   838  	cmdOut, err := ps.Output(script, switchName, switchType)
   839  	var created = strings.TrimSpace(cmdOut) == "True"
   840  	return created, err
   841  }
   842  
   843  func DeleteVirtualSwitch(switchName string) error {
   844  
   845  	var script = `
   846  param([string]$switchName)
   847  $switch = Hyper-V\Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
   848  if ($switch -ne $null) {
   849      $switch | Hyper-V\Remove-VMSwitch -Force -Confirm:$false
   850  }
   851  `
   852  
   853  	var ps powershell.PowerShellCmd
   854  	err := ps.Run(script, switchName)
   855  	return err
   856  }
   857  
   858  func StartVirtualMachine(vmName string) error {
   859  
   860  	var script = `
   861  param([string]$vmName)
   862  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
   863  if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
   864    Hyper-V\Start-VM -Name $vmName -Confirm:$false
   865  }
   866  `
   867  
   868  	var ps powershell.PowerShellCmd
   869  	err := ps.Run(script, vmName)
   870  	return err
   871  }
   872  
   873  func RestartVirtualMachine(vmName string) error {
   874  
   875  	var script = `
   876  param([string]$vmName)
   877  Hyper-V\Restart-VM $vmName -Force -Confirm:$false
   878  `
   879  
   880  	var ps powershell.PowerShellCmd
   881  	err := ps.Run(script, vmName)
   882  	return err
   883  }
   884  
   885  func StopVirtualMachine(vmName string) error {
   886  
   887  	var script = `
   888  param([string]$vmName)
   889  $vm = Hyper-V\Get-VM -Name $vmName
   890  if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
   891      Hyper-V\Stop-VM -VM $vm -Force -Confirm:$false
   892  }
   893  `
   894  
   895  	var ps powershell.PowerShellCmd
   896  	err := ps.Run(script, vmName)
   897  	return err
   898  }
   899  
   900  func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error {
   901  
   902  	integrationServiceId := ""
   903  	switch integrationServiceName {
   904  	case "Time Synchronization":
   905  		integrationServiceId = "2497F4DE-E9FA-4204-80E4-4B75C46419C0"
   906  	case "Heartbeat":
   907  		integrationServiceId = "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47"
   908  	case "Key-Value Pair Exchange":
   909  		integrationServiceId = "2A34B1C2-FD73-4043-8A5B-DD2159BC743F"
   910  	case "Shutdown":
   911  		integrationServiceId = "9F8233AC-BE49-4C79-8EE3-E7E1985B2077"
   912  	case "VSS":
   913  		integrationServiceId = "5CED1297-4598-4915-A5FC-AD21BB4D02A4"
   914  	case "Guest Service Interface":
   915  		integrationServiceId = "6C09BB55-D683-4DA0-8931-C9BF705F6480"
   916  	default:
   917  		panic("unrecognized Integration Service Name")
   918  	}
   919  
   920  	var script = `
   921  param([string]$vmName,[string]$integrationServiceId)
   922  Hyper-V\Get-VMIntegrationService -VmName $vmName | ?{$_.Id -match $integrationServiceId} | Hyper-V\Enable-VMIntegrationService
   923  `
   924  
   925  	var ps powershell.PowerShellCmd
   926  	err := ps.Run(script, vmName, integrationServiceId)
   927  	return err
   928  }
   929  
   930  func SetNetworkAdapterVlanId(switchName string, vlanId string) error {
   931  
   932  	var script = `
   933  param([string]$networkAdapterName,[string]$vlanId)
   934  Hyper-V\Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $networkAdapterName -Access -VlanId $vlanId
   935  `
   936  
   937  	var ps powershell.PowerShellCmd
   938  	err := ps.Run(script, switchName, vlanId)
   939  	return err
   940  }
   941  
   942  func SetVirtualMachineVlanId(vmName string, vlanId string) error {
   943  
   944  	var script = `
   945  param([string]$vmName,[string]$vlanId)
   946  Hyper-V\Set-VMNetworkAdapterVlan -VMName $vmName -Access -VlanId $vlanId
   947  `
   948  	var ps powershell.PowerShellCmd
   949  	err := ps.Run(script, vmName, vlanId)
   950  	return err
   951  }
   952  
   953  func GetExternalOnlineVirtualSwitch() (string, error) {
   954  
   955  	var script = `
   956  $adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | Sort-Object -Descending -Property Speed
   957  foreach ($adapter in $adapters) {
   958    $switch = Hyper-V\Get-VMSwitch -SwitchType External | Where-Object { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
   959  
   960    if ($switch -ne $null) {
   961      $switch.Name
   962      break
   963    }
   964  }
   965  `
   966  
   967  	var ps powershell.PowerShellCmd
   968  	cmdOut, err := ps.Output(script)
   969  	if err != nil {
   970  		return "", err
   971  	}
   972  
   973  	var switchName = strings.TrimSpace(cmdOut)
   974  	return switchName, nil
   975  }
   976  
   977  func CreateExternalVirtualSwitch(vmName string, switchName string) error {
   978  
   979  	var script = `
   980  param([string]$vmName,[string]$switchName)
   981  $switch = $null
   982  $names = @('ethernet','wi-fi','lan')
   983  $adapters = foreach ($name in $names) {
   984    Get-NetAdapter -Physical -Name $name -ErrorAction SilentlyContinue | where status -eq 'up'
   985  }
   986  
   987  foreach ($adapter in $adapters) {
   988    $switch = Hyper-V\Get-VMSwitch -SwitchType External | where { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
   989  
   990    if ($switch -eq $null) {
   991      $switch = Hyper-V\New-VMSwitch -Name $switchName -NetAdapterName $adapter.Name -AllowManagementOS $true -Notes 'Parent OS, VMs, WiFi'
   992    }
   993  
   994    if ($switch -ne $null) {
   995      break
   996    }
   997  }
   998  
   999  if($switch -ne $null) {
  1000    Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter -VMSwitch $switch
  1001  } else {
  1002    Write-Error 'No internet adapters found'
  1003  }
  1004  `
  1005  	var ps powershell.PowerShellCmd
  1006  	err := ps.Run(script, vmName, switchName)
  1007  	return err
  1008  }
  1009  
  1010  func GetVirtualMachineSwitchName(vmName string) (string, error) {
  1011  
  1012  	var script = `
  1013  param([string]$vmName)
  1014  (Hyper-V\Get-VMNetworkAdapter -VMName $vmName).SwitchName
  1015  `
  1016  
  1017  	var ps powershell.PowerShellCmd
  1018  	cmdOut, err := ps.Output(script, vmName)
  1019  	if err != nil {
  1020  		return "", err
  1021  	}
  1022  
  1023  	return strings.TrimSpace(cmdOut), nil
  1024  }
  1025  
  1026  func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error {
  1027  
  1028  	var script = `
  1029  param([string]$vmName,[string]$switchName)
  1030  Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter -SwitchName $switchName
  1031  `
  1032  
  1033  	var ps powershell.PowerShellCmd
  1034  	err := ps.Run(script, vmName, switchName)
  1035  	return err
  1036  }
  1037  
  1038  func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64,
  1039  	vhdBlockSize int64, controllerType string) error {
  1040  
  1041  	var script = `
  1042  param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes, [string]$vhdBlockSizeInByte, [string]$controllerType)
  1043  $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdName
  1044  Hyper-V\New-VHD -path $vhdPath -SizeBytes $vhdSizeInBytes -BlockSizeBytes $vhdBlockSizeInByte
  1045  Hyper-V\Add-VMHardDiskDrive -VMName $vmName -path $vhdPath -controllerType $controllerType
  1046  `
  1047  	var ps powershell.PowerShellCmd
  1048  	err := ps.Run(script, vmName, vhdRoot, vhdName, strconv.FormatInt(vhdSizeBytes, 10), strconv.FormatInt(vhdBlockSize, 10), controllerType)
  1049  	return err
  1050  }
  1051  
  1052  func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
  1053  
  1054  	var script = `
  1055  param([string]$vmName,[string]$switchName)
  1056  Hyper-V\Set-VMNetworkAdapterVlan -VMName $vmName -Untagged
  1057  Hyper-V\Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $switchName -Untagged
  1058  `
  1059  
  1060  	var ps powershell.PowerShellCmd
  1061  	err := ps.Run(script, vmName, switchName)
  1062  	return err
  1063  }
  1064  
  1065  func IsRunning(vmName string) (bool, error) {
  1066  
  1067  	var script = `
  1068  param([string]$vmName)
  1069  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
  1070  $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running
  1071  `
  1072  
  1073  	var ps powershell.PowerShellCmd
  1074  	cmdOut, err := ps.Output(script, vmName)
  1075  
  1076  	if err != nil {
  1077  		return false, err
  1078  	}
  1079  
  1080  	var isRunning = strings.TrimSpace(cmdOut) == "True"
  1081  	return isRunning, err
  1082  }
  1083  
  1084  func IsOff(vmName string) (bool, error) {
  1085  
  1086  	var script = `
  1087  param([string]$vmName)
  1088  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
  1089  $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off
  1090  `
  1091  
  1092  	var ps powershell.PowerShellCmd
  1093  	cmdOut, err := ps.Output(script, vmName)
  1094  
  1095  	if err != nil {
  1096  		return false, err
  1097  	}
  1098  
  1099  	var isRunning = strings.TrimSpace(cmdOut) == "True"
  1100  	return isRunning, err
  1101  }
  1102  
  1103  func Uptime(vmName string) (uint64, error) {
  1104  
  1105  	var script = `
  1106  param([string]$vmName)
  1107  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
  1108  $vm.Uptime.TotalSeconds
  1109  `
  1110  	var ps powershell.PowerShellCmd
  1111  	cmdOut, err := ps.Output(script, vmName)
  1112  
  1113  	if err != nil {
  1114  		return 0, err
  1115  	}
  1116  
  1117  	uptime, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64)
  1118  
  1119  	return uptime, err
  1120  }
  1121  
  1122  func Mac(vmName string) (string, error) {
  1123  	var script = `
  1124  param([string]$vmName, [int]$adapterIndex)
  1125  try {
  1126    $adapter = Hyper-V\Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
  1127    $mac = $adapter[$adapterIndex].MacAddress
  1128    if($mac -eq $null) {
  1129      return ""
  1130    }
  1131  } catch {
  1132    return ""
  1133  }
  1134  $mac
  1135  `
  1136  
  1137  	var ps powershell.PowerShellCmd
  1138  	cmdOut, err := ps.Output(script, vmName, "0")
  1139  
  1140  	return cmdOut, err
  1141  }
  1142  
  1143  func IpAddress(mac string) (string, error) {
  1144  	var script = `
  1145  param([string]$mac, [int]$addressIndex)
  1146  try {
  1147    $vm = Hyper-V\Get-VM | ?{$_.NetworkAdapters.MacAddress -eq $mac}
  1148    if ($vm.NetworkAdapters.IpAddresses) {
  1149      $ipAddresses = $vm.NetworkAdapters.IPAddresses
  1150      if ($ipAddresses -isnot [array]) {
  1151        $ipAddresses = @($ipAddresses)
  1152      }
  1153      $ip = $ipAddresses[$addressIndex]
  1154    } else {
  1155      $vm_info = Get-CimInstance -ClassName Msvm_ComputerSystem -Namespace root\virtualization\v2 -Filter "ElementName='$($vm.Name)'"
  1156      $ip_details = (Get-CimAssociatedInstance -InputObject $vm_info -ResultClassName Msvm_KvpExchangeComponent).GuestIntrinsicExchangeItems | %{ [xml]$_ } | ?{ $_.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text()='NetworkAddressIPv4']") }
  1157  
  1158      if ($null -eq $ip_details) {
  1159        return ""
  1160      }
  1161  
  1162      $ip_addresses = $ip_details.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").Value
  1163      $ip = ($ip_addresses -split ";")[0]
  1164    }
  1165  } catch {
  1166    return ""
  1167  }
  1168  $ip
  1169  `
  1170  
  1171  	var ps powershell.PowerShellCmd
  1172  	cmdOut, err := ps.Output(script, mac, "0")
  1173  
  1174  	return cmdOut, err
  1175  }
  1176  
  1177  func TurnOff(vmName string) error {
  1178  
  1179  	var script = `
  1180  param([string]$vmName)
  1181  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
  1182  if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
  1183    Hyper-V\Stop-VM -Name $vmName -TurnOff -Force -Confirm:$false
  1184  }
  1185  `
  1186  
  1187  	var ps powershell.PowerShellCmd
  1188  	err := ps.Run(script, vmName)
  1189  	return err
  1190  }
  1191  
  1192  func ShutDown(vmName string) error {
  1193  
  1194  	var script = `
  1195  param([string]$vmName)
  1196  $vm = Hyper-V\Get-VM -Name $vmName -ErrorAction SilentlyContinue
  1197  if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
  1198    Hyper-V\Stop-VM -Name $vmName -Force -Confirm:$false
  1199  }
  1200  `
  1201  
  1202  	var ps powershell.PowerShellCmd
  1203  	err := ps.Run(script, vmName)
  1204  	return err
  1205  }
  1206  
  1207  func TypeScanCodes(vmName string, scanCodes string) error {
  1208  	if len(scanCodes) == 0 {
  1209  		return nil
  1210  	}
  1211  
  1212  	var script = `
  1213  param([string]$vmName, [string]$scanCodes)
  1214  	#Requires -Version 3
  1215  
  1216  	function Hyper-V\Get-VMConsole
  1217  	{
  1218  	    [CmdletBinding()]
  1219  	    param (
  1220  	        [Parameter(Mandatory)]
  1221  	        [string] $VMName
  1222  	    )
  1223  
  1224  	    $ErrorActionPreference = "Stop"
  1225  
  1226  	    $vm = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1
  1227  	    if ($vm -eq $null){
  1228  	        Write-Error ("VirtualMachine({0}) is not found!" -f $VMName)
  1229  	    }
  1230  
  1231  	    $vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false
  1232  
  1233  		if ($vmKeyboard -eq $null) {
  1234  			$vmKeyboard = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1
  1235  		}
  1236  
  1237  		if ($vmKeyboard -eq $null) {
  1238  			$vmKeyboard = Get-CimInstance -Namespace "root\virtualization" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1
  1239  		}
  1240  
  1241  	    if ($vmKeyboard -eq $null){
  1242  	        Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName)
  1243  	    }
  1244  
  1245  	    #TODO: It may be better using New-Module -AsCustomObject to return console object?
  1246  
  1247  	    #Console object to return
  1248  	    $console = [pscustomobject] @{
  1249  	        Msvm_ComputerSystem = $vm
  1250  	        Msvm_Keyboard = $vmKeyboard
  1251  	    }
  1252  
  1253  	    #Need to import assembly to use System.Windows.Input.Key
  1254  	    Add-Type -AssemblyName WindowsBase
  1255  
  1256  	    #region Add Console Members
  1257  	    $console | Add-Member -MemberType ScriptMethod -Name TypeText -Value {
  1258  	        [OutputType([bool])]
  1259  	        param (
  1260  	            [ValidateNotNullOrEmpty()]
  1261  	            [Parameter(Mandatory)]
  1262  	            [string] $AsciiText
  1263  	        )
  1264  	        $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeText" -Arguments @{ asciiText = $AsciiText }
  1265  	        return (0 -eq $result.ReturnValue)
  1266  	    }
  1267  
  1268  	    #Define method:TypeCtrlAltDel
  1269  	    $console | Add-Member -MemberType ScriptMethod -Name TypeCtrlAltDel -Value {
  1270  	        $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeCtrlAltDel"
  1271  	        return (0 -eq $result.ReturnValue)
  1272  	    }
  1273  
  1274  	    #Define method:TypeKey
  1275  	    $console | Add-Member -MemberType ScriptMethod -Name TypeKey -Value {
  1276  	        [OutputType([bool])]
  1277  	        param (
  1278  	            [Parameter(Mandatory)]
  1279  	            [Windows.Input.Key] $Key,
  1280  	            [Windows.Input.ModifierKeys] $ModifierKey = [Windows.Input.ModifierKeys]::None
  1281  	        )
  1282  
  1283  	        $keyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey($Key)
  1284  
  1285  	        switch ($ModifierKey)
  1286  	        {
  1287  	            ([Windows.Input.ModifierKeys]::Control){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftCtrl)}
  1288  	            ([Windows.Input.ModifierKeys]::Alt){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftAlt)}
  1289  	            ([Windows.Input.ModifierKeys]::Shift){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftShift)}
  1290  	            ([Windows.Input.ModifierKeys]::Windows){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LWin)}
  1291  	        }
  1292  
  1293  	        if ($ModifierKey -eq [Windows.Input.ModifierKeys]::None)
  1294  	        {
  1295  	            $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
  1296  	        }
  1297  	        else
  1298  	        {
  1299  	            $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "PressKey" -Arguments @{ keyCode = $modifierKeyCode }
  1300  	            $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
  1301  	            $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "ReleaseKey" -Arguments @{ keyCode = $modifierKeyCode }
  1302  	        }
  1303  	        $result = return (0 -eq $result.ReturnValue)
  1304  	    }
  1305  
  1306  	    #Define method:Scancodes
  1307  	    $console | Add-Member -MemberType ScriptMethod -Name TypeScancodes -Value {
  1308  	        [OutputType([bool])]
  1309  	        param (
  1310  	            [Parameter(Mandatory)]
  1311  	            [byte[]] $ScanCodes
  1312  	        )
  1313  	        $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeScancodes" -Arguments @{ ScanCodes = $ScanCodes }
  1314  	        return (0 -eq $result.ReturnValue)
  1315  	    }
  1316  
  1317  	    #Define method:ExecCommand
  1318  	    $console | Add-Member -MemberType ScriptMethod -Name ExecCommand -Value {
  1319  	        param (
  1320  	            [Parameter(Mandatory)]
  1321  	            [string] $Command
  1322  	        )
  1323  	        if ([String]::IsNullOrEmpty($Command)){
  1324  	            return
  1325  	        }
  1326  
  1327  	        $console.TypeText($Command) > $null
  1328  	        $console.TypeKey([Windows.Input.Key]::Enter) > $null
  1329  	        #sleep -Milliseconds 100
  1330  	    }
  1331  
  1332  	    #Define method:Dispose
  1333  	    $console | Add-Member -MemberType ScriptMethod -Name Dispose -Value {
  1334  	        $this.Msvm_ComputerSystem.Dispose()
  1335  	        $this.Msvm_Keyboard.Dispose()
  1336  	    }
  1337  
  1338  
  1339  	    #endregion
  1340  
  1341  	    return $console
  1342  	}
  1343  
  1344  	$vmConsole = Hyper-V\Get-VMConsole -VMName $vmName
  1345  	$scanCodesToSend = ''
  1346  	$scanCodes.Split(' ') | %{
  1347  		$scanCode = $_
  1348  
  1349  		if ($scanCode.StartsWith('wait')){
  1350  			$timeToWait = $scanCode.Substring(4)
  1351  			if (!$timeToWait){
  1352  				$timeToWait = "1"
  1353  			}
  1354  
  1355  			if ($scanCodesToSend){
  1356  				$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
  1357  
  1358                  $scanCodesToSendByteArray | %{
  1359  				    $vmConsole.TypeScancodes($_)
  1360                  }
  1361  			}
  1362  
  1363  			write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point."
  1364  			Start-Sleep -s $timeToWait
  1365  
  1366  			$scanCodesToSend = ''
  1367  		} else {
  1368  			if ($scanCodesToSend){
  1369  				write-host "Sending special code '$scanCodesToSend' '$scanCode'"
  1370  				$scanCodesToSend = "$scanCodesToSend $scanCode"
  1371  			} else {
  1372  				write-host "Sending char '$scanCode'"
  1373  				$scanCodesToSend = "$scanCode"
  1374  			}
  1375  		}
  1376  	}
  1377  	if ($scanCodesToSend){
  1378  		$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
  1379  
  1380          $scanCodesToSendByteArray | %{
  1381  			$vmConsole.TypeScancodes($_)
  1382          }
  1383  	}
  1384  `
  1385  
  1386  	var ps powershell.PowerShellCmd
  1387  	err := ps.Run(script, vmName, scanCodes)
  1388  	return err
  1389  }
  1390  
  1391  func ConnectVirtualMachine(vmName string) (context.CancelFunc, error) {
  1392  	ctx, cancel := context.WithCancel(context.Background())
  1393  	cmd := exec.CommandContext(ctx, "vmconnect.exe", "localhost", vmName)
  1394  	err := cmd.Start()
  1395  	if err != nil {
  1396  		// Failed to start so cancel function not required
  1397  		cancel = nil
  1398  	}
  1399  	return cancel, err
  1400  }
  1401  
  1402  func DisconnectVirtualMachine(cancel context.CancelFunc) {
  1403  	cancel()
  1404  }