diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index bce480227..535652923 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -1,5 +1,9 @@ package common +import ( + "context" +) + // A driver is able to talk to HyperV and perform certain // operations with it. Some of the operations on here may seem overly // specific, but they were built specifically in mind to handle features @@ -109,4 +113,10 @@ type Driver interface { MountFloppyDrive(string, string) error UnmountFloppyDrive(string) error + + // Connect connects to a VM specified by the name given. + Connect(string) (context.CancelFunc, error) + + // Disconnect disconnects to a VM specified by the context cancel function. + Disconnect(context.CancelFunc) } diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index f33cfe29f..3fcb46323 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -1,5 +1,9 @@ package common +import ( + "context" +) + type DriverMock struct { IsRunning_Called bool IsRunning_VmName string @@ -240,6 +244,14 @@ type DriverMock struct { UnmountFloppyDrive_Called bool UnmountFloppyDrive_VmName string UnmountFloppyDrive_Err error + + Connect_Called bool + Connect_VmName string + Connect_Cancel context.CancelFunc + Connect_Err error + + Disconnect_Called bool + Disconnect_Cancel context.CancelFunc } func (d *DriverMock) IsRunning(vmName string) (bool, error) { @@ -553,3 +565,14 @@ func (d *DriverMock) UnmountFloppyDrive(vmName string) error { d.UnmountFloppyDrive_VmName = vmName return d.UnmountFloppyDrive_Err } + +func (d *DriverMock) Connect(vmName string) (context.CancelFunc, error) { + d.Connect_Called = true + d.Connect_VmName = vmName + return d.Connect_Cancel, d.Connect_Err +} + +func (d *DriverMock) Disconnect(cancel context.CancelFunc) { + d.Disconnect_Called = true + d.Disconnect_Cancel = cancel +} diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index ef948867a..cadaa2ba5 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -1,6 +1,7 @@ package common import ( + "context" "fmt" "log" "runtime" @@ -347,3 +348,14 @@ func (d *HypervPS4Driver) verifyHypervPermissions() error { return nil } + +// Connect connects to a VM specified by the name given. +func (d *HypervPS4Driver) Connect(vmName string) (context.CancelFunc, error) { + return hyperv.ConnectVirtualMachine(vmName) +} + +// Disconnect disconnects to a VM specified by calling the context cancel function returned +// from Connect. +func (d *HypervPS4Driver) Disconnect(cancel context.CancelFunc) { + hyperv.DisconnectVirtualMachine(cancel) +} diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index 02996fb6f..bb415ffa1 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -3,13 +3,16 @@ package common import ( "context" "fmt" + "log" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) type StepRun struct { - vmName string + GuiCancelFunc context.CancelFunc + Headless bool + vmName string } func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { @@ -29,6 +32,13 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste s.vmName = vmName + if !s.Headless { + ui.Say("Attempting to connect with vmconnect...") + s.GuiCancelFunc, err = driver.Connect(vmName) + if err != nil { + log.Printf(fmt.Sprintf("Non-fatal error starting vmconnect: %s. continuing...", err)) + } + } return multistep.ActionContinue } @@ -40,6 +50,11 @@ func (s *StepRun) Cleanup(state multistep.StateBag) { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + if !s.Headless && s.GuiCancelFunc != nil { + ui.Say("Disconnecting from vmconnect...") + s.GuiCancelFunc() + } + 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)) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 7afd40c52..d50e5e26a 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -115,6 +115,8 @@ type Config struct { // Create the VM with a Fixed VHD format disk instead of Dynamic VHDX FixedVHD bool `mapstructure:"use_fixed_vhd_format"` + Headless bool `mapstructure:"headless"` + ctx interpolate.Context } @@ -427,7 +429,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{}, + &hypervcommon.StepRun{ + Headless: b.config.Headless, + }, &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.FlatBootCommand(), diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 83d911e30..987c41a12 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -98,6 +98,8 @@ type Config struct { SkipExport bool `mapstructure:"skip_export"` + Headless bool `mapstructure:"headless"` + ctx interpolate.Context } @@ -436,7 +438,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{}, + &hypervcommon.StepRun{ + Headless: b.config.Headless, + }, &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.FlatBootCommand(), diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index a7cbb6a06..5b0108dbe 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -1,7 +1,9 @@ package hyperv import ( + "context" "errors" + "os/exec" "strconv" "strings" @@ -1244,3 +1246,18 @@ param([string]$vmName, [string]$scanCodes) 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() +} diff --git a/website/source/docs/builders/hyperv-iso.html.md.erb b/website/source/docs/builders/hyperv-iso.html.md.erb index 85b093acc..a2a97a789 100644 --- a/website/source/docs/builders/hyperv-iso.html.md.erb +++ b/website/source/docs/builders/hyperv-iso.html.md.erb @@ -102,6 +102,11 @@ can be configured for this builder. - `differencing_disk` (boolean) - If true enables differencing disks. Only the changes will be written to the new disk. This is especially useful if your source is a vhd/vhdx. This defaults to false. +- `headless` (boolean) - Packer defaults to building Hyper-V virtual + machines by launching a GUI that shows the console of the machine + being built. When this value is set to true, the machine will start without + a console. + - `skip_export` (boolean) - If true skips VM export. If you are interested only in the vhd/vhdx files, you can enable this option. This will create inline disks which improves the build performance. There will not be any copying of source vhds to temp directory. This defaults to false. diff --git a/website/source/docs/builders/hyperv-vmcx.html.md.erb b/website/source/docs/builders/hyperv-vmcx.html.md.erb index 3433decda..2bb00b0b8 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md.erb +++ b/website/source/docs/builders/hyperv-vmcx.html.md.erb @@ -139,6 +139,11 @@ can be configured for this builder. - `guest_additions_path` (string) - The path to the iso image for guest additions. +- `headless` (boolean) - Packer defaults to building Hyper-V virtual + machines by launching a GUI that shows the console of the machine + being built. When this value is set to true, the machine will start without + a console. + - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting