The File Structure of a VM

Avi Rzayev
8 min readJul 15, 2023

A virtual machine (VM) is a software emulation of a physical computer system. It allows you to run multiple operating systems (OS) or instances of an OS on a single physical machine, known as the host machine. Each virtual machine acts as an independent entity with its virtual hardware, including processors, memory, storage, and network interfaces.

The cool thing is that a Virtual Machine (VM) is a collection of files. While you may never need to open these files directly, they are highly significant. Understanding the components that make up a VM can provide you with a better understanding of its functioning.

This article will explore the file structure of a virtual machine (VM) and my experience with editing those files.

File Structure

Let me show you the files of my VM:

A typical file structure of a VM

There are many files but there are only a few that matter. In the next sections, we will cover each file type and its purpose.

.vmx file

.vmx file is a regular text file, containing all the information about the Virtual machine. It contains metadata about the VM, virtual hardware definitions, and links to other files in the directory.

The content of a .vmx file will look like this:

.encoding = "windows-1252"
config.version = "8"
virtualHW.version = "19"
mks.enable3d = "TRUE"
pciBridge0.present = "TRUE"
pciBridge4.present = "TRUE"
pciBridge4.virtualDev = "pcieRootPort"
pciBridge4.functions = "8"
pciBridge5.present = "TRUE"
pciBridge5.virtualDev = "pcieRootPort"
pciBridge5.functions = "8"
pciBridge6.present = "TRUE"
pciBridge6.virtualDev = "pcieRootPort"
pciBridge6.functions = "8"
pciBridge7.present = "TRUE"
pciBridge7.virtualDev = "pcieRootPort"
pciBridge7.functions = "8"
vmci0.present = "TRUE"
hpet0.present = "TRUE"
nvram = "Ubuntu 64-bit.nvram"
virtualHW.productCompatibility = "hosted"
powerType.powerOff = "soft"
powerType.powerOn = "soft"
powerType.suspend = "soft"
powerType.reset = "soft"
displayName = "Ubuntu 64-bit"
usb.vbluetooth.startConnected = "TRUE"
guestOS = "ubuntu-64"
tools.syncTime = "FALSE"
sound.autoDetect = "TRUE"
sound.fileName = "-1"
sound.present = "TRUE"
numvcpus = "4"
cpuid.coresPerSocket = "2"
vcpu.hotadd = "TRUE"
memsize = "8192"
mem.hotadd = "TRUE"
scsi0.virtualDev = "lsilogic"
scsi0.present = "TRUE"
sata0.present = "TRUE"
scsi0:0.fileName = "Ubuntu 64-bit.vmdk"
scsi0:0.present = "TRUE"
usb.present = "TRUE"
ehci.present = "TRUE"
svga.graphicsMemoryKB = "8388608"
serial0.fileType = "thinprint"
serial0.fileName = "thinprint"
floppy0.fileType = "file"
floppy0.fileName = "autoinst.flp"
serial0.present = "TRUE"
extendedConfigFile = "Ubuntu 64-bit.vmxf"
gui.lastPoweredViewMode = "fullscreen"
gui.stretchGuestMode = "fullfill"
tools.upgrade.policy = "useGlobal"
vmxstats.filename = "Ubuntu 64-bit.scoreboard"
uuid.bios = "56 4d ba a3 85 8d 36 e3-5c 44 ed da 52 8b cc cb"
uuid.location = "56 4d ba a3 85 8d 36 e3-5c 44 ed da 52 8b cc cb"
pciBridge0.pciSlotNumber = "17"
pciBridge4.pciSlotNumber = "21"
pciBridge5.pciSlotNumber = "22"
pciBridge6.pciSlotNumber = "23"
pciBridge7.pciSlotNumber = "24"
scsi0.pciSlotNumber = "16"
usb.pciSlotNumber = "32"
sound.pciSlotNumber = "34"
ehci.pciSlotNumber = "35"
sata0.pciSlotNumber = "36"
scsi0:0.redo = ""
svga.vramSize = "268435456"
vmotion.checkpointFBSize = "4194304"
vmotion.checkpointSVGAPrimarySize = "268435456"
vmotion.svga.mobMaxSize = "1073741824"
vmotion.svga.graphicsMemoryKB = "8388608"
vmotion.svga.supports3D = "1"
vmotion.svga.baseCapsLevel = "9"
vmotion.svga.maxPointSize = "1"
vmotion.svga.maxTextureSize = "16384"
vmotion.svga.maxVolumeExtent = "2048"
vmotion.svga.maxTextureAnisotropy = "16"
vmotion.svga.lineStipple = "0"
vmotion.svga.dxMaxConstantBuffers = "14"
vmotion.svga.dxProvokingVertex = "0"
vmotion.svga.sm41 = "1"
vmotion.svga.multisample2x = "1"
vmotion.svga.multisample4x = "1"
vmotion.svga.msFullQuality = "1"
vmotion.svga.logicOps = "1"
vmotion.svga.bc67 = "9"
vmotion.svga.sm5 = "1"
vmotion.svga.multisample8x = "1"
vmotion.svga.logicBlendOps = "1"
vmotion.svga.maxForcedSampleCount = "16"
vmotion.svga.gl43 = "1"
vmci0.id = "1746730973"
monitor.phys_bits_used = "45"
cleanShutdown = "TRUE"
softPowerOff = "FALSE"
usb:1.speed = "2"
usb:1.present = "TRUE"
usb:1.deviceType = "hub"
usb:1.port = "1"
usb:1.parent = "-1"
svga.guestBackedPrimaryAware = "TRUE"
floppy0.clientDevice = "FALSE"
guestInfo.detailed.data = "architecture='X86' bitness='64' distroName='Ubuntu 22.04.2 LTS' distroVersion='22.04' familyName='Linux' kernelVersion='5.19.0-46-generic' prettyName='Ubuntu 22.04.2 LTS'"
ethernet1.connectionType = "nat"
ethernet1.addressType = "generated"
ethernet1.virtualDev = "e1000"
ethernet1.pciSlotNumber = "37"
ethernet1.present = "TRUE"
ethernet1.generatedAddress = "00:0c:29:8b:cc:d5"
ethernet1.generatedAddressOffset = "10"
tools.remindInstall = "FALSE"
usb:0.present = "TRUE"
usb:0.deviceType = "hid"
usb:0.port = "0"
usb:0.parent = "-1"

