Support for boot_command

Setup local http server
Add compaction of hard drive
GetHostAdapterIpAddress function added for hyperv
renamed step step_start_vm to step_run to fall in ine with naming conventions of other builders
This commit is contained in:
Taliesin Sisson 2015-06-27 22:36:39 +01:00
parent 03b0698edd
commit aa1f1da1ff
10 changed files with 685 additions and 97 deletions

View File

@ -31,4 +31,10 @@ type Driver interface {
// Finds the IP address of a VM connected that uses DHCP by its MAC address
IpAddress(string) (string, error)
// Finds the IP address of a host adapter connected to switch
GetHostAdapterIpAddressForSwitch(string) (string, error)
// Type scan codes to virtual keyboard of vm
TypeScanCodes(string, string) error
}

View File

@ -41,12 +41,12 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) {
// Start starts a VM specified by the name given.
func (d *HypervPS4Driver) Start(vmName string) error {
return hyperv.Start(vmName)
return hyperv.StartVirtualMachine(vmName)
}
// Stop stops a VM specified by the name given.
func (d *HypervPS4Driver) Stop(vmName string) error {
return hyperv.TurnOff(vmName)
return hyperv.StopVirtualMachine(vmName)
}
func (d *HypervPS4Driver) Verify() error {
@ -97,6 +97,26 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) {
return res, err
}
// Finds the IP address of a host adapter connected to switch
func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName)
if err != nil {
return res, err
}
if res == "" {
err := fmt.Errorf("%s", "No ip address.")
return res, err
}
return res, err
}
// Type scan codes to virtual keyboard of vm
func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error {
return hyperv.TypeScanCodes(vmName, scanCodes)
}
func (d *HypervPS4Driver) verifyPSVersion() error {
log.Printf("Enter method: %s", "verifyPSVersion")

View File

@ -1,16 +1,20 @@
package common
import (
"errors"
"fmt"
"time"
"github.com/mitchellh/packer/template/interpolate"
"time"
)
type RunConfig struct {
Headless bool `mapstructure:"headless"`
RawBootWait string `mapstructure:"boot_wait"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
BootWait time.Duration ``
}
@ -19,11 +23,29 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
c.RawBootWait = "10s"
}
var err error
c.BootWait, err = time.ParseDuration(c.RawBootWait)
if err != nil {
return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)}
if c.HTTPPortMin == 0 {
c.HTTPPortMin = 8000
}
return nil
if c.HTTPPortMax == 0 {
c.HTTPPortMax = 9000
}
var errs []error
var err error
if c.RawBootWait != "" {
c.BootWait, err = time.ParseDuration(c.RawBootWait)
if err != nil {
errs = append(
errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
}
}
if c.HTTPPortMin > c.HTTPPortMax {
errs = append(errs,
errors.New("http_port_min must be less than http_port_max"))
}
return errs
}

View File

@ -6,20 +6,21 @@ package common
import (
"fmt"
"path/filepath"
"io/ioutil"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/powershell/hyperv"
"io/ioutil"
"path/filepath"
)
const(
const (
vhdDir string = "Virtual Hard Disks"
vmDir string = "Virtual Machines"
)
type StepExportVm struct {
OutputDir string
SkipCompaction bool
}
func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
@ -34,7 +35,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
// create temp path to export vm
errorMsg = "Error creating temp export path: %s"
vmExportPath , err := ioutil.TempDir(tmpPath, "export")
vmExportPath, err := ioutil.TempDir(tmpPath, "export")
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
@ -54,7 +55,21 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
}
// copy to output dir
expPath := filepath.Join(vmExportPath,vmName)
expPath := filepath.Join(vmExportPath, vmName)
if s.SkipCompaction {
ui.Say("Skipping disk compaction...")
} else {
ui.Say("Compacting disks...")
err = hyperv.CompactDisks(expPath, vhdDir)
if err != nil {
errorMsg = "Error compacting disks: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Coping to output dir...")
err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)

View File

@ -0,0 +1,78 @@
package common
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
"net/http"
)
// This step creates and runs the HTTP server that is serving files from the
// directory specified by the 'http_directory` configuration parameter in the
// template.
//
// Uses:
// ui packer.Ui
//
// Produces:
// http_port int - The port the HTTP server started on.
type StepHTTPServer struct {
HTTPDir string
HTTPPortMin uint
HTTPPortMax uint
l net.Listener
}
func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0
if s.HTTPDir == "" {
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
// Find an available TCP port for our HTTP server
var httpAddr string
portRange := int(s.HTTPPortMax - s.HTTPPortMin)
for {
var err error
var offset uint = 0
if portRange > 0 {
// Intn will panic if portRange == 0, so we do a check.
offset = uint(rand.Intn(portRange))
}
httpPort = offset + s.HTTPPortMin
httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort)
log.Printf("Trying port: %d", httpPort)
s.l, err = net.Listen("tcp", httpAddr)
if err == nil {
break
}
}
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
// Start the HTTP server and run it in the background
fileServer := http.FileServer(http.Dir(s.HTTPDir))
server := &http.Server{Addr: httpAddr, Handler: fileServer}
go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future
state.Put("http_port", httpPort)
return multistep.ActionContinue
}
func (s *StepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil {
// Close the listener so that the HTTP server stops
s.l.Close()
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
package common
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
type StepRun struct {
BootWait time.Duration
Headless bool
vmName string
}
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Starting the virtual machine...")
err := driver.Start(vmName)
if err != nil {
err := fmt.Errorf("Error starting vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vmName = vmName
if int64(s.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait))
wait := time.After(s.BootWait)
WAITLOOP:
for {
select {
case <-wait:
break WAITLOOP
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
}
return multistep.ActionContinue
}
func (s *StepRun) Cleanup(state multistep.StateBag) {
if s.vmName == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if running, _ := driver.IsRunning(s.vmName); running {
if err := driver.Stop(s.vmName); err != nil {
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
}
}
}

View File

@ -1,48 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
package common
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepStartVm struct {
Reason string
StartUpDelay int
}
func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error starting vm: %s"
vmName := state.Get("vmName").(string)
ui.Say("Starting vm for " + s.Reason + "...")
err := hyperv.StartVirtualMachine(vmName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.StartUpDelay != 0 {
//sleepTime := s.StartUpDelay * time.Second
sleepTime := 60 * time.Second
ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime))
time.Sleep(sleepTime);
}
return multistep.ActionContinue
}
func (s *StepStartVm) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,211 @@
package common
import (
"fmt"
"log"
"strings"
"unicode"
"unicode/utf8"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort uint
Name string
}
// This step "types" the boot command into the VM via the prltype script, built on the
// Parallels Virtualization SDK - Python API.
//
// Uses:
// driver Driver
// http_port int
// ui packer.Ui
// vmName string
//
// Produces:
// <nothing>
type StepTypeBootCommand struct {
BootCommand []string
SwitchName string
VMName string
Ctx interpolate.Context
}
func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(Driver)
hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName)
if err != nil {
err := fmt.Errorf("Error getting host adapter ip address: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Host IP for the HyperV machine: %s", hostIp))
s.Ctx.Data = &bootCommandTemplateData{
hostIp,
httpPort,
s.VMName,
}
ui.Say("Typing the boot command...")
scanCodesToSend := []string{}
for _, command := range s.BootCommand {
command, err := interpolate.Render(command, &s.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
scanCodesToSend = append(scanCodesToSend, scancodes(command)...)
}
scanCodesToSendString := strings.Join(scanCodesToSend, " ")
if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
func scancodes(message string) []string {
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
//
// Scancodes represent raw keyboard output and are fed to the VM by the
// VBoxManage controlvm keyboardputscancode program.
//
// Scancodes are recorded here in pairs. The first entry represents
// the key press and the second entry represents the key release and is
// derived from the first by the addition of 0x80.
special := make(map[string][]string)
special["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"53", "d3"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
special["<up>"] = []string{"48", "c8"}
special["<down>"] = []string{"50", "d0"}
special["<left>"] = []string{"4b", "cb"}
special["<right>"] = []string{"4d", "cd"}
special["<spacebar>"] = []string{"39", "b9"}
special["<insert>"] = []string{"52", "d2"}
special["<home>"] = []string{"47", "c7"}
special["<end>"] = []string{"4f", "cf"}
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
scancodeIndex := make(map[string]uint)
scancodeIndex["1234567890-="] = 0x02
scancodeIndex["!@#$%^&*()_+"] = 0x02
scancodeIndex["qwertyuiop[]"] = 0x10
scancodeIndex["QWERTYUIOP{}"] = 0x10
scancodeIndex["asdfghjkl;'`"] = 0x1e
scancodeIndex[`ASDFGHJKL:"~`] = 0x1e
scancodeIndex[`\zxcvbnm,./`] = 0x2b
scancodeIndex["|ZXCVBNM<>?"] = 0x2b
scancodeIndex[" "] = 0x39
scancodeMap := make(map[rune]uint)
for chars, start := range scancodeIndex {
var i uint = 0
for len(chars) > 0 {
r, size := utf8.DecodeRuneInString(chars)
chars = chars[size:]
scancodeMap[r] = start + i
i += 1
}
}
result := make([]string, 0, len(message)*2)
for len(message) > 0 {
var scancode []string
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = []string{"wait5"}
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = []string{"wait10"}
message = message[len("<wait10>"):]
}
if scancode == nil {
for specialCode, specialValue := range special {
if strings.HasPrefix(message, specialCode) {
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
scancode = specialValue
message = message[len(specialCode):]
break
}
}
}
if scancode == nil {
r, size := utf8.DecodeRuneInString(message)
message = message[size:]
scancodeInt := scancodeMap[r]
keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
scancode = make([]string, 0, 4)
if keyShift {
scancode = append(scancode, "2a")
}
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt))
if keyShift {
scancode = append(scancode, "aa")
}
scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80))
log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift)
}
result = append(result, scancode...)
}
return result
}

