Repository: hashicorp/packer-plugin-vsphere

Issue:
VMs created with EFI missing the `chipset.motherboardLayout` setting
#### Overview of the Issue

When creating a VM (vCenter / ESX 8.0 Update 3) using the vSphere ISO method, the resulting VM works fine but does not contain the option 'chipset.motherboardLayout' when inspecting the VMX file for the VM. 
If a VM is created using the vSphere UI (with the same hardware configuration) this line IS present in the file (it is set to 'acpi').

The line being missing causes problems when exporting and then re-importing the VM from a vCenter, since when it's imported the missing line gets added - the result of this is that the PCI IDs for the VM change, so internal OS IDs for things like the network interfaces change. For example in my Linux VM 'ens192' becomes 'ens33'.

The issue of changing PCI IDs is detailed here; https://knowledge.broadcom.com/external/article/313466/virtual-device-pci-slot-changes-upon-upg.html which led me to inspect that property - but even though my Packer built VM is being created on hardware version 21 it is not present. I also confirmed the workaround they list, which does solve the issue but requires manual intervention to the VMX.
I tried to use the 'configuration_parameters' option of the Packer plugin to add this option and set it to 'acpi' so that my PCI IDs remain the same regardless of whether the VM has been exported / re-imported, but the option doesn't appear to get set.

#### Reproduction Steps