There are many attributes that you can change. Some of the attributes are trivial. Let’s cover the essential ones:

  • displayName is the name of the VM that should be displayed in your client interface.
  • virtualHW.version is the version of the virtual hardware. It helps the Hypervisor (The software that emulates the VM) know what kind of capabilities are required to run the VM. If you had problems migrating a VM from one physical host to another, It might be because the host can’t run this type of hardware. Most of the cases you can just downgrade the version and everything would be fine.
  • guestOS is the OS type that the VM is running. When the host machine knows exactly what kind of OS it is running it helps to improve its performance.
  • numvcpus is the total CPU core that the VM will consume. The vcpu.hotadd set to TRUE allows us to add CPU while the VM is running (The concept of adding resources while the VM is running is called Hot Plug).
  • memsize is the total Memory that the VM will consume. The memsize.hotadd set to True allows us to add Memory while the VM is running
  • scsi<number> is the storage controller of your VM. A storage controller is a virtualized hardware component responsible for managing and controlling storage resources within the VM. It acts as an interface between the virtual machine and the underlying physical storage infrastructure. The available storage controllers are SCSI, IDE, SATA, and NVMe. Not all storage controllers will be supported on all host machines. scsi0.present tells us that the SCSI controller should be available to use.
  • scsi<number>:<disk_number>.filename represents the file that contains the storage of a virtual disk of a VM. The <disk_number> indicates the number of Hard disks that the storage controller manages.
  • ethernet<number> is the NIC (Network Interface) for your VM. <number> indicates the amount of NICs that the VM has. ethernet<number>.virtualDev is the type of NIC. You can read about VMware NIC types and how they affect performance.
  • ethernet<number>.connectionType is how the NIC will be exposed to the other network. It can be behind NAT and the host will operate as a router. It can be bridged to the network and the VM will be exposed to all network devices that the host is exposed to. It can be a private network between the host and the VM. Or it can be custom and be connected to a Virtual Switch.
  • ethernet<number>.generatedAddress is the MAC address of the NIC.

You got the concept. Every VM setting in the client interface is saved in the .vmx file. Let’s talk about .vmdk files.

.vmdk files

.vmdk files contain the storage of the VM. All the filesystem, the OS, and partitions are written into a.vmdk file. .vmdk is the Hard Disk of the Virtual Machine. There are four types of .vmdk files and we can identify them by their filename or size.

  • Monolithic sparse disk: A single .vmdk file that contains a single Hard Disk storage data of a VM. When creating a VM, the file is generated with a small size But has the potential to grow to the size that has been configured to the Hard disk.
    The name format will be <vm_name>-<hard_disk_num>.vmdk
    When you open the file you will see a few lines of readable data, it is the disk descriptor. After the descriptor, there are the actual bytes of data of the hard disk.
  • Monolithic flat disk: A single .vmdk file that contains a single Hard Disk storage data of a VM. When creating a VM, the file is created with the size of the Hard Disk, allocating the storage before it has been used.
    The name format will be as same as before:
    <vm_name>-<hard_disk_num>.vmdk
    You can identify the type of disks by their size. And also by opening them and looking at the createType filed in the descriptor.
  • TwoGbMaxExtentSparse: Multiple .vmdk disks that are representing a single Hard Disk. The files are generated when creating a VM and will fill up with data when the VM will write to the Hard Disk. This method is used when the host machine can’t cope with large-size files.
    The name format for those .vmdk files: <vm_name>-s<number>.vmdk
    There will be also <vm_name>.vmdk that will serve as a Hard Disk descriptor and link all the other .vmdk files.
  • Delta vmdk file: those files are generated when taking a snapshot of a VM. When taking a snapshot the original .vmdk file is locked and has read-only access and a new delta file is generated where the newer changes will be written to. The name format is: <vm_name>-<snapshot_number>.vmdk

And if we started to talk about snapshots, let’s move on to the .vmsd and .vmsnfiles.

.vmsn file

.vmsn files are created per VM snapshot. The file contains all the VM information (basically the same contents as the .vmx). So when reverting to the snapshot the VM settings are restored and unnecessary devices are removed. .vmsn files may save the VM memory if a live snapshot is being taken.

.vmsd file

.vmsd file will look like this:

.encoding = "windows-1252"
snapshot.lastUID = "2"
snapshot.current = "2"
snapshot0.uid = "1"
snapshot0.filename = "Ubuntu 64-bit-Snapshot1.vmsn"
snapshot0.displayName = "Base"
snapshot0.createTimeHigh = "393347"
snapshot0.createTimeLow = "1167943288"
snapshot0.numDisks = "3"
snapshot0.disk0.fileName = "Ubuntu 64-bit.vmdk"
snapshot0.disk0.node = "scsi0:0"
snapshot0.disk1.fileName = "Ubuntu 64-bit-0.vmdk"
snapshot0.disk1.node = "scsi0:1"
snapshot0.disk2.fileName = "Ubuntu 64-bit-1.vmdk"
snapshot0.disk2.node = "scsi0:2"
snapshot.numSnapshots = "2"
snapshot.mru0.uid = "2"
snapshot1.uid = "2"
snapshot1.filename = "Ubuntu 64-bit-Snapshot2.vmsn"
snapshot1.parent = "1"
snapshot1.displayName = "Snapi"
snapshot1.createTimeHigh = "393347"
snapshot1.createTimeLow = "1253721288"
snapshot1.numDisks = "3"
snapshot1.disk0.fileName = "Ubuntu 64-bit-000001.vmdk"
snapshot1.disk0.node = "scsi0:0"
snapshot1.disk1.fileName = "Ubuntu 64-bit-0-000001.vmdk"
snapshot1.disk1.node = "scsi0:1"
snapshot1.disk2.fileName = "Ubuntu 64-bit-1-000001.vmdk"
snapshot1.disk2.node = "scsi0:2"
snapshot.mru1.uid = "1"

The .vmsd contain all the snapshot tree of the VM. The attribute snapshot<number>.filenamerepresents the relevant .vmsn files to load the right configuration per snapshot.

Each snapshot has a uid (snapshot<number>.uid) that helps the child snapshot to point to their parents (snapshot<number>.parent) and that’s how the snapshot tree is created:

Snapshot tree in the client interface

There is also a snapshot<number>.disk<number> attribute that points to the parent disk of the snapshot (The disk that is locked after taking the snapshot). Quite Simple isn’t it?

.nvram file

NVRAM (non-volatile random-access memory) refers to computer memory that can hold data even when power to the memory chips has been turned off. It is used to store the computer’s state for faster boot. In virtualization, the .nvram is used to save all the bios settings of the VM.

Changing VM Files

It’s not common to edit your VM files directly. Most of the time if you want to add more CPU or delete a disk, you do it in the client interface. But sometimes you face some limitations and you have to do things manually.

In my situation, I had a local virtual machine with snapshots on my PC. One of those snapshots was a live snapshot (snapshot with VM memory saved). I needed to upload this VM into a vCenter and keep its snapshots. The task is quite simple — we take the files and put them into a datastore and then register a VM.

But there were many problems with that. The VC (vCenter) didn’t like the live snapshot. I don’t know why. I opened the.vmsd file and found the problematic snapshot. The cool thing is that it had an attribute named snapshot0.type = “1” which means it is a live snapshot. By editing it to snapshot0.type = “0” the snapshot became a regular snapshot. And I was able to run this VM at my VC.

I hope you enjoyed reading!

--

--