diff --git a/builder/hyperv/common/config.go b/builder/hyperv/common/config.go index 707e24dd..e031ef5d 100644 --- a/builder/hyperv/common/config.go +++ b/builder/hyperv/common/config.go @@ -156,6 +156,10 @@ type CommonConfig struct { // built. When this value is set to true, the machine will start without a // console. Headless bool `mapstructure:"headless" required:"false"` + // Path to a named pipe for COM1 serial output. When set, the VM's COM1 + // port will be configured to output to this pipe, enabling capture of + // boot console output. Example: `\\.\pipe\packer-console` + ComPortPipePath string `mapstructure:"com_port_pipe_path" required:"false"` // When configured, determines the device or device type that is given preferential // treatment when choosing a boot device. // diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 12d2de3a..df06b24b 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -137,4 +137,7 @@ type Driver interface { // Disconnect disconnects to a VM specified by the context cancel function. Disconnect(context.CancelFunc) + + // SetVirtualMachineComPort configures a COM port to output to a named pipe. + SetVirtualMachineComPort(string, int, string) error } diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index a7d2cb78..47d3cb27 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -290,6 +290,12 @@ type DriverMock struct { Disconnect_Called bool Disconnect_Cancel context.CancelFunc + + SetVirtualMachineComPort_Called bool + SetVirtualMachineComPort_VmName string + SetVirtualMachineComPort_PortNumber int + SetVirtualMachineComPort_PipePath string + SetVirtualMachineComPort_Err error } func (d *DriverMock) IsRunning(vmName string) (bool, error) { @@ -673,3 +679,11 @@ func (d *DriverMock) Disconnect(cancel context.CancelFunc) { d.Disconnect_Called = true d.Disconnect_Cancel = cancel } + +func (d *DriverMock) SetVirtualMachineComPort(vmName string, portNumber int, pipePath string) error { + d.SetVirtualMachineComPort_Called = true + d.SetVirtualMachineComPort_VmName = vmName + d.SetVirtualMachineComPort_PortNumber = portNumber + d.SetVirtualMachineComPort_PipePath = pipePath + return d.SetVirtualMachineComPort_Err +} diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index ffef9220..5bf0f190 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -402,3 +402,8 @@ func (d *HypervPS4Driver) Connect(vmName string) (context.CancelFunc, error) { func (d *HypervPS4Driver) Disconnect(cancel context.CancelFunc) { hyperv.DisconnectVirtualMachine(cancel) } + +// SetVirtualMachineComPort configures a COM port to output to a named pipe. +func (d *HypervPS4Driver) SetVirtualMachineComPort(vmName string, portNumber int, pipePath string) error { + return hyperv.SetVMComPort(vmName, portNumber, pipePath) +} diff --git a/builder/hyperv/common/powershell/hyperv/hyperv.go b/builder/hyperv/common/powershell/hyperv/hyperv.go index d95fe0d6..f61f1374 100644 --- a/builder/hyperv/common/powershell/hyperv/hyperv.go +++ b/builder/hyperv/common/powershell/hyperv/hyperv.go @@ -1627,3 +1627,13 @@ func ConnectVirtualMachine(vmName string) (context.CancelFunc, error) { func DisconnectVirtualMachine(cancel context.CancelFunc) { cancel() } + +func SetVMComPort(vmName string, number int, pipePath string) error { + var script = ` +param([string]$vmName, [int]$number, [string]$pipePath) +Set-VMComPort -VMName $vmName -Number $number -Path $pipePath +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.Itoa(number), pipePath) + return err +} diff --git a/builder/hyperv/common/step_clone_vm.go b/builder/hyperv/common/step_clone_vm.go index a0544954..932fa136 100644 --- a/builder/hyperv/common/step_clone_vm.go +++ b/builder/hyperv/common/step_clone_vm.go @@ -40,6 +40,7 @@ type StepCloneVM struct { KeepRegistered bool AdditionalDiskSize []uint DiskBlockSize uint + ComPortPipePath string } func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -189,6 +190,16 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist } } + if s.ComPortPipePath != "" { + err = driver.SetVirtualMachineComPort(s.VMName, 1, s.ComPortPipePath) + if err != nil { + err := fmt.Errorf("Error configuring COM port: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) // instance_id is the generic term used so that users can have access to the diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 99c46aee..4c9f9949 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -42,6 +42,7 @@ type StepCreateVM struct { FixedVHD bool Version string KeepRegistered bool + ComPortPipePath string } func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -189,6 +190,16 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis } } + if s.ComPortPipePath != "" { + err = driver.SetVirtualMachineComPort(s.VMName, 1, s.ComPortPipePath) + if err != nil { + err := fmt.Errorf("Error configuring COM port: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) // instance_id is the generic term used so that users can have access to the diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 7d97c643..1d5687ee 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -248,6 +248,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) FixedVHD: b.config.FixedVHD, Version: b.config.Version, KeepRegistered: b.config.KeepRegistered, + ComPortPipePath: b.config.ComPortPipePath, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/builder/hyperv/iso/builder.hcl2spec.go b/builder/hyperv/iso/builder.hcl2spec.go index a9401b24..672cfdb4 100644 --- a/builder/hyperv/iso/builder.hcl2spec.go +++ b/builder/hyperv/iso/builder.hcl2spec.go @@ -115,6 +115,7 @@ type FlatConfig struct { SkipCompaction *bool `mapstructure:"skip_compaction" required:"false" cty:"skip_compaction" hcl:"skip_compaction"` SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export" hcl:"skip_export"` Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"` + ComPortPipePath *string `mapstructure:"com_port_pipe_path" required:"false" cty:"com_port_pipe_path" hcl:"com_port_pipe_path"` FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device" hcl:"first_boot_device"` BootOrder []string `mapstructure:"boot_order" required:"false" cty:"boot_order" hcl:"boot_order"` ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command" hcl:"shutdown_command"` @@ -243,6 +244,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "skip_compaction": &hcldec.AttrSpec{Name: "skip_compaction", Type: cty.Bool, Required: false}, "skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false}, "headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false}, + "com_port_pipe_path": &hcldec.AttrSpec{Name: "com_port_pipe_path", Type: cty.String, Required: false}, "first_boot_device": &hcldec.AttrSpec{Name: "first_boot_device", Type: cty.String, Required: false}, "boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.List(cty.String), Required: false}, "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index cdc368b2..3b98c75a 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -288,6 +288,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) KeepRegistered: b.config.KeepRegistered, AdditionalDiskSize: b.config.AdditionalDiskSize, DiskBlockSize: b.config.DiskBlockSize, + ComPortPipePath: b.config.ComPortPipePath, }, &hypervcommon.StepResizeVhd{ diff --git a/builder/hyperv/vmcx/builder.hcl2spec.go b/builder/hyperv/vmcx/builder.hcl2spec.go index 1890fc33..8301e7a6 100644 --- a/builder/hyperv/vmcx/builder.hcl2spec.go +++ b/builder/hyperv/vmcx/builder.hcl2spec.go @@ -115,6 +115,7 @@ type FlatConfig struct { SkipCompaction *bool `mapstructure:"skip_compaction" required:"false" cty:"skip_compaction" hcl:"skip_compaction"` SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export" hcl:"skip_export"` Headless *bool `mapstructure:"headless" required:"false" cty:"headless" hcl:"headless"` + ComPortPipePath *string `mapstructure:"com_port_pipe_path" required:"false" cty:"com_port_pipe_path" hcl:"com_port_pipe_path"` FirstBootDevice *string `mapstructure:"first_boot_device" required:"false" cty:"first_boot_device" hcl:"first_boot_device"` BootOrder []string `mapstructure:"boot_order" required:"false" cty:"boot_order" hcl:"boot_order"` ShutdownCommand *string `mapstructure:"shutdown_command" required:"false" cty:"shutdown_command" hcl:"shutdown_command"` @@ -246,6 +247,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "skip_compaction": &hcldec.AttrSpec{Name: "skip_compaction", Type: cty.Bool, Required: false}, "skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false}, "headless": &hcldec.AttrSpec{Name: "headless", Type: cty.Bool, Required: false}, + "com_port_pipe_path": &hcldec.AttrSpec{Name: "com_port_pipe_path", Type: cty.String, Required: false}, "first_boot_device": &hcldec.AttrSpec{Name: "first_boot_device", Type: cty.String, Required: false}, "boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.List(cty.String), Required: false}, "shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false}, diff --git a/docs-partials/builder/hyperv/common/CommonConfig-not-required.mdx b/docs-partials/builder/hyperv/common/CommonConfig-not-required.mdx index 816be863..9a9783ec 100644 --- a/docs-partials/builder/hyperv/common/CommonConfig-not-required.mdx +++ b/docs-partials/builder/hyperv/common/CommonConfig-not-required.mdx @@ -114,6 +114,10 @@ built. When this value is set to true, the machine will start without a console. +- `com_port_pipe_path` (string) - Path to a named pipe for COM1 serial output. When set, the VM's COM1 + port will be configured to output to this pipe, enabling capture of + boot console output. Example: `\\.\pipe\packer-console` + - `first_boot_device` (string) - When configured, determines the device or device type that is given preferential treatment when choosing a boot device.