View File

@ -48,8 +48,8 @@ type Config struct {
hypervcommon.FloppyConfig `mapstructure:",squash"`
hypervcommon.OutputConfig `mapstructure:",squash"`
hypervcommon.SSHConfig `mapstructure:",squash"`
hypervcommon.ShutdownConfig `mapstructure:",squash"`
hypervcommon.RunConfig `mapstructure:",squash"`
hypervcommon.ShutdownConfig `mapstructure:",squash"`
// The size, in megabytes, of the hard disk to create for the VM.
// By default, this is 130048 (about 127 GB).
@ -95,6 +95,7 @@ type Config struct {
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
SwitchName string `mapstructure:"switch_name"`
Cpu uint `mapstructure:"cpu"`
Generation uint `mapstructure:"generation"`
@ -107,6 +108,8 @@ type Config struct {
SSHWaitTimeout time.Duration
SkipCompaction bool `mapstructure:"skip_compaction"`
ctx interpolate.Context
}
@ -115,7 +118,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{},
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
@ -271,6 +276,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
&hypervcommon.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
@ -291,8 +301,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&hypervcommon.StepMountSecondaryDvdImages{},
&hypervcommon.StepStartVm{
Reason: "OS installation",
&hypervcommon.StepRun{
BootWait: b.config.BootWait,
Headless: b.config.Headless,
},
&hypervcommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand,
SwitchName: b.config.SwitchName,
VMName: b.config.VMName,
Ctx: b.config.ctx,
},
// configure the communicator ssh, winrm
@ -321,6 +339,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
SkipCompaction: b.config.SkipCompaction,
},
// the clean up actions for each step will be executed reverse order

View File

@ -5,6 +5,29 @@ import (
"strings"
)
func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
var script = `
param([string]$switchName, [int]$addressIndex)
$HostVMAdapter = 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 = `
@ -106,7 +129,7 @@ param([string]$vmName)
$vm = Get-VM -Name $vmName
if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) {
Stop-VM -VM $vm -TurnOff -Force
Stop-VM -VM $vm -TurnOff -Force -Confirm:$false
}
Remove-VM -Name $vmName -Force
@ -129,6 +152,19 @@ Export-VM -Name $vmName -Path $path
return err
}
func CompactDisks(expPath string, vhdDir string) error {
var script = `
param([string]$srcPath, [string]$vhdDirName)
Get-ChildItem "$srcPath/$vhdDirName" -Filter *.vhd* | %{
Optimize-VHD -Path $_.FullName -Mode Full
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, expPath, vhdDir)
return err
}
func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
var script = `
@ -180,7 +216,10 @@ func StartVirtualMachine(vmName string) error {
var script = `
param([string]$vmName)
Start-VM -Name $vmName
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
Start-VM -Name $vmName
}
`
var ps powershell.PowerShellCmd
@ -206,7 +245,7 @@ func StopVirtualMachine(vmName string) error {
param([string]$vmName)
$vm = Get-VM -Name $vmName
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
Stop-VM -VM $vm
Stop-VM -VM $vm -Confirm:$false
}
`
@ -297,7 +336,7 @@ foreach ($adapter in $adapters) {
}
if($switch -ne $null) {
Get-VMNetworkAdapter VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch
Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch
} else {
Write-Error 'No internet adapters found'
}
@ -327,7 +366,7 @@ func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName strin
var script = `
param([string]$vmName,[string]$switchName)
Get-VMNetworkAdapter VMName $vmName | Connect-VMNetworkAdapter SwitchName $switchName
Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -SwitchName $switchName
`
var ps powershell.PowerShellCmd
@ -404,28 +443,13 @@ $ip
return cmdOut, err
}
func Start(vmName string) error {
var script = `
param([string]$vmName)
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
Start-VM Name $vmName
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func TurnOff(vmName string) error {
var script = `
param([string]$vmName)
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
Stop-VM -Name $vmName -TurnOff
Stop-VM -Name $vmName -TurnOff -Confirm:$false
}
`
@ -440,7 +464,7 @@ func ShutDown(vmName string) error {
param([string]$vmName)
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
Stop-VM Name $vmName
Stop-VM -Name $vmName -Confirm:$false
}
`
@ -448,3 +472,174 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
err := ps.Run(script, vmName)
return err
}
func TypeScanCodes(vmName string, scanCodes string) error {
var script = `
param([string]$vmName, [string]$scanCodes)
#Requires -Version 3
#Requires -RunAsAdministrator
function Get-VMConsole
{
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $VMName
)
$ErrorActionPreference = "Stop"
$vm = Get-CimInstance -ComputerName localhost -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){
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 = Get-VMConsole -VMName $vmName
$scanCodesToSend = ''
$scanCodes.Split(' ') | %{
$scanCode = $_
if ($scanCode.StartsWith('wait')){
$timeToWait = $scanCode.Substring(4)
if (!$timeToWait){
$timeToWait = "10"
}
Start-Sleep -s $timeToWait
if ($scanCodesToSend){
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
$scanCodesToSendByteArray | %{
$vmConsole.TypeScancodes($_)
}
}
$scanCodesToSend = ''
} else {
if ($scanCodesToSend){
$scanCodesToSend = "$scanCodesToSend $scanCode"
} else {
$scanCodesToSend = "$scanCode"
}
}
}
if ($scanCodesToSend){
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
$scanCodesToSendByteArray | %{
$vmConsole.TypeScancodes($_)
}
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, scanCodes)
return err
}