Deploy a VM using vSphere ISO (I'm using RHEL9 not that should matter) - configuration is simply setting basic CPU, RAM etc.. nothing to specify the hardware version, so it defaults to the latest (21 in my case) and use EFI. 
- Once the VM is built, power it off and inspect the advanced properties - note the ethernet0 PCI ID. 
- Check the VMX file for the VM, note the lack of any line setting 'chipset.motherboardLayout'
- Export the VM to OVF and then re-import it back into vCenter
- Inspect the advanced properties again, note the ethernet0 PCI ID (it should be the same)
- Power on the imported VM
- Inspect the ethernet0 PCI ID once more.. it should now have changed!
- Check the VMX file for the imported VM, you should find a line setting 'chipset.motherboardLayout'

### Packer Version

- 1.11.2

### Plugin Version and Builders

- 1.4.0

Please select the builder.

- [x] `vsphere-iso`
- [ ] `vsphere-clone`

### VMware vSphere Version

- 8.0 Update 3

### Guest Operating System

- RHEL 9
- Server 2022

### Simplified Packer Buildfile

Unfortunately I cannot access the buildfile - the build config is noted above, it is nothing special just a simple install from ISO.

### Operating System and Environment Details

- Operating System: RHEL 9

Discussion:
@tenthirtyam: Can you confirm the same but without the export to OVF and reimport - does it occur with just using the template after being build and then cloned for a new VM?@tenthirtyam: Can you also confirm that if you build a VM manually (not using Packer) and perform the same operation steps that there is a different outcome?@tenthirtyam: Some additional reference:

https://williamlam.com/2023/01/acpi-motherboard-layout-requires-efi-considerations-for-switching-vm-firmware-in-vsphere-8.html@realroywalker: > Can you confirm the same but without the export to OVF and reimport - does it occur with just using the template after being build and then cloned for a new VM?

I have tested this today - the behaviour is slightly different with no export. 

- I built the VM using packer, powered it off and noted the properties inside the VMs VMX file.
  For the packer built VM, 'chipset.motherboardLayout' was missing, and the ethernet.pciSlotNumber was 160.
- I cloned the packer VM via the vSphere UI (Right Click > Clone to Virtual Machine) and inspected the VMX before powering 
  it on. 
  The ethernet.pciSlotNumber was still 160, but chipset.motherboardLayout had been added and was set to 'i440bx'.
- I powered up the new VM, turned it off and checked the properties again - they remained the same. So in this case the 
  PCI id doesn't change

> Can you also confirm that if you build a VM manually (not using Packer) and perform the same operation steps that there is a different outcome?

I carried out the same tests as above, but this time using a manually built VM (via the vSphere UI).

- The manually built VM had 'chipset.motherboardLayout' in the VMX right off the bat (it was set to 'acpi), and the ethernet.pciSlotNumber was 33.
- Then I cloned this VM using the vSphere UI and checked the properties of the new VM, they were identical (and also after powering up / back down).

So it seems that the first part of the issue is packer doesn't add the 'chipset.motherboardLayout' property for whatever reason - then because it's missing vCenter adds the property as 'acpi' (I guess it's default) when the VM is being imported. This triggers the PCI ID changes, since the board layout is different.

Quite how it manages to realise the original layout is actually i440bx when doing a clone inside vCenter I don't know.. but in that case because it's the same as the original layout the IDs don't change.

I've spun up an environment where I can test this issue easily now so feel free to let me know if you need any further things checked.
@tenthirtyam: Curious, if you create a VM with govc, what is the default?

```shell
govc vm.create -m 2048 -c 2 -g otherGuest64 -net "your-network" -ds "your-datastore" -folder "/path/to/your/folder" -pool "your-resource-pool" your-vm-name
```

Reason I'm asking is I want to rule out if this is an govmomi SDK issue first.@realroywalker: > Curious, if you create a VM with govc, what is the default?
> 
> ```shell
> govc vm.create -m 2048 -c 2 -g otherGuest64 -net "your-network" -ds "your-datastore" -folder "/path/to/your/folder" -pool "your-resource-pool" your-vm-name
> ```
> 
> Reason I'm asking is I want to rule out if this is an govmomi SDK issue first.

I just tried with the latest github release of govc (0.44.1) and the command as you suggested does not add the 'chipset.motherboardLayout' option to the VMX of the new VM. 
However - if I supply the option "-firmware efi", then it does create with the chipset.motherboardLayout option (set to acpi).
I also tried with "-firmware bios" but this oddly didn't result in the option appearing with 'i440bx' as the setting.. it was still absent entirely.

To be clear, in my packer tests my hcl also defines the firmware as efi - so in that case I'd have expected the packer VM to have the chipset option set to acpi.@tenthirtyam: Thanks for confirming. 

I'll take a look at this for the v1.5.0 milestone.

v1.4.1 is locked in.@tenthirtyam: Could you try the following as a workaround?

```hcl
configuration_parameters = {
    "chipset.motherboardLayout" = "acpi"
}
```@realroywalker: > Could you try the following as a workaround?
> 
> ```hcl
> configuration_parameters = {
>     "chipset.motherboardLayout" = "acpi"
> }
> ```

Already tried that and no difference I'm afraid 😣@RaphaelDerouelle: Hello, 

I’m having the same issue for the chipset.motherboardLayout missing which is causing problems on large scale infrastructure, needing vms to be reconfigured, do you plan to correct it soon ? If not could you provide me with a workaround ? Thank you 

Best regards@tenthirtyam: i'll be looking at this for the future v2.1.0 release per the current milestone.@bbomgardner: Reporting this issue also. It is causing problems for us especially during [VM restore](https://knowledge.broadcom.com/external/article/408674/virtual-machine-fails-to-boot-after-rest.html). Thank you all for the work on this so far.@tenthirtyam: This issue is resolved in #579 and is slated for the next release milestone, v2.1.0.

Solution patch:
diff --git a/.web-docs/README.md b/.web-docs/README.md
index 626fb9e..deb9ac7 100644
--- a/.web-docs/README.md
+++ b/.web-docs/README.md
@@ -49,6 +49,11 @@ packer plugins install github.com/hashicorp/vsphere
   This builder deploys and publishes new virtual machine to a vSphere Supervisor cluster using VM
   Service.
 
+#### Data Sources
+
+- [vsphere-virtualmachine](/packer/integrations/hashicorp/vsphere/latest/components/data-source/vsphere-virtualmachine) -
+  This data source returns the name of a virtual machine that matches all defined filters.
+
 #### Post-Processors
 
 - [vsphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) -
diff --git a/.web-docs/components/builder/vsphere-clone/README.md b/.web-docs/components/builder/vsphere-clone/README.md
index 7554003..cac6d92 100644
--- a/.web-docs/components/builder/vsphere-clone/README.md
+++ b/.web-docs/components/builder/vsphere-clone/README.md
@@ -319,8 +319,22 @@ JSON Example:
   are silently ignored. Refer to the [`VirtualMachineConfigSpec`](https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.ConfigSpec.html)
   in the vSphere API documentation.
 
-- `tools_sync_time` (bool) - Enable time synchronization with the ESX host where the virtual machine
-  is running. Defaults to `false`.
+- `tools_sync_time` (\*bool) - Enable or disable time synchronization between the guest operating system and the
+  ESX host at startup and after VM operations that may introduce time drift (such
+  as resume from suspend, vMotion, or snapshot restore). If set to `true`, time
+  synchronization is explicitly enabled. If set to `false`, time synchronization is
+  explicitly disabled. If omitted, the builder does not modify the virtual
+  machine's time synchronization settings:
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`true`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
+
+- `tools_sync_time_periodically` (\*bool) - Enable or disable periodic time synchronization between the guest operating
+  system and the ESX host. Use this setting only if the guest operating system does
+  not have native time synchronization.
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`false`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
 
 - `tools_upgrade_policy` (bool) - Automatically check for and upgrade VMware Tools after a virtual machine
   power cycle. Defaults to `false`.
diff --git a/.web-docs/components/builder/vsphere-iso/README.md b/.web-docs/components/builder/vsphere-iso/README.md
index fd76b7f..990f564 100644
--- a/.web-docs/components/builder/vsphere-iso/README.md
+++ b/.web-docs/components/builder/vsphere-iso/README.md
@@ -1641,8 +1641,22 @@ JSON Example:
   are silently ignored. Refer to the [`VirtualMachineConfigSpec`](https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.ConfigSpec.html)
   in the vSphere API documentation.
 
-- `tools_sync_time` (bool) - Enable time synchronization with the ESX host where the virtual machine
-  is running. Defaults to `false`.
+- `tools_sync_time` (\*bool) - Enable or disable time synchronization between the guest operating system and the
+  ESX host at startup and after VM operations that may introduce time drift (such
+  as resume from suspend, vMotion, or snapshot restore). If set to `true`, time
+  synchronization is explicitly enabled. If set to `false`, time synchronization is
+  explicitly disabled. If omitted, the builder does not modify the virtual
+  machine's time synchronization settings:
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`true`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
+
+- `tools_sync_time_periodically` (\*bool) - Enable or disable periodic time synchronization between the guest operating
+  system and the ESX host. Use this setting only if the guest operating system does
+  not have native time synchronization.
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`false`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
 
 - `tools_upgrade_policy` (bool) - Automatically check for and upgrade VMware Tools after a virtual machine
   power cycle. Defaults to `false`.
diff --git a/.web-docs/components/data-source/virtualmachine/README.md b/.web-docs/components/data-source/virtualmachine/README.md
new file mode 100644
index 0000000..9e396b8
--- /dev/null
+++ b/.web-docs/components/data-source/virtualmachine/README.md
@@ -0,0 +1,235 @@
+Type: `vsphere-virtualmachine`
+Artifact BuilderId: `vsphere.virtualmachine`
+
+This data source retrieves information about existing virtual machines from vSphere
+and returns the name of a virtual machine that matches all specified filters. This
+virtual machine can be used in the vSphere Clone builder to select a template.
+
+## Configuration Reference
+
+### Filters Configuration
+
+**Optional:**
+
+<!-- Code generated from the comments of the Config struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `name` (string) - Basic filter with glob support (e.g. `ubuntu_basic*`). Defaults to `*`.
+  Using strict globs will not reduce execution time because vSphere API
+  returns the full inventory. But can be used for better readability over
+  regular expressions.
+
+- `name_regex` (string) - Extended name filter with regular expressions support
+  (e.g. `ubuntu[-_]basic[0-9]*`). Default is empty. The match of the
+  regular expression is checked by substring. Use `^` and `$` to define a
+  full string. For example, the `^[^_]+$` filter will search names
+  without any underscores. The expression must use
+  [Go Regex Syntax](https://pkg.go.dev/regexp/syntax).
+
+- `template` (bool) - Filter to return only objects that are virtual machine templates.
+  Defaults to `false` and returns all virtual machines.
+
+- `host` (string) - Filter to search virtual machines only on the specified ESX host.
+
+- `tag` ([]Tag) - Filter to return only the virtual machines that have attached all specified tags.
+  Specify one or more `tag` blocks to define list of tags for the filter.
+  
+  HCL Example:
+  
+  ```hcl
+  	tag {
+  	  category = "team"
+  	  name     = "operations"
+  	}
+  	tag {
+  	  category = "sla"
+  	  name     = "gold"
+  	}
+  ```
+
+- `latest` (bool) - This filter determines how to handle multiple virtual machines that were matched
+  with all previous filters. Virtual machine creation time is being used to find
+  the latest. By default, multiple matching machines results in an error.
+
+<!-- End of code generated from the comments of the Config struct in datasource/virtualmachine/data.go; -->
+
+
+### Tags Filter Configuration
+
+**Required:**
+
+<!-- Code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `name` (string) - Name of the tag added to virtual machine which must pass the `tag`
+  filter.
+
+- `category` (string) - Name of the tag category that contains the tag.
+  
+  -> **Note:** Both `name` and `category` must be specified in the `tag`
+  filter.
+
+<!-- End of code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; -->
+
+
+### Connection Configuration
+
+**Optional:**
+
+<!-- Code generated from the comments of the ConnectConfig struct in builder/vsphere/common/step_connect.go; DO NOT EDIT MANUALLY -->
+
+- `vcenter_server` (string) - The fully qualified domain name or IP address of the vCenter instance
+  instance.
+
+- `username` (string) - The username to authenticate with the vCenter instance.
+
+- `password` (string) - The password to authenticate with the vCenter instance.
+
+- `insecure_connection` (bool) - Do not validate the certificate of the vCenter instance.
+  Defaults to `false`.
+  
+  -> **Note:** This option is beneficial in scenarios where the certificate
+  is self-signed or does not meet standard validation criteria.
+
+- `datacenter` (string) - The name of the datacenter object in the vSphere inventory.
+  
+  -> **Note:** Required if more than one datacenter object exists in the
+  vSphere inventory.
+
+<!-- End of code generated from the comments of the ConnectConfig struct in builder/vsphere/common/step_connect.go; -->
+
+
+## Output
+
+<!-- Code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `vm_name` (string) - Name of the found virtual machine.
+
+<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; -->
+
+
+## Example Usage
+
+### Filter by Tags and Get Latest
+
+This example demonstrates how to connect to vSphere cluster and search for the latest
+virtual machine that matches the specified tag filters.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  latest         = true
+
+  tag {
+    category = "team"
+    name     = "operations"
+  }
+
+  tag {
+    category = "sla"
+    name     = "gold"
+  }
+}
+
+locals {
+  vm_name = data.vsphere-virtualmachine.example.vm_name
+}
+
+source "null" "example" {
+    communicator = "none"
+}
+
+build {
+  sources = [
+    "source.null.example"
+  ]
+
+  provisioner "shell-local" {
+    inline = [
+      "echo vm_name: ${local.vm_name}",
+    ]
+  }
+}
+```
+
+### Filter by Name with Glob Pattern
+
+This example shows how to use the `name` filter with glob patterns to find virtual
+machines matching a specific naming convention.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name           = "linux-debian-13-*"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter by Name with Regular Expression
+
+This example demonstrates using the `name_regex` filter for more complex pattern
+matching with regular expressions.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name_regex     = "^linux-debian-[0-9]+-.+$"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter for VM Templates Only
+
+This example shows how to use the `template` filter to search only for virtual
+machine templates.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name           = "linux-debian-13-*"
+  template       = true
+}
+
+locals {
+  result_template = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter by ESX Host
+
+This example demonstrates filtering virtual machines by a specific ESX host,
+combined with other filters.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  host           = "esxi-01.example.com"
+  name           = "linux-debian-13-*"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
diff --git a/.web-docs/metadata.hcl b/.web-docs/metadata.hcl
index 017cc2a..1a7e558 100644
--- a/.web-docs/metadata.hcl
+++ b/.web-docs/metadata.hcl
@@ -33,4 +33,9 @@ integration {
     name = "vSphere Template"
     slug = "vsphere-template"
   }
+  component {
+    type = "data-source"
+    name = "vSphere Virtual Machine"
+    slug = "vsphere-virtualmachine"
+  }
 }
diff --git a/builder/vsphere/clone/config.go b/builder/vsphere/clone/config.go
index 1526315..ea9f344 100644
--- a/builder/vsphere/clone/config.go
+++ b/builder/vsphere/clone/config.go
@@ -88,6 +88,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
 	errs = packersdk.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
+	errs = packersdk.MultiErrorAppend(errs, c.ConfigParamsConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.FlagConfig.Prepare(&c.HardwareConfig)...)
 	errs = packersdk.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
 	errs = packersdk.MultiErrorAppend(errs, c.CDRomConfig.Prepare(&c.ReattachCDRomConfig)...)
diff --git a/builder/vsphere/clone/config.hcl2spec.go b/builder/vsphere/clone/config.hcl2spec.go
index 8603b1f..985f456 100644
--- a/builder/vsphere/clone/config.hcl2spec.go
+++ b/builder/vsphere/clone/config.hcl2spec.go
@@ -71,6 +71,7 @@ type FlatConfig struct {
 	VirtualPrecisionClock           *string                                     `mapstructure:"precision_clock" cty:"precision_clock" hcl:"precision_clock"`
 	ConfigParams                    map[string]string                           `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
 	ToolsSyncTime                   *bool                                       `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
+	ToolsSyncTimePeriodically       *bool                                       `mapstructure:"tools_sync_time_periodically" cty:"tools_sync_time_periodically" hcl:"tools_sync_time_periodically"`
 	ToolsUpgradePolicy              *bool                                       `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
 	VbsEnabled                      *bool                                       `mapstructure:"vbs_enabled" cty:"vbs_enabled" hcl:"vbs_enabled"`
 	VvtdEnabled                     *bool                                       `mapstructure:"vvtd_enabled" cty:"vvtd_enabled" hcl:"vvtd_enabled"`
@@ -224,6 +225,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
 		"precision_clock":                &hcldec.AttrSpec{Name: "precision_clock", Type: cty.String, Required: false},
 		"configuration_parameters":       &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
 		"tools_sync_time":                &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
+		"tools_sync_time_periodically":   &hcldec.AttrSpec{Name: "tools_sync_time_periodically", Type: cty.Bool, Required: false},
 		"tools_upgrade_policy":           &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
 		"vbs_enabled":                    &hcldec.AttrSpec{Name: "vbs_enabled", Type: cty.Bool, Required: false},
 		"vvtd_enabled":                   &hcldec.AttrSpec{Name: "vvtd_enabled", Type: cty.Bool, Required: false},
diff --git a/builder/vsphere/common/step_config_params.go b/builder/vsphere/common/step_config_params.go
index 076c523..fe2225d 100644
--- a/builder/vsphere/common/step_config_params.go
+++ b/builder/vsphere/common/step_config_params.go
@@ -47,9 +47,23 @@ type ConfigParamsConfig struct {
 	// are silently ignored. Refer to the [`VirtualMachineConfigSpec`](https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.ConfigSpec.html)
 	// in the vSphere API documentation.
 	ConfigParams map[string]string `mapstructure:"configuration_parameters"`
-	// Enable time synchronization with the ESX host where the virtual machine
-	// is running. Defaults to `false`.
-	ToolsSyncTime bool `mapstructure:"tools_sync_time"`
+	// Enable or disable time synchronization between the guest operating system and the
+	// ESX host at startup and after VM operations that may introduce time drift (such
+	// as resume from suspend, vMotion, or snapshot restore). If set to `true`, time
+	// synchronization is explicitly enabled. If set to `false`, time synchronization is
+	// explicitly disabled. If omitted, the builder does not modify the virtual
+	// machine's time synchronization settings:
+	//   - `vsphere-iso` builder uses the vSphere default for new virtual machines
+	//      (`true`).
+	//   - `vsphere-clone` builder inherits the setting from the source virtual machine.
+	ToolsSyncTime *bool `mapstructure:"tools_sync_time"`
+	// Enable or disable periodic time synchronization between the guest operating
+	// system and the ESX host. Use this setting only if the guest operating system does
+	// not have native time synchronization.
+	//   - `vsphere-iso` builder uses the vSphere default for new virtual machines
+	//      (`false`).
+	//   - `vsphere-clone` builder inherits the setting from the source virtual machine.
+	ToolsSyncTimePeriodically *bool `mapstructure:"tools_sync_time_periodically"`
 	// Automatically check for and upgrade VMware Tools after a virtual machine
 	// power cycle. Defaults to `false`.
 	ToolsUpgradePolicy bool `mapstructure:"tools_upgrade_policy"`
@@ -59,6 +73,18 @@ type StepConfigParams struct {
 	Config *ConfigParamsConfig
 }
 
+func (c *ConfigParamsConfig) Prepare() []error {
+	var errs []error
+
+	if c.ToolsSyncTimePeriodically != nil && *c.ToolsSyncTimePeriodically {
+		if c.ToolsSyncTime == nil || !*c.ToolsSyncTime {
+			errs = append(errs, fmt.Errorf("'tools_sync_time_periodically' requires 'tools_sync_time' to be set to 'true'"))
+		}
+	}
+
+	return errs
+}
+
 func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
 	ui := state.Get("ui").(packersdk.Ui)
 	vm := state.Get("vm").(*driver.VirtualMachineDriver)
@@ -69,11 +95,18 @@ func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) mult
 	}
 
 	var info *types.ToolsConfigInfo
-	if s.Config.ToolsSyncTime || s.Config.ToolsUpgradePolicy {
+
+	if s.Config.ToolsSyncTime != nil || s.Config.ToolsSyncTimePeriodically != nil || s.Config.ToolsUpgradePolicy {
 		info = &types.ToolsConfigInfo{}
 
-		if s.Config.ToolsSyncTime {
-			info.SyncTimeWithHost = &s.Config.ToolsSyncTime
+		// Gate: Whether time synchronization is allowed.
+		if s.Config.ToolsSyncTime != nil {
+			info.SyncTimeWithHostAllowed = s.Config.ToolsSyncTime
+		}
+
+		// Optional: Whether periodic time synchronization is allowed.
+		if s.Config.ToolsSyncTimePeriodically != nil {
+			info.SyncTimeWithHost = s.Config.ToolsSyncTimePeriodically
 		}
 
 		if s.Config.ToolsUpgradePolicy {
diff --git a/builder/vsphere/common/step_config_params.hcl2spec.go b/builder/vsphere/common/step_config_params.hcl2spec.go
index e15d69d..a5719f3 100644
--- a/builder/vsphere/common/step_config_params.hcl2spec.go
+++ b/builder/vsphere/common/step_config_params.hcl2spec.go
@@ -10,9 +10,10 @@ import (
 // FlatConfigParamsConfig is an auto-generated flat version of ConfigParamsConfig.
 // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
 type FlatConfigParamsConfig struct {
-	ConfigParams       map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
-	ToolsSyncTime      *bool             `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
-	ToolsUpgradePolicy *bool             `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
+	ConfigParams              map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
+	ToolsSyncTime             *bool             `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
+	ToolsSyncTimePeriodically *bool             `mapstructure:"tools_sync_time_periodically" cty:"tools_sync_time_periodically" hcl:"tools_sync_time_periodically"`
+	ToolsUpgradePolicy        *bool             `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
 }
 
 // FlatMapstructure returns a new FlatConfigParamsConfig.
@@ -27,9 +28,10 @@ func (*ConfigParamsConfig) FlatMapstructure() interface{ HCL2Spec() map[string]h
 // The decoded values from this spec will then be applied to a FlatConfigParamsConfig.
 func (*FlatConfigParamsConfig) HCL2Spec() map[string]hcldec.Spec {
 	s := map[string]hcldec.Spec{
-		"configuration_parameters": &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
-		"tools_sync_time":          &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
-		"tools_upgrade_policy":     &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
+		"configuration_parameters":     &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
+		"tools_sync_time":              &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
+		"tools_sync_time_periodically": &hcldec.AttrSpec{Name: "tools_sync_time_periodically", Type: cty.Bool, Required: false},
+		"tools_upgrade_policy":         &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
 	}
 	return s
 }
diff --git a/builder/vsphere/common/step_connect.go b/builder/vsphere/common/step_connect.go
index e35c6b2..1401b71 100644
--- a/builder/vsphere/common/step_connect.go
+++ b/builder/vsphere/common/step_connect.go
@@ -59,6 +59,8 @@ type StepConnect struct {
 }
 
 func (s *StepConnect) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
+	// Convert common.ConnectConfig to driver.ConnectConfig
+	// Both structs have identical fields to avoid import cycles
 	d, err := driver.NewDriver(&driver.ConnectConfig{
 		VCenterServer:      s.Config.VCenterServer,
 		Username:           s.Config.Username,
diff --git a/builder/vsphere/driver/cluster.go b/builder/vsphere/driver/cluster.go
index 709b752..d3da6f8 100644
--- a/builder/vsphere/driver/cluster.go
+++ b/builder/vsphere/driver/cluster.go
@@ -14,7 +14,7 @@ type Cluster struct {
 // Returns a Cluster object or an error if not found or if the retrieval
 // process fails.
 func (d *VCenterDriver) FindCluster(name string) (*Cluster, error) {
-	c, err := d.finder.ClusterComputeResource(d.ctx, name)
+	c, err := d.Finder.ClusterComputeResource(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
diff --git a/builder/vsphere/driver/datastore.go b/builder/vsphere/driver/datastore.go
index 0e2e676..07b9610 100644
--- a/builder/vsphere/driver/datastore.go
+++ b/builder/vsphere/driver/datastore.go
@@ -37,7 +37,7 @@ type DatastoreDriver struct {
 // NewDatastore creates a new Datastore object.
 func (d *VCenterDriver) NewDatastore(ref *types.ManagedObjectReference) Datastore {
 	return &DatastoreDriver{
-		ds:     object.NewDatastore(d.client.Client, *ref),
+		ds:     object.NewDatastore(d.Client.Client, *ref),
 		driver: d,
 	}
 }
@@ -68,7 +68,7 @@ func (d *VCenterDriver) FindDatastore(name string, host string) (Datastore, erro
 		name = inf.Name
 	}
 
-	ds, err := d.finder.Datastore(d.ctx, name)
+	ds, err := d.Finder.Datastore(d.Ctx, name)
 	if err != nil {
 		return nil, fmt.Errorf("error finding datastore with name %s: %s", name, err)
 	}
@@ -87,10 +87,10 @@ func (d *VCenterDriver) GetDatastoreName(id string) (string, error) {
 		Type:  "Datastore",
 		Value: id,
 	}
-	pc := property.DefaultCollector(d.vimClient)
+	pc := property.DefaultCollector(d.VimClient)
 	var me mo.ManagedEntity
 
-	err := pc.RetrieveOne(d.ctx, obj, []string{"name"}, &me)
+	err := pc.RetrieveOne(d.Ctx, obj, []string{"name"}, &me)
 	if err != nil {
 		return id, err
 	}
@@ -108,7 +108,7 @@ func (ds *DatastoreDriver) Info(params ...string) (*mo.Datastore, error) {
 		p = params
 	}
 	var info mo.Datastore
-	err := ds.ds.Properties(ds.driver.ctx, ds.ds.Reference(), p, &info)
+	err := ds.ds.Properties(ds.driver.Ctx, ds.ds.Reference(), p, &info)
 	if err != nil {
 		return nil, err
 	}
@@ -116,14 +116,14 @@ func (ds *DatastoreDriver) Info(params ...string) (*mo.Datastore, error) {
 }
 
 func (ds *DatastoreDriver) DirExists(filepath string) bool {
-	_, err := ds.ds.Stat(ds.driver.ctx, filepath)
+	_, err := ds.ds.Stat(ds.driver.Ctx, filepath)
 	var notFoundError *object.DatastoreNoSuchDirectoryError
 	return !errors.As(err, &notFoundError)
 }
 
 // FileExists checks if a file exists in a datastore.
 func (ds *DatastoreDriver) FileExists(path string) bool {
-	_, err := ds.ds.Stat(ds.driver.ctx, path)
+	_, err := ds.ds.Stat(ds.driver.Ctx, path)
 	return err == nil
 }
 
@@ -145,9 +145,9 @@ func (ds *DatastoreDriver) ResolvePath(path string) string {
 // GetDatastoreFilePath retrieves the full path of a file in a specified datastore directory by its datastore ID and name.
 func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
 	ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID}
-	ds := object.NewDatastore(d.vimClient, ref)
+	ds := object.NewDatastore(d.VimClient, ref)
 
-	b, err := ds.Browser(d.ctx)
+	b, err := ds.Browser(d.Ctx)
 	if err != nil {
 		return filename, err
 	}
@@ -157,12 +157,12 @@ func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string)
 		MatchPattern: []string{pat},
 	}
 
-	task, err := b.SearchDatastore(d.ctx, dir, &spec)
+	task, err := b.SearchDatastore(d.Ctx, dir, &spec)
 	if err != nil {
 		return filename, err
 	}
 
-	info, err := task.WaitForResult(d.ctx, nil)
+	info, err := task.WaitForResult(d.Ctx, nil)
 	if err != nil {
 		return filename, err
 	}
@@ -182,7 +182,7 @@ func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string)
 // in the datastore, with optional host context.
 func (ds *DatastoreDriver) UploadFile(src, dst, host string, setHost bool) error {
 	p := soap.DefaultUpload
-	ctx := ds.driver.ctx
+	ctx := ds.driver.Ctx
 
 	if setHost && host != "" {
 		h, err := ds.driver.FindHost(host)
@@ -197,22 +197,22 @@ func (ds *DatastoreDriver) UploadFile(src, dst, host string, setHost bool) error
 
 // Delete deletes a file from a datastore by a path.
 func (ds *DatastoreDriver) Delete(path string) error {
-	dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
+	dc, err := ds.driver.Finder.Datacenter(ds.driver.Ctx, ds.ds.DatacenterPath)
 	if err != nil {
 		return err
 	}
 	fm := ds.ds.NewFileManager(dc, false)
-	return fm.Delete(ds.driver.ctx, path)
+	return fm.Delete(ds.driver.Ctx, path)
 }
 
 // MakeDirectory creates a directory in a datastore by a path.
 func (ds *DatastoreDriver) MakeDirectory(path string) error {
-	dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
+	dc, err := ds.driver.Finder.Datacenter(ds.driver.Ctx, ds.ds.DatacenterPath)
 	if err != nil {
 		return err
 	}
 	fm := ds.ds.NewFileManager(dc, false)
-	return fm.FileManager.MakeDirectory(ds.driver.ctx, path, dc, true)
+	return fm.FileManager.MakeDirectory(ds.driver.Ctx, path, dc, true)
 }
 
 // RemoveDatastorePrefix removes the datastore prefix from a path.
diff --git a/builder/vsphere/driver/driver.go b/builder/vsphere/driver/driver.go
index 4184c76..465eb97 100644
--- a/builder/vsphere/driver/driver.go
+++ b/builder/vsphere/driver/driver.go
@@ -51,29 +51,29 @@ type Driver interface {
 }
 
 type VCenterDriver struct {
-	// context that controls the authenticated sessions used to run the VM commands
-	ctx        context.Context
-	client     *govmomi.Client
-	vimClient  *vim25.Client
-	restClient *RestClient
-	finder     *find.Finder
-	datacenter *object.Datacenter
+	Ctx        context.Context
+	Client     *govmomi.Client
+	VimClient  *vim25.Client
+	RestClient *RestClient
+	Finder     *find.Finder
+	Datacenter *object.Datacenter
 }
 
 func NewVCenterDriver(ctx context.Context, client *govmomi.Client, vimClient *vim25.Client, user *url.Userinfo, finder *find.Finder, datacenter *object.Datacenter) *VCenterDriver {
 	return &VCenterDriver{
-		ctx:       ctx,
-		client:    client,
-		vimClient: vimClient,
-		restClient: &RestClient{
+		Ctx:       ctx,
+		Client:    client,
+		VimClient: vimClient,
+		RestClient: &RestClient{
 			client:      rest.NewClient(vimClient),
 			credentials: user,
 		},
-		datacenter: datacenter,
-		finder:     finder,
+		Datacenter: datacenter,
+		Finder:     finder,
 	}
 }
 
+// ConnectConfig contains the configuration for connecting to vCenter.
 type ConnectConfig struct {
 	VCenterServer      string
 	Username           string
@@ -117,24 +117,24 @@ func NewDriver(config *ConnectConfig) (Driver, error) {
 	finder.SetDatacenter(datacenter)
 
 	d := &VCenterDriver{
-		ctx:       ctx,
-		client:    client,
-		vimClient: vimClient,
-		restClient: &RestClient{
+		Ctx:       ctx,
+		Client:    client,
+		VimClient: vimClient,
+		RestClient: &RestClient{
 			client:      rest.NewClient(vimClient),
 			credentials: credentials,
 		},
-		datacenter: datacenter,
-		finder:     finder,
+		Datacenter: datacenter,
+		Finder:     finder,
 	}
 	return d, nil
 }
 
 func (d *VCenterDriver) Cleanup() (error, error) {
-	return d.restClient.client.Logout(d.ctx), d.client.SessionManager.Logout(d.ctx)
+	return d.RestClient.client.Logout(d.Ctx), d.Client.SessionManager.Logout(d.Ctx)
 }
 
-// RestClient manages RESTful interactions with vCenter, handling client initialization and credential storage.
+// RestClient manages RESTful interactions with vCenter.
 type RestClient struct {
 	client      *rest.Client
 	credentials *url.Userinfo
@@ -147,3 +147,8 @@ func (r *RestClient) Login(ctx context.Context) error {
 func (r *RestClient) Logout(ctx context.Context) error {
 	return r.client.Logout(ctx)
 }
+
+// Client returns the underlying rest.Client for direct API access.
+func (r *RestClient) Client() *rest.Client {
+	return r.client
+}
diff --git a/builder/vsphere/driver/folder.go b/builder/vsphere/driver/folder.go
index 2c93c2e..e80f6fd 100644
--- a/builder/vsphere/driver/folder.go
+++ b/builder/vsphere/driver/folder.go
@@ -22,7 +22,7 @@ type Folder struct {
 
 func (d *VCenterDriver) NewFolder(ref *types.ManagedObjectReference) *Folder {
 	return &Folder{
-		folder: object.NewFolder(d.client.Client, *ref),
+		folder: object.NewFolder(d.Client.Client, *ref),
 		driver: d,
 	}
 }
@@ -34,17 +34,17 @@ func (d *VCenterDriver) FindFolder(name string) (*Folder, error) {
 	if name != "" {
 		// If the folder does not exist, create it.
 		parent := ""
-		parentFolder, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm"))
+		parentFolder, err := d.Finder.Folder(d.Ctx, path.Join(d.Datacenter.InventoryPath, "vm"))
 		if err != nil {
 			return nil, err
 		}
 		folders := strings.Split(name, "/")
 		for _, folder := range folders {
 			parent = path.Join(parent, folder)
-			f, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm", parent))
+			f, err := d.Finder.Folder(d.Ctx, path.Join(d.Datacenter.InventoryPath, "vm", parent))
 			var notFoundError *find.NotFoundError
 			if errors.As(err, &notFoundError) {
-				f, err = parentFolder.CreateFolder(d.ctx, folder)
+				f, err = parentFolder.CreateFolder(d.Ctx, folder)
 			}
 			if err != nil {
 				return nil, err
@@ -53,7 +53,7 @@ func (d *VCenterDriver) FindFolder(name string) (*Folder, error) {
 		}
 	}
 
-	f, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm", name))
+	f, err := d.Finder.Folder(d.Ctx, path.Join(d.Datacenter.InventoryPath, "vm", name))
 	if err != nil {
 		return nil, err
 	}
@@ -74,7 +74,7 @@ func (f *Folder) Info(params ...string) (*mo.Folder, error) {
 		p = params
 	}
 	var info mo.Folder
-	err := f.folder.Properties(f.driver.ctx, f.folder.Reference(), p, &info)
+	err := f.folder.Properties(f.driver.Ctx, f.folder.Reference(), p, &info)
 	if err != nil {
 		return nil, err
 	}
diff --git a/builder/vsphere/driver/host.go b/builder/vsphere/driver/host.go
index 3c4728f..62df39b 100644
--- a/builder/vsphere/driver/host.go
+++ b/builder/vsphere/driver/host.go
@@ -18,7 +18,7 @@ type Host struct {
 // ManagedObjectReference and the VCenterDriver instance.
 func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host {
 	return &Host{
-		host:   object.NewHostSystem(d.client.Client, *ref),
+		host:   object.NewHostSystem(d.Client.Client, *ref),
 		driver: d,
 	}
 }
@@ -26,7 +26,7 @@ func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host {
 // FindHost locates a host within the vCenter environment by its name. Returns
 // a Host object or an error if not found or if the retrieval process fails.
 func (d *VCenterDriver) FindHost(name string) (*Host, error) {
-	h, err := d.finder.HostSystem(d.ctx, name)
+	h, err := d.Finder.HostSystem(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
@@ -46,7 +46,7 @@ func (h *Host) Info(params ...string) (*mo.HostSystem, error) {
 		p = params
 	}
 	var info mo.HostSystem
-	err := h.host.Properties(h.driver.ctx, h.host.Reference(), p, &info)
+	err := h.host.Properties(h.driver.Ctx, h.host.Reference(), p, &info)
 	if err != nil {
 		return nil, err
 	}
diff --git a/builder/vsphere/driver/library.go b/builder/vsphere/driver/library.go
index 4661fa0..fc7dc18 100644
--- a/builder/vsphere/driver/library.go
+++ b/builder/vsphere/driver/library.go
@@ -20,8 +20,8 @@ type Library struct {
 // FindContentLibraryByName retrieves a content library by its name. Returns a
 // Library object or an error if the library is not found.
 func (d *VCenterDriver) FindContentLibraryByName(name string) (*Library, error) {
-	lm := library.NewManager(d.restClient.client)
-	l, err := lm.GetLibraryByName(d.ctx, name)
+	lm := library.NewManager(d.RestClient.client)
+	l, err := lm.GetLibraryByName(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
@@ -35,8 +35,8 @@ func (d *VCenterDriver) FindContentLibraryByName(name string) (*Library, error)
 // the specified library ID.  Returns the library item if found or an error if
 // the item is not found or the retrieval process fails.
 func (d *VCenterDriver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
-	lm := library.NewManager(d.restClient.client)
-	items, err := lm.GetLibraryItems(d.ctx, libraryId)
+	lm := library.NewManager(d.RestClient.client)
+	items, err := lm.GetLibraryItems(d.Ctx, libraryId)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (d *VCenterDriver) FindContentLibraryItemUUID(libraryId string, name string
 // not identified as a content library path or if the retrieval process fails.
 func (d *VCenterDriver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
 	log.Printf("Check if ISO path is a Content Library path")
-	err := d.restClient.Login(d.ctx)
+	err := d.RestClient.Login(d.Ctx)
 	if err != nil {
 		log.Printf("vCenter client not available. ISO path not identified as a Content Library path")
 		return isoPath, err
@@ -107,20 +107,20 @@ func (d *VCenterDriver) FindContentLibraryFileDatastorePath(isoPath string) (str
 		return isoPath, err
 	}
 
-	_ = d.restClient.Logout(d.ctx)
+	_ = d.RestClient.Logout(d.Ctx)
 	return path.Join(libItemDir, isoFilePath), nil
 }
 
 // UpdateContentLibraryItem updates the metadata of a content library item,
 // such as its name and description. Returns an error if the update fails.
 func (d *VCenterDriver) UpdateContentLibraryItem(item *library.Item, name string, description string) error {
-	lm := library.NewManager(d.restClient.client)
+	lm := library.NewManager(d.RestClient.client)
 	item.Patch(&library.Item{
 		ID:          item.ID,
 		Name:        name,
 		Description: &description,
 	})
-	return lm.UpdateLibraryItem(d.ctx, item)
+	return lm.UpdateLibraryItem(d.Ctx, item)
 }
 
 type LibraryFilePath struct {
diff --git a/builder/vsphere/driver/network.go b/builder/vsphere/driver/network.go
index 5b91fc1..566f79a 100644
--- a/builder/vsphere/driver/network.go
+++ b/builder/vsphere/driver/network.go
@@ -20,7 +20,7 @@ type Network struct {
 // ManagedObjectReference.
 func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network {
 	return &Network{
-		network: object.NewNetwork(d.client.Client, *ref),
+		network: object.NewNetwork(d.Client.Client, *ref),
 		driver:  d,
 	}
 }
@@ -28,7 +28,7 @@ func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network {
 // FindNetwork locates a network by its name within the vCenter context.
 // Returns a Network object or an error if the network is not found.
 func (d *VCenterDriver) FindNetwork(name string) (*Network, error) {
-	n, err := d.finder.Network(d.ctx, name)
+	n, err := d.Finder.Network(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
@@ -41,7 +41,7 @@ func (d *VCenterDriver) FindNetwork(name string) (*Network, error) {
 // FindNetworks retrieves a list of networks in the vCenter matching the
 // provided name and returns them as Network objects.
 func (d *VCenterDriver) FindNetworks(name string) ([]*Network, error) {
-	ns, err := d.finder.NetworkList(d.ctx, name)
+	ns, err := d.Finder.NetworkList(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
@@ -72,7 +72,7 @@ func (n *Network) Info(params ...string) (*mo.Network, error) {
 		return nil, fmt.Errorf("unexpected %t network object type", n.network)
 	}
 
-	err := network.Properties(n.driver.ctx, network.Reference(), p, &info)
+	err := network.Properties(n.driver.Ctx, network.Reference(), p, &info)
 	if err != nil {
 		return nil, err
 	}
diff --git a/builder/vsphere/driver/resource_pool.go b/builder/vsphere/driver/resource_pool.go
index 6b5fec5..8708f11 100644
--- a/builder/vsphere/driver/resource_pool.go
+++ b/builder/vsphere/driver/resource_pool.go
@@ -23,7 +23,7 @@ type ResourcePool struct {
 // provided ManagedObjectReference.
 func (d *VCenterDriver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
 	return &ResourcePool{
-		pool:   object.NewResourcePool(d.client.Client, *ref),
+		pool:   object.NewResourcePool(d.Client.Client, *ref),
 		driver: d,
 	}
 }
@@ -41,13 +41,13 @@ func (d *VCenterDriver) FindResourcePool(cluster string, host string, name strin
 	}
 
 	resourcePath := fmt.Sprintf("%v/Resources/%v", res, name)
-	p, err := d.finder.ResourcePool(d.ctx, resourcePath)
+	p, err := d.Finder.ResourcePool(d.Ctx, resourcePath)
 	if err != nil {
 		log.Printf("[WARN] %s not found. Looking for default resource pool.", resourcePath)
-		dp, dperr := d.finder.DefaultResourcePool(d.ctx)
+		dp, dperr := d.Finder.DefaultResourcePool(d.Ctx)
 		var notFoundError *find.NotFoundError
 		if errors.As(dperr, &notFoundError) {
-			vapp, verr := d.finder.VirtualApp(d.ctx, name)
+			vapp, verr := d.Finder.VirtualApp(d.Ctx, name)
 			if verr != nil {
 				return nil, err
 			}
@@ -73,7 +73,7 @@ func (p *ResourcePool) Info(params ...string) (*mo.ResourcePool, error) {
 		params2 = params
 	}
 	var info mo.ResourcePool
-	err := p.pool.Properties(p.driver.ctx, p.pool.Reference(), params2, &info)
+	err := p.pool.Properties(p.driver.Ctx, p.pool.Reference(), params2, &info)
 	if err != nil {
 		return nil, err
 	}
diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go
index 36675f1..ef56be8 100644
--- a/builder/vsphere/driver/vm.go
+++ b/builder/vsphere/driver/vm.go
@@ -143,20 +143,21 @@ type CreateConfig struct {
 	NICs          []NIC
 	USBController []string
 	Version       uint
+	Firmware      string
 	StorageConfig StorageConfig
 }
 
 // NewVM creates a new virtual machine object.
 func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
 	return &VirtualMachineDriver{
-		vm:     object.NewVirtualMachine(d.client.Client, *ref),
+		vm:     object.NewVirtualMachine(d.Client.Client, *ref),
 		driver: d,
 	}
 }
 
 // FindVM locates a virtual machine by its name.
 func (d *VCenterDriver) FindVM(name string) (VirtualMachine, error) {
-	vm, err := d.finder.VirtualMachine(d.ctx, name)
+	vm, err := d.Finder.VirtualMachine(d.Ctx, name)
 	if err != nil {
 		return nil, err
 	}
@@ -218,6 +219,19 @@ func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
 		createSpec.Version = fmt.Sprintf("%s%d", "vmx-", config.Version)
 	}
 
+	// Set firmware, if specified.
+	if config.Firmware != "" {
+		firmware := config.Firmware
+		efiSecureBootEnabled := firmware == "efi-secure"
+		if efiSecureBootEnabled {
+			firmware = "efi"
+		}
+		createSpec.Firmware = firmware
+		createSpec.BootOptions = &types.VirtualMachineBootOptions{
+			EfiSecureBootEnabled: types.NewBool(efiSecureBootEnabled),
+		}
+	}
+
 	folder, err := d.FindFolder(config.Folder)
 	if err != nil {
 		return nil, err
@@ -282,11 +296,11 @@ func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
 		VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
 	}
 
-	task, err := folder.folder.CreateVM(d.ctx, createSpec, resourcePool.pool, host)
+	task, err := folder.folder.CreateVM(d.Ctx, createSpec, resourcePool.pool, host)
 	if err != nil {
 		return nil, err
 	}
-	taskInfo, err := task.WaitForResult(d.ctx, nil)
+	taskInfo, err := task.WaitForResult(d.Ctx, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -296,7 +310,9 @@ func (d *VCenterDriver) CreateVM(config *CreateConfig) (VirtualMachine, error) {
 		return nil, fmt.Errorf("something went wrong when creating the VM")
 	}
 
-	return d.NewVM(&vmRef), nil
+	vm := d.NewVM(&vmRef)
+
+	return vm, nil
 }
 
 // Info retrieves properties of the virtual machine object with optional filters
@@ -310,7 +326,7 @@ func (vm *VirtualMachineDriver) Info(params ...string) (*mo.VirtualMachine, erro
 		p = params
 	}
 	var info mo.VirtualMachine
-	err := vm.vm.Properties(vm.driver.ctx, vm.vm.Reference(), p, &info)
+	err := vm.vm.Properties(vm.driver.Ctx, vm.vm.Reference(), p, &info)
 	if err != nil {
 		return nil, err
 	}
@@ -406,7 +422,7 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
 		configSpec.Annotation = config.Annotation
 	}
 
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -465,7 +481,6 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
 			Device:    adapter.(types.BaseVirtualDevice),
 			Operation: types.VirtualDeviceConfigSpecOperationEdit,
 		}
-
 		configSpec.DeviceChange = append(configSpec.DeviceChange, config)
 	}
 
@@ -475,7 +490,7 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
 	}
 	configSpec.VAppConfig = vAppConfig
 
-	task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
+	task, err := vm.vm.Clone(vm.driver.Ctx, folder.folder, config.Name, cloneSpec)
 	if err != nil {
 		return nil, fmt.Errorf("error calling vm.vm.Clone task: %s", err)
 	}
@@ -559,12 +574,12 @@ func (vm *VirtualMachineDriver) AddPublicKeys(ctx context.Context, publicKeys st
 	}
 
 	confSpec := types.VirtualMachineConfigSpec{VAppConfig: config}
-	task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
+	task, err := vm.vm.Reconfigure(vm.driver.Ctx, confSpec)
 	if err != nil {
 		return err
 	}
 
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
@@ -580,11 +595,11 @@ func (vm *VirtualMachineDriver) Properties(ctx context.Context) (*mo.VirtualMach
 
 // Destroy removes the virtual machine.
 func (vm *VirtualMachineDriver) Destroy() error {
-	task, err := vm.vm.Destroy(vm.driver.ctx)
+	task, err := vm.vm.Destroy(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
@@ -628,7 +643,7 @@ func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
 	}
 
 	if config.VideoRAM != 0 || config.Displays != 0 {
-		devices, err := vm.vm.Device(vm.driver.ctx)
+		devices, err := vm.vm.Device(vm.driver.Ctx)
 		if err != nil {
 			return err
 		}
@@ -654,7 +669,7 @@ func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
 	}
 
 	if config.VGPUProfile != "" {
-		devices, err := vm.vm.Device(vm.driver.ctx)
+		devices, err := vm.vm.Device(vm.driver.Ctx)
 		if err != nil {
 			return err
 		}
@@ -692,12 +707,10 @@ func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
 		confSpec.DeviceChange = append(confSpec.DeviceChange, spec)
 	}
 
-	efiSecureBootEnabled := false
 	firmware := config.Firmware
-
-	if firmware == "efi-secure" {
+	efiSecureBootEnabled := firmware == "efi-secure"
+	if efiSecureBootEnabled {
 		firmware = "efi"
-		efiSecureBootEnabled = true
 	}
 
 	confSpec.Firmware = firmware
@@ -709,12 +722,12 @@ func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
 		}
 	}
 
-	task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
+	task, err := vm.vm.Reconfigure(vm.driver.Ctx, confSpec)
 	if err != nil {
 		return err
 	}
 
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	if err != nil {
 		return err
 	}
@@ -751,28 +764,28 @@ func (vm *VirtualMachineDriver) Configure(config *HardwareConfig) error {
 		}
 	}
 
-	return err
+	return nil
 }
 
 // Reconfigure modifies the configuration of an existing virtual machine based
 // on the provided configuration specification.
 func (vm *VirtualMachineDriver) Reconfigure(confSpec types.VirtualMachineConfigSpec) error {
-	task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
+	task, err := vm.vm.Reconfigure(vm.driver.Ctx, confSpec)
 	if err != nil {
 		return err
 	}
 
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
 // Customize applies the given CustomizationSpec to the virtual machine.
 func (vm *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error {
-	task, err := vm.vm.Customize(vm.driver.ctx, spec)
+	task, err := vm.vm.Customize(vm.driver.Ctx, spec)
 	if err != nil {
 		return err
 	}
-	return task.Wait(vm.driver.ctx)
+	return task.Wait(vm.driver.Ctx)
 }
 
 // ResizeDisk adjusts the size of the virtual disk to the specified diskSize in
@@ -780,7 +793,7 @@ func (vm *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error {
 // an error if the operation fails.
 // TODO: This method should be refactored to support resizing multiple disks.
 func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) ([]types.BaseVirtualDeviceConfigSpec, error) {
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -803,11 +816,11 @@ func (vm *VirtualMachineDriver) ResizeDisk(diskSize int64) ([]types.BaseVirtualD
 
 // PowerOn starts the virtual machine and waits for the operation to complete.
 func (vm *VirtualMachineDriver) PowerOn() error {
-	task, err := vm.vm.PowerOn(vm.driver.ctx)
+	task, err := vm.vm.PowerOn(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
@@ -839,7 +852,7 @@ func (vm *VirtualMachineDriver) WaitForIP(ctx context.Context, ipNet *net.IPNet)
 
 // PowerOff stops the virtual machine and waits for the operation to complete.
 func (vm *VirtualMachineDriver) PowerOff() error {
-	state, err := vm.vm.PowerState(vm.driver.ctx)
+	state, err := vm.vm.PowerState(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -848,17 +861,17 @@ func (vm *VirtualMachineDriver) PowerOff() error {
 		return nil
 	}
 
-	task, err := vm.vm.PowerOff(vm.driver.ctx)
+	task, err := vm.vm.PowerOff(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
 // IsPoweredOff checks if the virtual machine is powered off.
 func (vm *VirtualMachineDriver) IsPoweredOff() (bool, error) {
-	state, err := vm.vm.PowerState(vm.driver.ctx)
+	state, err := vm.vm.PowerState(vm.driver.Ctx)
 	if err != nil {
 		return false, err
 	}
@@ -868,7 +881,7 @@ func (vm *VirtualMachineDriver) IsPoweredOff() (bool, error) {
 
 // StartShutdown initiates a guest shutdown operation.
 func (vm *VirtualMachineDriver) StartShutdown() error {
-	err := vm.vm.ShutdownGuest(vm.driver.ctx)
+	err := vm.vm.ShutdownGuest(vm.driver.Ctx)
 	return err
 }
 
@@ -899,22 +912,22 @@ func (vm *VirtualMachineDriver) WaitForShutdown(ctx context.Context, timeout tim
 
 // CreateSnapshot creates a snapshot of the virtual machine.
 func (vm *VirtualMachineDriver) CreateSnapshot(name string) error {
-	task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false)
+	task, err := vm.vm.CreateSnapshot(vm.driver.Ctx, name, "", false, false)
 	if err != nil {
 		return err
 	}
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
 // ConvertToTemplate converts the virtual machine to a template.
 func (vm *VirtualMachineDriver) ConvertToTemplate() error {
-	return vm.vm.MarkAsTemplate(vm.driver.ctx)
+	return vm.vm.MarkAsTemplate(vm.driver.Ctx)
 }
 
 // IsTemplate checks if the virtual machine is a template.
 func (vm *VirtualMachineDriver) IsTemplate() (bool, error) {
-	state, err := vm.vm.IsTemplate(vm.driver.ctx)
+	state, err := vm.vm.IsTemplate(vm.driver.Ctx)
 	if err != nil {
 		return false, err
 	}
@@ -938,12 +951,12 @@ func (vm *VirtualMachineDriver) ConvertToVirtualMachine(vsphereCluster string, v
 		return err
 	}
 
-	return vm.vm.MarkAsVirtualMachine(vm.driver.ctx, *resourcePool.pool, host)
+	return vm.vm.MarkAsVirtualMachine(vm.driver.Ctx, *resourcePool.pool, host)
 }
 
 // ImportOvfToContentLibrary imports the OVF to the content library.
 func (vm *VirtualMachineDriver) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
-	err := vm.driver.restClient.Login(vm.driver.ctx)
+	err := vm.driver.RestClient.Login(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -977,18 +990,18 @@ func (vm *VirtualMachineDriver) ImportOvfToContentLibrary(ovf vcenter.OVF) error
 	ovf.Source.Value = vm.vm.Reference().Value
 	ovf.Source.Type = "VirtualMachine"
 
-	vcm := vcenter.NewManager(vm.driver.restClient.client)
-	_, err = vcm.CreateOVF(vm.driver.ctx, ovf)
+	vcm := vcenter.NewManager(vm.driver.RestClient.client)
+	_, err = vcm.CreateOVF(vm.driver.Ctx, ovf)
 	if err != nil {
 		return err
 	}
 
-	return vm.driver.restClient.Logout(vm.driver.ctx)
+	return vm.driver.RestClient.Logout(vm.driver.Ctx)
 }
 
 // ImportToContentLibrary imports the virtual machine to the content library.
 func (vm *VirtualMachineDriver) ImportToContentLibrary(template vcenter.Template) error {
-	err := vm.driver.restClient.Login(vm.driver.ctx)
+	err := vm.driver.RestClient.Login(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -1052,15 +1065,15 @@ func (vm *VirtualMachineDriver) ImportToContentLibrary(template vcenter.Template
 		template.Placement.Host = h.host.Reference().Value
 	}
 
-	vcm := vcenter.NewManager(vm.driver.restClient.client)
-	_, err = vcm.CreateTemplate(vm.driver.ctx, template)
+	vcm := vcenter.NewManager(vm.driver.RestClient.client)
+	_, err = vcm.CreateTemplate(vm.driver.Ctx, template)
 	if err != nil {
 		log.Printf("cannot create template: %v", err)
 		vm.logout()
 		return err
 	}
 
-	return vm.driver.restClient.Logout(vm.driver.ctx)
+	return vm.driver.RestClient.Logout(vm.driver.Ctx)
 }
 
 // GetDir returns the directory of the virtual machine. Returns an error if the
@@ -1089,7 +1102,7 @@ func addNetwork(d *VCenterDriver, devices object.VirtualDeviceList, config *Crea
 			return nil, err
 		}
 
-		backing, err := network.EthernetCardBackingInfo(d.ctx)
+		backing, err := network.EthernetCardBackingInfo(d.Ctx)
 		if err != nil {
 			return nil, err
 		}
@@ -1161,7 +1174,7 @@ func findNetwork(network string, host string, d *VCenterDriver) (object.NetworkR
 			return nil, fmt.Errorf("more than one network found on host %s, specify the network", host)
 		}
 
-		return object.NewNetwork(d.client.Client, i.Network[0]), nil
+		return object.NewNetwork(d.Client.Client, i.Network[0]), nil
 	}
 
 	return nil, fmt.Errorf("error finding network; 'host' and 'network' not specified. at least one of the two must be specified")
@@ -1233,7 +1246,7 @@ func newVGPUProfile(vGPUProfile string) types.VirtualPCIPassthrough {
 // MountCdrom mounts a CD-ROM to the virtual machine.
 func (vm *VirtualMachineDriver) MountCdrom(controllerType string, datastoreIsoPath string, _cdrom types.BaseVirtualDevice) error {
 	cdrom := _cdrom.(*types.VirtualCdrom)
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -1259,7 +1272,7 @@ func (vm *VirtualMachineDriver) MountCdrom(controllerType string, datastoreIsoPa
 
 // AddCdrom adds a CD-ROM to the virtual machine.
 func (vm *VirtualMachineDriver) AddCdrom(controllerType string, datastoreIsoPath string) error {
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -1300,7 +1313,7 @@ func (vm *VirtualMachineDriver) AddCdrom(controllerType string, datastoreIsoPath
 
 // AddFloppy adds a floppy disk to the virtual machine.
 func (vm *VirtualMachineDriver) AddFloppy(imgPath string) error {
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -1319,7 +1332,7 @@ func (vm *VirtualMachineDriver) AddFloppy(imgPath string) error {
 
 // SetBootOrder sets the boot order of the virtual machine.
 func (vm *VirtualMachineDriver) SetBootOrder(order []string) error {
-	devices, err := vm.vm.Device(vm.driver.ctx)
+	devices, err := vm.vm.Device(vm.driver.Ctx)
 	if err != nil {
 		return err
 	}
@@ -1328,12 +1341,12 @@ func (vm *VirtualMachineDriver) SetBootOrder(order []string) error {
 		BootOrder: devices.BootOrder(order),
 	}
 
-	return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions)
+	return vm.vm.SetBootOptions(vm.driver.Ctx, &bootOptions)
 }
 
 // RemoveDevice removes a device from the virtual machine.
 func (vm *VirtualMachineDriver) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
-	return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...)
+	return vm.vm.RemoveDevice(vm.driver.Ctx, keepFiles, device...)
 }
 
 // addDevice adds a device to the virtual machine.
@@ -1346,12 +1359,12 @@ func (vm *VirtualMachineDriver) addDevice(device types.BaseVirtualDevice) error
 		return err
 	}
 
-	task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
+	task, err := vm.vm.Reconfigure(vm.driver.Ctx, confSpec)
 	if err != nil {
 		return err
 	}
 
-	_, err = task.WaitForResult(vm.driver.ctx, nil)
+	_, err = task.WaitForResult(vm.driver.Ctx, nil)
 	return err
 }
 
@@ -1372,12 +1385,12 @@ func (vm *VirtualMachineDriver) AddConfigParams(params map[string]string, info *
 	confSpec.Tools = info
 
 	if len(confSpec.ExtraConfig) > 0 || confSpec.Tools != nil {
-		task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
+		task, err := vm.vm.Reconfigure(vm.driver.Ctx, confSpec)
 		if err != nil {
 			return fmt.Errorf("failed to start reconfiguration task: %w", err)
 		}
 
-		_, err = task.WaitForResult(vm.driver.ctx, nil)
+		_, err = task.WaitForResult(vm.driver.Ctx, nil)
 		if err != nil {
 			return fmt.Errorf("reconfiguration task failed: %w", err)
 		}
@@ -1386,7 +1399,7 @@ func (vm *VirtualMachineDriver) AddConfigParams(params map[string]string, info *
 
 		// Retrieve the current configuration.
 		var moVM mo.VirtualMachine
-		err = vm.vm.Properties(vm.driver.ctx, vm.vm.Reference(), []string{"config.extraConfig"}, &moVM)
+		err = vm.vm.Properties(vm.driver.Ctx, vm.vm.Reference(), []string{"config.extraConfig"}, &moVM)
 		if err != nil {
 			return fmt.Errorf("failed to retrieve current configuration: %w", err)
 		}
@@ -1436,12 +1449,12 @@ func (vm *VirtualMachineDriver) AddFlag(ctx context.Context, flagSpec *types.Vir
 
 // Export exports the virtual machine.
 func (vm *VirtualMachineDriver) Export() (*nfc.Lease, error) {
-	return vm.vm.Export(vm.driver.ctx)
+	return vm.vm.Export(vm.driver.Ctx)
 }
 
 // CreateDescriptor creates a descriptor for the virtual machine used when exporting the virtual machine to an OVF.
 func (vm *VirtualMachineDriver) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
-	return m.CreateDescriptor(vm.driver.ctx, vm.vm, cdp)
+	return m.CreateDescriptor(vm.driver.Ctx, vm.vm, cdp)
 }
 
 // NewOvfManager creates a new OVF manager instance.
@@ -1452,7 +1465,7 @@ func (vm *VirtualMachineDriver) NewOvfManager() *ovf.Manager {
 // GetOvfExportOptions retrieves the OVF export options for the virtual machine.
 func (vm *VirtualMachineDriver) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
 	var mgr mo.OvfManager
-	err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
+	err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.Ctx, m.Reference(), nil, &mgr)
 	if err != nil {
 		return nil, err
 	}
@@ -1481,12 +1494,12 @@ func (vm *VirtualMachineDriver) NewNetwork(ref *types.ManagedObjectReference) *N
 
 // Datacenter returns the datacenter of the virtual machine.
 func (vm *VirtualMachineDriver) Datacenter() *object.Datacenter {
-	return vm.driver.datacenter
+	return vm.driver.Datacenter
 }
 
 // FindContentLibraryItemUUID finds a content library item by name.
 func (vm *VirtualMachineDriver) FindContentLibraryItemUUID(library string, name string) (string, error) {
-	err := vm.driver.restClient.Login(vm.driver.ctx)
+	err := vm.driver.RestClient.Login(vm.driver.Ctx)
 	if err != nil {
 		return "", err
 	}
@@ -1510,7 +1523,7 @@ func (vm *VirtualMachineDriver) FindContentLibraryItemUUID(library string, name
 
 // FindContentLibraryTemplateDatastoreName finds the datastore name of the content library template.
 func (vm *VirtualMachineDriver) FindContentLibraryTemplateDatastoreName(library string) ([]string, error) {
-	err := vm.driver.restClient.Login(vm.driver.ctx)
+	err := vm.driver.RestClient.Login(vm.driver.Ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -1530,15 +1543,15 @@ func (vm *VirtualMachineDriver) FindContentLibraryTemplateDatastoreName(library
 		}
 		datastores = append(datastores, name)
 	}
-	return datastores, vm.driver.restClient.Logout(vm.driver.ctx)
+	return datastores, vm.driver.RestClient.Logout(vm.driver.Ctx)
 }
 
 // logout logs the user out of the vCenter.
 func (vm *VirtualMachineDriver) logout() {
-	if vm.driver.restClient == nil {
+	if vm.driver.RestClient == nil {
 		return
 	}
-	if err := vm.driver.restClient.Logout(vm.driver.ctx); err != nil {
+	if err := vm.driver.RestClient.Logout(vm.driver.Ctx); err != nil {
 		log.Printf("cannot logout: %s ", err)
 	}
 }
diff --git a/builder/vsphere/driver/vm_cdrom.go b/builder/vsphere/driver/vm_cdrom.go
index d034fd6..1aff781 100644
--- a/builder/vsphere/driver/vm_cdrom.go
+++ b/builder/vsphere/driver/vm_cdrom.go
@@ -119,7 +119,7 @@ func (vm *VirtualMachineDriver) EjectCdroms() error {
 		c := cd.(*types.VirtualCdrom)
 		c.Backing = &types.VirtualCdromRemotePassthroughBackingInfo{}
 		c.Connectable = &types.VirtualDeviceConnectInfo{}
-		err := vm.vm.EditDevice(vm.driver.ctx, c)
+		err := vm.vm.EditDevice(vm.driver.Ctx, c)
 		if err != nil {
 			return err
 		}
diff --git a/builder/vsphere/driver/vm_keyboard.go b/builder/vsphere/driver/vm_keyboard.go
index 2b8a9b2..2b264b6 100644
--- a/builder/vsphere/driver/vm_keyboard.go
+++ b/builder/vsphere/driver/vm_keyboard.go
@@ -36,7 +36,7 @@ func (vm *VirtualMachineDriver) TypeOnKeyboard(input KeyInput) (int32, error) {
 		Spec: spec,
 	}
 
-	resp, err := methods.PutUsbScanCodes(vm.driver.ctx, vm.driver.client.RoundTripper, req)
+	resp, err := methods.PutUsbScanCodes(vm.driver.Ctx, vm.driver.Client.RoundTripper, req)
 	if err != nil {
 		return 0, err
 	}
diff --git a/builder/vsphere/iso/builder.go b/builder/vsphere/iso/builder.go
index 4a19285..9202852 100644
--- a/builder/vsphere/iso/builder.go
+++ b/builder/vsphere/iso/builder.go
@@ -84,6 +84,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
 			Config:   &b.config.CreateConfig,
 			Location: &b.config.LocationConfig,
 			Force:    b.config.PackerForce,
+			Firmware: b.config.Firmware,
 		},
 		&common.StepConfigureHardware{
 			Config: &b.config.HardwareConfig,
diff --git a/builder/vsphere/iso/config.go b/builder/vsphere/iso/config.go
index daaef33..e9fb596 100644
--- a/builder/vsphere/iso/config.go
+++ b/builder/vsphere/iso/config.go
@@ -110,6 +110,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
 	errs = packersdk.MultiErrorAppend(errs, c.CreateConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
+	errs = packersdk.MultiErrorAppend(errs, c.ConfigParamsConfig.Prepare()...)
 	errs = packersdk.MultiErrorAppend(errs, c.FlagConfig.Prepare(&c.HardwareConfig)...)
 	errs = packersdk.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
 	errs = packersdk.MultiErrorAppend(errs, c.CDRomConfig.Prepare(&c.ReattachCDRomConfig)...)
diff --git a/builder/vsphere/iso/config.hcl2spec.go b/builder/vsphere/iso/config.hcl2spec.go
index c52cd56..da69b9d 100644
--- a/builder/vsphere/iso/config.hcl2spec.go
+++ b/builder/vsphere/iso/config.hcl2spec.go
@@ -69,6 +69,7 @@ type FlatConfig struct {
 	VirtualPrecisionClock           *string                                     `mapstructure:"precision_clock" cty:"precision_clock" hcl:"precision_clock"`
 	ConfigParams                    map[string]string                           `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
 	ToolsSyncTime                   *bool                                       `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
+	ToolsSyncTimePeriodically       *bool                                       `mapstructure:"tools_sync_time_periodically" cty:"tools_sync_time_periodically" hcl:"tools_sync_time_periodically"`
 	ToolsUpgradePolicy              *bool                                       `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
 	VbsEnabled                      *bool                                       `mapstructure:"vbs_enabled" cty:"vbs_enabled" hcl:"vbs_enabled"`
 	VvtdEnabled                     *bool                                       `mapstructure:"vvtd_enabled" cty:"vvtd_enabled" hcl:"vvtd_enabled"`
@@ -229,6 +230,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
 		"precision_clock":                &hcldec.AttrSpec{Name: "precision_clock", Type: cty.String, Required: false},
 		"configuration_parameters":       &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
 		"tools_sync_time":                &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
+		"tools_sync_time_periodically":   &hcldec.AttrSpec{Name: "tools_sync_time_periodically", Type: cty.Bool, Required: false},
 		"tools_upgrade_policy":           &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
 		"vbs_enabled":                    &hcldec.AttrSpec{Name: "vbs_enabled", Type: cty.Bool, Required: false},
 		"vvtd_enabled":                   &hcldec.AttrSpec{Name: "vvtd_enabled", Type: cty.Bool, Required: false},
diff --git a/builder/vsphere/iso/step_create.go b/builder/vsphere/iso/step_create.go
index 8ad76ac..a5128a6 100644
--- a/builder/vsphere/iso/step_create.go
+++ b/builder/vsphere/iso/step_create.go
@@ -170,6 +170,7 @@ type StepCreateVM struct {
 	Config        *CreateConfig
 	Location      *common.LocationConfig
 	Force         bool
+	Firmware      string
 	GeneratedData *packerbuilderdata.GeneratedData
 }
 
@@ -228,6 +229,7 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
 		NICs:          networkCards,
 		USBController: s.Config.USBController,
 		Version:       s.Config.Version,
+		Firmware:      s.Firmware,
 	})
 	if err != nil {
 		state.Put("error", fmt.Errorf("error creating virtual machine: %v", err))
diff --git a/datasource/virtualmachine/data.go b/datasource/virtualmachine/data.go
new file mode 100644
index 0000000..8e67e12
--- /dev/null
+++ b/datasource/virtualmachine/data.go
@@ -0,0 +1,171 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+//go:generate packer-sdc struct-markdown
+//go:generate packer-sdc mapstructure-to-hcl2 -type Config,Tag,DatasourceOutput
+
+package virtualmachine
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/hashicorp/hcl/v2/hcldec"
+	"github.com/hashicorp/packer-plugin-sdk/common"
+	"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
+	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
+	"github.com/hashicorp/packer-plugin-sdk/template/config"
+	vsphere "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+	"github.com/zclconf/go-cty/cty"
+)
+
+type Tag struct {
+	// Name of the tag added to virtual machine which must pass the `tag`
+	// filter.
+	Name string `mapstructure:"name" required:"true"`
+	// Name of the tag category that contains the tag.
+	//
+	// -> **Note:** Both `name` and `category` must be specified in the `tag`
+	// filter.
+	Category string `mapstructure:"category" required:"true"`
+}
+
+type Config struct {
+	common.PackerConfig   `mapstructure:",squash"`
+	vsphere.ConnectConfig `mapstructure:",squash"`
+
+	// Basic filter with glob support (e.g. `ubuntu_basic*`). Defaults to `*`.
+	// Using strict globs will not reduce execution time because vSphere API
+	// returns the full inventory. But can be used for better readability over
+	// regular expressions.
+	Name string `mapstructure:"name"`
+	// Extended name filter with regular expressions support
+	// (e.g. `ubuntu[-_]basic[0-9]*`). Default is empty. The match of the
+	// regular expression is checked by substring. Use `^` and `$` to define a
+	// full string. For example, the `^[^_]+$` filter will search names
+	// without any underscores. The expression must use
+	// [Go Regex Syntax](https://pkg.go.dev/regexp/syntax).
+	NameRegex string `mapstructure:"name_regex"`
+	// Filter to return only objects that are virtual machine templates.
+	// Defaults to `false` and returns all virtual machines.
+	Template bool `mapstructure:"template"`
+	// Filter to search virtual machines only on the specified ESX host.
+	Host string `mapstructure:"host"`
+	// Filter to return only the virtual machines that have attached all specified tags.
+	// Specify one or more `tag` blocks to define list of tags for the filter.
+	//
+	// HCL Example:
+	//
+	// ```hcl
+	//	tag {
+	//	  category = "team"
+	//	  name     = "operations"
+	//	}
+	//	tag {
+	//	  category = "sla"
+	//	  name     = "gold"
+	//	}
+	// ```
+	Tags []Tag `mapstructure:"tag"`
+	// This filter determines how to handle multiple virtual machines that were matched
+	// with all previous filters. Virtual machine creation time is being used to find
+	// the latest. By default, multiple matching machines results in an error.
+	Latest bool `mapstructure:"latest"`
+}
+
+type Datasource struct {
+	config Config
+}
+
+type DatasourceOutput struct {
+	// Name of the found virtual machine.
+	VmName string `mapstructure:"vm_name"`
+}
+
+func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
+	return d.config.FlatMapstructure().HCL2Spec()
+}
+
+func (d *Datasource) Configure(raws ...interface{}) error {
+	err := config.Decode(&d.config, nil, raws...)
+	if err != nil {
+		return err
+	}
+
+	if d.config.Name == "" {
+		d.config.Name = "*"
+	}
+
+	var errs error
+	if d.config.VCenterServer == "" {
+		errs = packersdk.MultiErrorAppend(errs, errors.New("'vcenter_server' is required"))
+	}
+	if d.config.Username == "" {
+		errs = packersdk.MultiErrorAppend(errs, errors.New("'username' is required"))
+	}
+	if d.config.Password == "" {
+		errs = packersdk.MultiErrorAppend(errs, errors.New("'password' is required"))
+	}
+	if len(d.config.Tags) > 0 {
+		for _, tag := range d.config.Tags {
+			if tag.Name == "" || tag.Category == "" {
+				errs = packersdk.MultiErrorAppend(errs, errors.New("both name and category are required for tag"))
+			}
+		}
+	}
+
+	return errs
+}
+
+func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
+	return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
+}
+
+func (d *Datasource) Execute() (cty.Value, error) {
+	driverConfig := &driver.ConnectConfig{
+		VCenterServer:      d.config.VCenterServer,
+		Username:           d.config.Username,
+		Password:           d.config.Password,
+		InsecureConnection: d.config.InsecureConnection,
+		Datacenter:         d.config.Datacenter,
+	}
+
+	dr, err := driver.NewDriver(driverConfig)
+	if err != nil {
+		return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to initialize driver: %w", err)
+	}
+
+	vcDriver := dr.(*driver.VCenterDriver)
+
+	vmList, err := vcDriver.Finder.VirtualMachineList(vcDriver.Ctx, d.config.Name)
+	if err != nil {
+		return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to retrieve virtual machines list: %w", err)
+	}
+
+	filteredVms, err := filterVms(vmList, d.config, vcDriver)
+	if err != nil {
+		return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to filter virtual machines: %w", err)
+	}
+
+	if len(filteredVms) == 0 {
+		return cty.NullVal(cty.EmptyObject), errors.New("no virtual machine matches the filters")
+	}
+
+	if len(filteredVms) > 1 {
+		if d.config.Latest {
+			filteredVms, err = findLatestVM(vcDriver, filteredVms)
+			if err != nil {
+				return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to find the latest virtual machine: %w", err)
+			}
+		} else {
+			return cty.NullVal(cty.EmptyObject), errors.New("more than one virtual machine matched the filters")
+		}
+	}
+
+	output := DatasourceOutput{
+		VmName: filteredVms[0].Name(),
+	}
+
+	return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
+}
diff --git a/datasource/virtualmachine/data.hcl2spec.go b/datasource/virtualmachine/data.hcl2spec.go
new file mode 100644
index 0000000..31aa2da
--- /dev/null
+++ b/datasource/virtualmachine/data.hcl2spec.go
@@ -0,0 +1,115 @@
+// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
+
+package virtualmachine
+
+import (
+	"github.com/hashicorp/hcl/v2/hcldec"
+	"github.com/zclconf/go-cty/cty"
+)
+
+// FlatConfig is an auto-generated flat version of Config.
+// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
+type FlatConfig struct {
+	PackerBuildName     *string           `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
+	PackerBuilderType   *string           `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
+	PackerCoreVersion   *string           `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
+	PackerDebug         *bool             `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
+	PackerForce         *bool             `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
+	PackerOnError       *string           `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
+	PackerUserVars      map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
+	PackerSensitiveVars []string          `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
+	VCenterServer       *string           `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
+	Username            *string           `mapstructure:"username" cty:"username" hcl:"username"`
+	Password            *string           `mapstructure:"password" cty:"password" hcl:"password"`
+	InsecureConnection  *bool             `mapstructure:"insecure_connection" cty:"insecure_connection" hcl:"insecure_connection"`
+	Datacenter          *string           `mapstructure:"datacenter" cty:"datacenter" hcl:"datacenter"`
+	Name                *string           `mapstructure:"name" cty:"name" hcl:"name"`
+	NameRegex           *string           `mapstructure:"name_regex" cty:"name_regex" hcl:"name_regex"`
+	Template            *bool             `mapstructure:"template" cty:"template" hcl:"template"`
+	Host                *string           `mapstructure:"host" cty:"host" hcl:"host"`
+	Tags                []FlatTag         `mapstructure:"tag" cty:"tag" hcl:"tag"`
+	Latest              *bool             `mapstructure:"latest" cty:"latest" hcl:"latest"`
+}
+
+// FlatMapstructure returns a new FlatConfig.
+// FlatConfig is an auto-generated flat version of Config.
+// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
+func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
+	return new(FlatConfig)
+}
+
+// HCL2Spec returns the hcl spec of a Config.
+// This spec is used by HCL to read the fields of Config.
+// The decoded values from this spec will then be applied to a FlatConfig.
+func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
+	s := map[string]hcldec.Spec{
+		"packer_build_name":          &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
+		"packer_builder_type":        &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
+		"packer_core_version":        &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
+		"packer_debug":               &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
+		"packer_force":               &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
+		"packer_on_error":            &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
+		"packer_user_variables":      &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
+		"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
+		"vcenter_server":             &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
+		"username":                   &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
+		"password":                   &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
+		"insecure_connection":        &hcldec.AttrSpec{Name: "insecure_connection", Type: cty.Bool, Required: false},
+		"datacenter":                 &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false},
+		"name":                       &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
+		"name_regex":                 &hcldec.AttrSpec{Name: "name_regex", Type: cty.String, Required: false},
+		"template":                   &hcldec.AttrSpec{Name: "template", Type: cty.Bool, Required: false},
+		"host":                       &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
+		"tag":                        &hcldec.BlockListSpec{TypeName: "tag", Nested: hcldec.ObjectSpec((*FlatTag)(nil).HCL2Spec())},
+		"latest":                     &hcldec.AttrSpec{Name: "latest", Type: cty.Bool, Required: false},
+	}
+	return s
+}
+
+// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput.
+// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
+type FlatDatasourceOutput struct {
+	VmName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
+}
+
+// FlatMapstructure returns a new FlatDatasourceOutput.
+// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput.
+// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
+func (*DatasourceOutput) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
+	return new(FlatDatasourceOutput)
+}
+
+// HCL2Spec returns the hcl spec of a DatasourceOutput.
+// This spec is used by HCL to read the fields of DatasourceOutput.
+// The decoded values from this spec will then be applied to a FlatDatasourceOutput.
+func (*FlatDatasourceOutput) HCL2Spec() map[string]hcldec.Spec {
+	s := map[string]hcldec.Spec{
+		"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+	}
+	return s
+}
+
+// FlatTag is an auto-generated flat version of Tag.
+// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
+type FlatTag struct {
+	Name     *string `mapstructure:"name" required:"true" cty:"name" hcl:"name"`
+	Category *string `mapstructure:"category" required:"true" cty:"category" hcl:"category"`
+}
+
+// FlatMapstructure returns a new FlatTag.
+// FlatTag is an auto-generated flat version of Tag.
+// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
+func (*Tag) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
+	return new(FlatTag)
+}
+
+// HCL2Spec returns the hcl spec of a Tag.
+// This spec is used by HCL to read the fields of Tag.
+// The decoded values from this spec will then be applied to a FlatTag.
+func (*FlatTag) HCL2Spec() map[string]hcldec.Spec {
+	s := map[string]hcldec.Spec{
+		"name":     &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
+		"category": &hcldec.AttrSpec{Name: "category", Type: cty.String, Required: false},
+	}
+	return s
+}
diff --git a/datasource/virtualmachine/filters.go b/datasource/virtualmachine/filters.go
new file mode 100644
index 0000000..e588473
--- /dev/null
+++ b/datasource/virtualmachine/filters.go
@@ -0,0 +1,165 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package virtualmachine
+
+import (
+	"fmt"
+	"regexp"
+	"time"
+
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/property"
+	"github.com/vmware/govmomi/vapi/tags"
+	"github.com/vmware/govmomi/vim25/mo"
+)
+
+// filterVms removes virtual machines from vmList that do not match the datasource config filters.
+func filterVms(vmList []*object.VirtualMachine, c Config, d *driver.VCenterDriver) ([]*object.VirtualMachine, error) {
+	filterFuncs := make([]func(*object.VirtualMachine) (bool, error), 0)
+
+	if c.NameRegex != "" {
+		re := regexp.MustCompile(c.NameRegex)
+		filterFuncs = append(filterFuncs, func(vm *object.VirtualMachine) (bool, error) {
+			return re.MatchString(vm.Name()), nil
+		})
+	}
+
+	if c.Template {
+		filterFuncs = append(filterFuncs, func(vm *object.VirtualMachine) (bool, error) {
+			isTemplate, err := vm.IsTemplate(d.Ctx)
+			if err != nil {
+				return false, fmt.Errorf("error checking if virtual machine is a template: %w", err)
+			}
+			return isTemplate, nil
+		})
+	}
+
+	if c.Host != "" {
+		hostVms, err := getHostVms(d, c.Host)
+		if err != nil {
+			return nil, err
+		}
+
+		filterFuncs = append(filterFuncs, func(vm *object.VirtualMachine) (bool, error) {
+			vmName := vm.Name()
+			for _, hostVm := range hostVms {
+				if vmName == hostVm.Name {
+					return true, nil
+				}
+			}
+			return false, nil
+		})
+	}
+
+	if c.Tags != nil {
+		filterFuncs = append(filterFuncs, func(vm *object.VirtualMachine) (bool, error) {
+			result, err := configTagsMatchHostTags(d, vm, c.Tags)
+			if err != nil {
+				return false, err
+			}
+			return result, nil
+		})
+	}
+
+	result := make([]*object.VirtualMachine, 0)
+	for _, vm := range vmList {
+		var ok bool
+		var err error
+		if len(filterFuncs) == 0 {
+			ok = true
+		}
+		for _, vmPassedFilter := range filterFuncs {
+			ok, err = vmPassedFilter(vm)
+			if err != nil {
+				return nil, fmt.Errorf("failed to filter vm: %w", err)
+			}
+			if !ok {
+				break
+			}
+		}
+		if ok {
+			result = append(result, vm)
+		}
+	}
+
+	return result, nil
+}
+
+// findLatestVM returns the most recently created virtual machine from vmList.
+func findLatestVM(d *driver.VCenterDriver, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) {
+	var latestVM *object.VirtualMachine
+	var latestTimestamp time.Time
+	for _, elementVM := range vmList {
+		var vmConfig mo.VirtualMachine
+		err := elementVM.Properties(d.Ctx, elementVM.Reference(), []string{"config"}, &vmConfig)
+		if err != nil {
+			return nil, fmt.Errorf("error retrieving config properties for the virtual machine: %w", err)
+		}
+		if vmConfig.Config.CreateDate.After(latestTimestamp) {
+			latestVM = elementVM
+			latestTimestamp = *vmConfig.Config.CreateDate
+		}
+	}
+	result := []*object.VirtualMachine{latestVM}
+	return result, nil
+}
+
+// getHostVms retrieves all virtual machines on the specified host.
+func getHostVms(d *driver.VCenterDriver, hostName string) ([]mo.VirtualMachine, error) {
+	pc := property.DefaultCollector(d.Client.Client)
+	obj, err := d.Finder.HostSystem(d.Ctx, hostName)
+	if err != nil {
+		return nil, fmt.Errorf("error finding defined host system: %w", err)
+	}
+
+	var host mo.HostSystem
+	err = pc.RetrieveOne(d.Ctx, obj.Reference(), []string{"vm"}, &host)
+	if err != nil {
+		return nil, fmt.Errorf("error retrieving properties of host system: %w", err)
+	}
+
+	var hostVms []mo.VirtualMachine
+	err = pc.Retrieve(d.Ctx, host.Vm, []string{"name"}, &hostVms)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get properties for the virtual machine: %w", err)
+	}
+	return hostVms, nil
+}
+
+// configTagsMatchHostTags returns true if the virtual machine has all tags specified in tagList.
+func configTagsMatchHostTags(d *driver.VCenterDriver, vm *object.VirtualMachine, tagList []Tag) (bool, error) {
+	err := d.RestClient.Login(d.Ctx)
+	if err != nil {
+		return false, fmt.Errorf("failed to login to REST API: %w", err)
+	}
+
+	tagMan := tags.NewManager(d.RestClient.Client())
+	realTagsList, err := tagMan.GetAttachedTags(d.Ctx, vm.Reference())
+	if err != nil {
+		return false, fmt.Errorf("failed return tags for the virtual machine: %w", err)
+	}
+	matchedTagsCount := 0
+	for _, configTag := range tagList {
+		configTagMatched := false
+		for _, realTag := range realTagsList {
+			if configTag.Name == realTag.Name {
+				category, err := tagMan.GetCategory(d.Ctx, realTag.CategoryID)
+				if err != nil {
+					return false, fmt.Errorf("failed to return tag category for tag: %w", err)
+				}
+				if configTag.Category == category.Name {
+					configTagMatched = true
+					break
+				}
+			}
+		}
+		if configTagMatched {
+			matchedTagsCount++
+		} else {
+			break
+		}
+	}
+	return matchedTagsCount == len(tagList), nil
+}
diff --git a/docs-partials/builder/vsphere/common/ConfigParamsConfig-not-required.mdx b/docs-partials/builder/vsphere/common/ConfigParamsConfig-not-required.mdx
index b2fcd9d..17c8c10 100644
--- a/docs-partials/builder/vsphere/common/ConfigParamsConfig-not-required.mdx
+++ b/docs-partials/builder/vsphere/common/ConfigParamsConfig-not-required.mdx
@@ -28,8 +28,22 @@
   are silently ignored. Refer to the [`VirtualMachineConfigSpec`](https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.ConfigSpec.html)
   in the vSphere API documentation.
 
-- `tools_sync_time` (bool) - Enable time synchronization with the ESX host where the virtual machine
-  is running. Defaults to `false`.
+- `tools_sync_time` (\*bool) - Enable or disable time synchronization between the guest operating system and the
+  ESX host at startup and after VM operations that may introduce time drift (such
+  as resume from suspend, vMotion, or snapshot restore). If set to `true`, time
+  synchronization is explicitly enabled. If set to `false`, time synchronization is
+  explicitly disabled. If omitted, the builder does not modify the virtual
+  machine's time synchronization settings:
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`true`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
+
+- `tools_sync_time_periodically` (\*bool) - Enable or disable periodic time synchronization between the guest operating
+  system and the ESX host. Use this setting only if the guest operating system does
+  not have native time synchronization.
+    - `vsphere-iso` builder uses the vSphere default for new virtual machines
+       (`false`).
+    - `vsphere-clone` builder inherits the setting from the source virtual machine.
 
 - `tools_upgrade_policy` (bool) - Automatically check for and upgrade VMware Tools after a virtual machine
   power cycle. Defaults to `false`.
diff --git a/docs-partials/datasource/virtualmachine/Config-not-required.mdx b/docs-partials/datasource/virtualmachine/Config-not-required.mdx
new file mode 100644
index 0000000..61cc59d
--- /dev/null
+++ b/docs-partials/datasource/virtualmachine/Config-not-required.mdx
@@ -0,0 +1,40 @@
+<!-- Code generated from the comments of the Config struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `name` (string) - Basic filter with glob support (e.g. `ubuntu_basic*`). Defaults to `*`.
+  Using strict globs will not reduce execution time because vSphere API
+  returns the full inventory. But can be used for better readability over
+  regular expressions.
+
+- `name_regex` (string) - Extended name filter with regular expressions support
+  (e.g. `ubuntu[-_]basic[0-9]*`). Default is empty. The match of the
+  regular expression is checked by substring. Use `^` and `$` to define a
+  full string. For example, the `^[^_]+$` filter will search names
+  without any underscores. The expression must use
+  [Go Regex Syntax](https://pkg.go.dev/regexp/syntax).
+
+- `template` (bool) - Filter to return only objects that are virtual machine templates.
+  Defaults to `false` and returns all virtual machines.
+
+- `host` (string) - Filter to search virtual machines only on the specified ESX host.
+
+- `tag` ([]Tag) - Filter to return only the virtual machines that have attached all specified tags.
+  Specify one or more `tag` blocks to define list of tags for the filter.
+  
+  HCL Example:
+  
+  ```hcl
+  	tag {
+  	  category = "team"
+  	  name     = "operations"
+  	}
+  	tag {
+  	  category = "sla"
+  	  name     = "gold"
+  	}
+  ```
+
+- `latest` (bool) - This filter determines how to handle multiple virtual machines that were matched
+  with all previous filters. Virtual machine creation time is being used to find
+  the latest. By default, multiple matching machines results in an error.
+
+<!-- End of code generated from the comments of the Config struct in datasource/virtualmachine/data.go; -->
diff --git a/docs-partials/datasource/virtualmachine/DatasourceOutput.mdx b/docs-partials/datasource/virtualmachine/DatasourceOutput.mdx
new file mode 100644
index 0000000..ff30024
--- /dev/null
+++ b/docs-partials/datasource/virtualmachine/DatasourceOutput.mdx
@@ -0,0 +1,5 @@
+<!-- Code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `vm_name` (string) - Name of the found virtual machine.
+
+<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; -->
diff --git a/docs-partials/datasource/virtualmachine/Tag-required.mdx b/docs-partials/datasource/virtualmachine/Tag-required.mdx
new file mode 100644
index 0000000..0b9d544
--- /dev/null
+++ b/docs-partials/datasource/virtualmachine/Tag-required.mdx
@@ -0,0 +1,11 @@
+<!-- Code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->
+
+- `name` (string) - Name of the tag added to virtual machine which must pass the `tag`
+  filter.
+
+- `category` (string) - Name of the tag category that contains the tag.
+  
+  -> **Note:** Both `name` and `category` must be specified in the `tag`
+  filter.
+
+<!-- End of code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; -->
diff --git a/docs/README.md b/docs/README.md
index 626fb9e..deb9ac7 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -49,6 +49,11 @@ packer plugins install github.com/hashicorp/vsphere
   This builder deploys and publishes new virtual machine to a vSphere Supervisor cluster using VM
   Service.
 
+#### Data Sources
+
+- [vsphere-virtualmachine](/packer/integrations/hashicorp/vsphere/latest/components/data-source/vsphere-virtualmachine) -
+  This data source returns the name of a virtual machine that matches all defined filters.
+
 #### Post-Processors
 
 - [vsphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) -
diff --git a/docs/datasources/virtualmachine.mdx b/docs/datasources/virtualmachine.mdx
new file mode 100644
index 0000000..db7d9ff
--- /dev/null
+++ b/docs/datasources/virtualmachine.mdx
@@ -0,0 +1,171 @@
+---
+modeline: |
+  vim: set ft=pandoc:
+description: |
+  This data source retrieves information about existing virtual machines from vSphere
+  and returns the name of a virtual machine that matches all specified filters. This
+  virtual machine can be used in the vSphere Clone builder to select a template.
+page_title: vSphere Virtual Machine - Data Source
+sidebar_title: vSphere Virtual Machine
+---
+
+# Virtual Machine Data Source
+
+Type: `vsphere-virtualmachine`
+Artifact BuilderId: `vsphere.virtualmachine`
+
+This data source retrieves information about existing virtual machines from vSphere
+and returns the name of a virtual machine that matches all specified filters. This
+virtual machine can be used in the vSphere Clone builder to select a template.
+
+## Configuration Reference
+
+### Filters Configuration
+
+**Optional:**
+
+@include 'datasource/virtualmachine/Config-not-required.mdx'
+
+### Tags Filter Configuration
+
+**Required:**
+
+@include 'datasource/virtualmachine/Tag-required.mdx'
+
+### Connection Configuration
+
+**Optional:**
+
+@include 'builder/vsphere/common/ConnectConfig-not-required.mdx'
+
+## Output
+
+@include 'datasource/virtualmachine/DatasourceOutput.mdx'
+
+## Example Usage
+
+### Filter by Tags and Get Latest
+
+This example demonstrates how to connect to vSphere cluster and search for the latest
+virtual machine that matches the specified tag filters.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  latest         = true
+
+  tag {
+    category = "team"
+    name     = "operations"
+  }
+
+  tag {
+    category = "sla"
+    name     = "gold"
+  }
+}
+
+locals {
+  vm_name = data.vsphere-virtualmachine.example.vm_name
+}
+
+source "null" "example" {
+    communicator = "none"
+}
+
+build {
+  sources = [
+    "source.null.example"
+  ]
+
+  provisioner "shell-local" {
+    inline = [
+      "echo vm_name: ${local.vm_name}",
+    ]
+  }
+}
+```
+
+### Filter by Name with Glob Pattern
+
+This example shows how to use the `name` filter with glob patterns to find virtual
+machines matching a specific naming convention.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name           = "linux-debian-13-*"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter by Name with Regular Expression
+
+This example demonstrates using the `name_regex` filter for more complex pattern
+matching with regular expressions.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name_regex     = "^linux-debian-[0-9]+-.+$"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter for VM Templates Only
+
+This example shows how to use the `template` filter to search only for virtual
+machine templates.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  name           = "linux-debian-13-*"
+  template       = true
+}
+
+locals {
+  result_template = data.vsphere-virtualmachine.example.vm_name
+}
+```
+
+### Filter by ESX Host
+
+This example demonstrates filtering virtual machines by a specific ESX host,
+combined with other filters.
+
+```hcl
+data "vsphere-virtualmachine" "example" {
+  vcenter_server = "vcenter.example.com"
+  username       = "administrator@vsphere.local"
+  password       = "VMware1!"
+  datacenter     = "dc-01"
+  host           = "esxi-01.example.com"
+  name           = "linux-debian-13-*"
+  latest         = true
+}
+
+locals {
+  vm_result = data.vsphere-virtualmachine.example.vm_name
+}
+```
\ No newline at end of file
diff --git a/main.go b/main.go
index 19eb19e..43a1b96 100644
--- a/main.go
+++ b/main.go
@@ -10,6 +10,7 @@ import (
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/clone"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/iso"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/supervisor"
+	"github.com/hashicorp/packer-plugin-vsphere/datasource/virtualmachine"
 	"github.com/hashicorp/packer-plugin-vsphere/post-processor/vsphere"
 	vsphereTemplate "github.com/hashicorp/packer-plugin-vsphere/post-processor/vsphere-template"
 	"github.com/hashicorp/packer-plugin-vsphere/version"
@@ -20,6 +21,7 @@ func main() {
 	pps.RegisterBuilder("iso", new(iso.Builder))
 	pps.RegisterBuilder("clone", new(clone.Builder))
 	pps.RegisterBuilder("supervisor", new(supervisor.Builder))
+	pps.RegisterDatasource("virtualmachine", new(virtualmachine.Datasource))
 	pps.RegisterPostProcessor(plugin.DEFAULT_NAME, new(vsphere.PostProcessor))
 	pps.RegisterPostProcessor("template", new(vsphereTemplate.PostProcessor))
 	pps.SetVersion(version.PluginVersion)



Test patch:
diff --git a/builder/vsphere/driver/driver_test.go b/builder/vsphere/driver/driver_test.go
index 109b6f1..b578d84 100644
--- a/builder/vsphere/driver/driver_test.go
+++ b/builder/vsphere/driver/driver_test.go
@@ -152,15 +152,15 @@ func (s *VCenterSimulator) NewSimulatorDriver() (*VCenterDriver, error) {
 	finder.SetDatacenter(datacenter)
 
 	d := &VCenterDriver{
-		ctx:       ctx,
-		client:    client,
-		vimClient: vimClient,
-		restClient: &RestClient{
+		Ctx:       ctx,
+		Client:    client,
+		VimClient: vimClient,
+		RestClient: &RestClient{
 			client:      rest.NewClient(vimClient),
 			credentials: user,
 		},
-		datacenter: datacenter,
-		finder:     finder,
+		Datacenter: datacenter,
+		Finder:     finder,
 	}
 	return d, nil
 }
diff --git a/builder/vsphere/iso/builder_acc_test.go b/builder/vsphere/iso/builder_acc_test.go
index bd34c19..5ad807e 100644
--- a/builder/vsphere/iso/builder_acc_test.go
+++ b/builder/vsphere/iso/builder_acc_test.go
@@ -10,8 +10,8 @@ import (
 	"testing"
 
 	"github.com/hashicorp/packer-plugin-sdk/acctest"
-	commonT "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common/testing"
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common/utils"
+	"github.com/hashicorp/packer-plugin-vsphere/testing/vsphere"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -20,13 +20,13 @@ func TestAccISOBuilderAcc_default(t *testing.T) {
 	config := defaultConfig()
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_basic_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -57,7 +57,7 @@ func defaultConfig() map[string]interface{} {
 		"ssh_username": "packer",
 		"ssh_password": "VMw@re1!",
 
-		"vm_name": commonT.NewVMName(),
+		"vm_name": vsphere.NewVmName(),
 		"storage": map[string]interface{}{
 			"disk_size": 2048,
 		},
@@ -70,7 +70,7 @@ func defaultConfig() map[string]interface{} {
 
 // checkDefault verifies the configuration of a virtual machine by comparing its properties against expected values.
 func checkDefault(name string, host string, datastore string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -139,13 +139,13 @@ func TestAccISOBuilderAcc_notes(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_notes_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -161,7 +161,7 @@ func TestAccISOBuilderAcc_notes(t *testing.T) {
 
 // checkNotes verifies a virtual machine has the "config.annotation" field set.
 func checkNotes(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -197,13 +197,13 @@ func TestAccISOBuilderAcc_hardware(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_hardware_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -219,7 +219,7 @@ func TestAccISOBuilderAcc_hardware(t *testing.T) {
 
 // checkHardware verifies a virtual machine hardware configuration.
 func checkHardware(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -303,13 +303,13 @@ func TestAccISOBuilderAcc_limit(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_limit_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -325,7 +325,7 @@ func TestAccISOBuilderAcc_limit(t *testing.T) {
 
 // checkLimit verifies a virtual machine unlimited CPU allocation limit.
 func checkLimit(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -353,13 +353,13 @@ func TestAccISOBuilderAcc_sata(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_sata_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -375,7 +375,7 @@ func TestAccISOBuilderAcc_sata(t *testing.T) {
 
 // checkSata verifies a virtual machine SATA controller configuration.
 func checkSata(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -406,13 +406,13 @@ func TestAccISOBuilderAcc_cdrom(t *testing.T) {
 	}
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_cdrom_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -434,13 +434,13 @@ func TestAccISOBuilderAcc_networkCard(t *testing.T) {
 	}
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_networkCard_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -456,7 +456,7 @@ func TestAccISOBuilderAcc_networkCard(t *testing.T) {
 
 // checkNetworkCard verifies a virtual machine network card configuration.
 func checkNetworkCard(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -501,13 +501,13 @@ func TestAccISOBuilderAcc_createFloppy(t *testing.T) {
 	config["floppy_files"] = []string{tmpFile.Name()}
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_createFloppy_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("unexpected error: expected 'nil', but returned '%s'", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -526,13 +526,13 @@ func TestAccISOBuilderAcc_full(t *testing.T) {
 	config := fullConfig()
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_full_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -560,7 +560,7 @@ func fullConfig() map[string]interface{} {
 		"host":                host,
 		"insecure_connection": true,
 
-		"vm_name": commonT.NewVMName(),
+		"vm_name": vsphere.NewVmName(),
 
 		"RAM": 512,
 		"disk_controller_type": []string{
@@ -611,7 +611,7 @@ func fullConfig() map[string]interface{} {
 
 // checkFull verifies a virtual machine full configuration.
 func checkFull(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -651,13 +651,13 @@ func TestAccISOBuilderAcc_bootOrder(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_bootOrder_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -673,7 +673,7 @@ func TestAccISOBuilderAcc_bootOrder(t *testing.T) {
 
 // checkBootOrder verifies a virtual machine boot order configuration.
 func checkBootOrder(name string) error {
-	d, err := commonT.TestConn()
+	d, err := vsphere.TestConn()
 	if err != nil {
 		return fmt.Errorf("cannot connect %v", err)
 	}
@@ -702,13 +702,13 @@ func TestISOBuilderAcc_cluster(t *testing.T) {
 	config["host"] = "esxi-02.example.com"
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_bootOrder_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
@@ -734,13 +734,13 @@ func TestISOBuilderAcc_clusterDRS(t *testing.T) {
 
 	testCase := &acctest.PluginTestCase{
 		Name:     "vsphere-iso_bootOrder_test",
-		Template: commonT.RenderConfig("vsphere-iso", config),
+		Template: vsphere.RenderConfig("vsphere-iso", config),
 		Teardown: func() error {
-			d, err := commonT.TestConn()
+			d, err := vsphere.TestConn()
 			if err != nil {
 				return fmt.Errorf("cannot connect %v", err)
 			}
-			return commonT.CleanupVM(d, config["vm_name"].(string))
+			return vsphere.CleanupVm(d, config["vm_name"].(string))
 		},
 		Check: func(buildCommand *exec.Cmd, logfile string) error {
 			if buildCommand.ProcessState != nil {
diff --git a/datasource/virtualmachine/data_test.go b/datasource/virtualmachine/data_test.go
new file mode 100644
index 0000000..e04519b
--- /dev/null
+++ b/datasource/virtualmachine/data_test.go
@@ -0,0 +1,212 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package virtualmachine
+
+import (
+	"testing"
+	"time"
+
+	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
+	"github.com/hashicorp/packer-plugin-vsphere/testing/vsphere"
+	"github.com/vmware/govmomi/simulator"
+)
+
+func TestDatasource_Execute(t *testing.T) {
+	machinesToPrepare := []vsphere.SimulatedVmConfig{
+		{
+			Name: "first-vm",
+			Tags: []vsphere.Tag{
+				{
+					Category: "operating-system-class",
+					Name:     "Linux",
+				},
+			},
+		}, {
+			Name: "second-vm",
+			Tags: []vsphere.Tag{
+				{
+					Category: "operating-system-class",
+					Name:     "Linux",
+				},
+				{
+					Category: "security-team",
+					Name:     "red",
+				},
+				{
+					Category: "security-team",
+					Name:     "blue",
+				},
+			},
+			Template: true,
+		}, {
+			Name: "machine-three",
+			Tags: []vsphere.Tag{
+				{
+					Category: "operating-system-class",
+					Name:     "Linux",
+				},
+				{
+					Category: "security-team",
+					Name:     "blue",
+				},
+			},
+			CreationTime: time.Now().AddDate(0, 0, 1),
+		},
+	}
+
+	model := simulator.VPX()
+	model.Datacenter = 2
+	model.Machine = 8
+
+	vcSim, err := vsphere.NewSimulator(model)
+	if err != nil {
+		t.Fatalf("error creating vCenter simulator: %s", err)
+	}
+	defer vcSim.Stop()
+
+	err = vcSim.ApplyVmConfiguration(machinesToPrepare)
+	if err != nil {
+		t.Fatalf("error customizing simulator: %s", err)
+	}
+
+	simulatorPassword, _ := vcSim.Server.URL.User.Password()
+	connectConfig := common.ConnectConfig{
+		VCenterServer:      vcSim.Server.URL.Host,
+		Username:           vcSim.Server.URL.User.Username(),
+		Password:           simulatorPassword,
+		InsecureConnection: true,
+		Datacenter:         vcSim.Datacenter.Name(),
+	}
+
+	dsTestConfigs := []struct {
+		name          string
+		expectFailure bool
+		expectVmName  string
+		config        Config
+	}{
+		{
+			name:          "first-vm was found by name, no error",
+			expectFailure: false,
+			expectVmName:  "first-vm",
+			config: Config{
+				Name: "first-vm",
+			},
+		},
+		{
+			name:          "no machines match the filter, error",
+			expectFailure: true,
+			expectVmName:  "",
+			config: Config{
+				Name: "firstest-vm",
+			},
+		},
+		{
+			name:          "second-vm was found by the regex, no error",
+			expectFailure: false,
+			expectVmName:  "second-vm",
+			config: Config{
+				NameRegex: "^seco.*m$",
+			},
+		},
+		{
+			name:          "multiple machines match the regex, but latest not used, error",
+			expectFailure: true,
+			expectVmName:  "",
+			config: Config{
+				NameRegex: ".*-vm",
+			},
+		},
+		{
+			name:          "multiple guests match the regex and latest used, no error",
+			expectFailure: false,
+			expectVmName:  "machine-three",
+			config: Config{
+				NameRegex: "^[^_]+$",
+				Latest:    true,
+			},
+		},
+		{
+			name:          "found machine that is a template, no error",
+			expectFailure: false,
+			expectVmName:  "second-vm",
+			config: Config{
+				Template: true,
+			},
+		},
+		{
+			name:          "found multiple machines at the host, error",
+			expectFailure: true,
+			expectVmName:  "",
+			config: Config{
+				Host: "DC0_H0",
+			},
+		},
+		{
+			name:          "cluster host not found, error",
+			expectFailure: true,
+			expectVmName:  "",
+			config: Config{
+				Host: "unexpected_host",
+			},
+		},
+		{
+			name:          "found machine with defined set of tags, no error",
+			expectFailure: false,
+			expectVmName:  "second-vm",
+			config: Config{
+				Tags: []Tag{
+					{
+						Category: "security-team",
+						Name:     "blue",
+					},
+					{
+						Category: "security-team",
+						Name:     "red",
+					},
+				},
+			},
+		},
+		{
+			name:          "found multiple machines with defined set of tags, error",
+			expectFailure: true,
+			expectVmName:  "",
+			config: Config{
+				Tags: []Tag{
+					{
+						Category: "operating-system-class",
+						Name:     "Linux",
+					},
+				},
+			},
+		},
+	}
+
+	for _, testConfig := range dsTestConfigs {
+		t.Run(testConfig.name, func(t *testing.T) {
+			testConfig.config.ConnectConfig = connectConfig
+
+			ds := Datasource{
+				config: testConfig.config,
+			}
+			err := ds.Configure()
+			if err != nil {
+				t.Fatalf("Failed to configure datasource: %s", err)
+			}
+
+			result, err := ds.Execute()
+			if err != nil && !testConfig.expectFailure {
+				t.Fatalf("unexpected failure: %s", err)
+			}
+			if err == nil && testConfig.expectFailure {
+				t.Errorf("expected failure, but execution succeeded")
+			}
+			if err == nil {
+				vmName := result.GetAttr("vm_name").AsString()
+				if vmName != testConfig.expectVmName {
+					t.Errorf("expected vm name `%s`, but got `%s`", testConfig.expectVmName, vmName)
+				}
+			}
+		})
+	}
+}
diff --git a/builder/vsphere/common/testing/utility.go b/testing/vsphere/acceptance.go
similarity index 88%
rename from builder/vsphere/common/testing/utility.go
rename to testing/vsphere/acceptance.go
index a9bde19..dda908c 100644
--- a/builder/vsphere/common/testing/utility.go
+++ b/testing/vsphere/acceptance.go
@@ -1,7 +1,7 @@
 // Copyright (c) HashiCorp, Inc.
 // SPDX-License-Identifier: MPL-2.0
 
-package testing
+package vsphere
 
 import (
 	"encoding/json"
@@ -13,7 +13,7 @@ import (
 	"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
 )
 
-func NewVMName() string {
+func NewVmName() string {
 	r := rand.New(rand.NewSource(time.Now().UnixNano()))
 	return fmt.Sprintf("test-%v", r.Intn(1000))
 }
@@ -49,7 +49,7 @@ func TestConn() (driver.Driver, error) {
 	return d, nil
 }
 
-func GetVM(d driver.Driver, name string) (driver.VirtualMachine, error) {
+func getVm(d driver.Driver, name string) (driver.VirtualMachine, error) {
 	vm, err := d.FindVM(name)
 	if err != nil {
 		return nil, fmt.Errorf("error finding virtual machine: %v", err)
@@ -57,8 +57,8 @@ func GetVM(d driver.Driver, name string) (driver.VirtualMachine, error) {
 	return vm, nil
 }
 
-func CleanupVM(d driver.Driver, name string) error {
-	vm, err := GetVM(d, name)
+func CleanupVm(d driver.Driver, name string) error {
+	vm, err := getVm(d, name)
 	if err != nil {
 		return fmt.Errorf("error finding virtual machine: %v", err)
 	}
diff --git a/testing/vsphere/helpers.go b/testing/vsphere/helpers.go
new file mode 100644
index 0000000..b7b8c55
--- /dev/null
+++ b/testing/vsphere/helpers.go
@@ -0,0 +1,65 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package vsphere
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vapi/tags"
+)
+
+// markSimulatedVmAsTemplate powers off the virtual machine and converts it to a template.
+func markSimulatedVmAsTemplate(ctx context.Context, vm *object.VirtualMachine) error {
+	task, err := vm.PowerOff(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to issue powering off command to the machine: %w", err)
+	}
+	err = task.Wait(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to power off the machine: %w", err)
+	}
+	err = vm.MarkAsTemplate(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to mark virtual machine as a template: %w", err)
+	}
+	return nil
+}
+
+// ensureCategory ensures a tag category exists by name and returns its ID.
+func ensureCategory(ctx context.Context, man *tags.Manager, catName string) (string, error) {
+	categoryList, err := man.GetCategories(ctx)
+	if err != nil {
+		return "", fmt.Errorf("cannot return categories from cluster: %w", err)
+	}
+	for _, category := range categoryList {
+		if category.Name == catName {
+			return category.ID, nil
+		}
+	}
+	newCategoryID, err := man.CreateCategory(ctx, &tags.Category{Name: catName, Cardinality: "MULTIPLE"})
+	if err != nil {
+		return "", fmt.Errorf("cannot create category: %w", err)
+	}
+	return newCategoryID, nil
+}
+
+// ensureTag ensures a tag exists within the specified category and returns its ID.
+func ensureTag(ctx context.Context, man *tags.Manager, catID string, tagName string) (string, error) {
+	tagsInCategory, err := man.GetTagsForCategory(ctx, catID)
+	if err != nil {
+		return "", fmt.Errorf("cannot return tags for category: %w", err)
+	}
+	for _, tag := range tagsInCategory {
+		if tag.Name == tagName {
+			return tag.ID, nil
+		}
+	}
+	newTagID, err := man.CreateTag(ctx, &tags.Tag{Name: tagName, CategoryID: catID})
+	if err != nil {
+		return "", fmt.Errorf("cannot create tag: %w", err)
+	}
+	return newTagID, nil
+}
diff --git a/testing/vsphere/simulator.go b/testing/vsphere/simulator.go
new file mode 100644
index 0000000..bd33592
--- /dev/null
+++ b/testing/vsphere/simulator.go
@@ -0,0 +1,163 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package vsphere
+
+import (
+	"context"
+	"crypto/tls"
+	"fmt"
+	"net/url"
+	"time"
+
+	"github.com/vmware/govmomi"
+	"github.com/vmware/govmomi/find"
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/simulator"
+	"github.com/vmware/govmomi/vapi/rest"
+	_ "github.com/vmware/govmomi/vapi/simulator"
+	"github.com/vmware/govmomi/vapi/tags"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type Tag struct {
+	Category string
+	Name     string
+}
+
+type SimulatedVmConfig struct {
+	Name         string
+	Tags         []Tag
+	Template     bool
+	CreationTime time.Time
+}
+
+type simulatorContext struct {
+	Model      *simulator.Model
+	Server     *simulator.Server
+	Ctx        context.Context
+	Client     *govmomi.Client
+	RestClient *rest.Client
+	Finder     *find.Finder
+	Datacenter *object.Datacenter
+}
+
+// NewSimulator creates a new vCenter simulator with the specified model.
+func NewSimulator(model *simulator.Model) (*simulatorContext, error) {
+	ctx := context.Background()
+	if model == nil {
+		return nil, fmt.Errorf("model has not been initialized")
+	}
+
+	err := model.Create()
+	if err != nil {
+		return nil, fmt.Errorf("failed to create simulator model: %w", err)
+	}
+	model.Service.RegisterEndpoints = true
+	model.Service.TLS = new(tls.Config)
+
+	server := model.Service.NewServer()
+
+	u, err := url.Parse(server.URL.String())
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse simulator URL: %w", err)
+	}
+	password, _ := simulator.DefaultLogin.Password()
+	u.User = url.UserPassword(simulator.DefaultLogin.Username(), password)
+
+	client, err := govmomi.NewClient(ctx, u, true)
+	if err != nil {
+		return nil, fmt.Errorf("failed to connect to SOAP simulator: %w", err)
+	}
+
+	restClient := rest.NewClient(client.Client)
+	err = restClient.Login(ctx, simulator.DefaultLogin)
+	if err != nil {
+		return nil, fmt.Errorf("failed to login to REST simulator: %w", err)
+	}
+
+	finder := find.NewFinder(client.Client, false)
+	dcs, err := finder.DatacenterList(ctx, "*")
+	if err != nil {
+		return nil, fmt.Errorf("failed to list datacenters: %w", err)
+	}
+	if len(dcs) == 0 {
+		return nil, fmt.Errorf("datacenters were not found in the simulator: %w", err)
+	}
+	finder.SetDatacenter(dcs[0])
+
+	return &simulatorContext{
+		Ctx:        ctx,
+		Server:     server,
+		Model:      model,
+		Client:     client,
+		Finder:     finder,
+		RestClient: restClient,
+		Datacenter: dcs[0],
+	}, nil
+}
+
+func (sim *simulatorContext) Stop() {
+	if sim.Model != nil {
+		sim.Model.Remove()
+	}
+	if sim.Server != nil {
+		sim.Server.Close()
+	}
+}
+
+// ApplyVmConfiguration applies virtual machines in the simulator according to the provided configurations.
+func (sim *simulatorContext) ApplyVmConfiguration(vmsConfig []SimulatedVmConfig) error {
+	tagMan := tags.NewManager(sim.RestClient)
+
+	vms, err := sim.Finder.VirtualMachineList(sim.Ctx, "*")
+	if err != nil {
+		return fmt.Errorf("failed to list virtual machines in cluster: %w", err)
+	}
+
+	for i := 0; i < len(vmsConfig); i++ {
+		vmConfig := types.VirtualMachineConfigSpec{
+			Name: vmsConfig[i].Name,
+		}
+
+		if !vmsConfig[i].CreationTime.IsZero() {
+			vmConfig.CreateDate = &vmsConfig[i].CreationTime
+		}
+
+		if vmsConfig[i].Name != "" {
+			task, err := vms[i].Reconfigure(sim.Ctx, vmConfig)
+			if err != nil {
+				return fmt.Errorf("failed to issue rename of virtual machine command: %w", err)
+			}
+			if err = task.Wait(sim.Ctx); err != nil {
+				return fmt.Errorf("failed to rename virtual machine: %w", err)
+			}
+		}
+
+		if vmsConfig[i].Template {
+			err = markSimulatedVmAsTemplate(sim.Ctx, vms[i])
+			if err != nil {
+				return fmt.Errorf("failed to convert to templates: %w", err)
+			}
+		}
+
+		if vmsConfig[i].Tags != nil {
+			for _, tag := range vmsConfig[i].Tags {
+				catID, err := ensureCategory(sim.Ctx, tagMan, tag.Category)
+				if err != nil {
+					return fmt.Errorf("failed to ensure category exists: %w", err)
+				}
+				tagID, err := ensureTag(sim.Ctx, tagMan, catID, tag.Name)
+				if err != nil {
+					return fmt.Errorf("failed to ensure tag exists: %w", err)
+				}
+				err = tagMan.AttachTag(sim.Ctx, tagID, vms[i].Reference())
+				if err != nil {
+					return fmt.Errorf("failed to attach tag to virtual machine: %w", err)
+				}
+			}
+		}
+	}
+
+	return nil
+}