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