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