Windows Server Core VM and share folder

I used the Simos Windows VM creation guide as a blueprint to automate the creation of a Windows VM using Incus with a Windows Server Core ISO (without any GUI).

ISO: Windows Server Download | MAS

The instance configuration is as follows:

instance := api.InstancesPost{
		Name: args.Name,
		Type: api.InstanceTypeVM,
		Source: api.InstanceSource{
			Type: "none",
		},
		InstancePut: api.InstancePut{
			Config: map[string]string{
				"limits.cpu":    "4",
				"limits.memory": "4GiB",
				// Expose QEMU monitor via TCP to send the "any key" bypass
				"raw.qemu": "-device intel-hda -device hda-duplex -audio spice -monitor tcp:" + monitorAddr + ",server,nowait",
				"raw.qemu.conf": "[audiodev \"qemu_spice-audiodev\"]\ndriver = \"none\"",
			},
			Devices: map[string]map[string]string{
				"root": {
					"type": "disk",
					"pool": "default",
					"path": "/",
					"size": "25GiB",
				},
				"vtpm": {"type": "tpm"},
				"eth0": {
					"type":    "nic",
					"network": "incusbr0",
				},
				"install": {
					"type":          "disk",
					"source":        path.Join(downloadsDir, "windows-server.iso"),
					"boot.priority": "10",
				},
				// ISO containing SSH and winfsp
				"software": {
					"type":   "disk",
					"io.bus": "usb",
					"source": path.Join(isoDir, "software.iso"),
				},
				"autounattend": {
					"type":   "disk",
					"io.bus": "usb",
					"source": isoPath,
				},
				"virtio": {
					"type": "disk",
					// USB bus supported since incus v6.11
					"io.bus": "usb",
					"source": path.Join(downloadsDir, "virtio.iso"),
				},
			},
		},
	}

To automate the installation and setup of some software, I used an unattended ISO, which loads some Virtio drivers using a DriverPaths directive, enabling Windows to correctly detect the storage.

<DriverPaths>
    <PathAndCredentials wcm:action="add" wcm:keyValue="1">
        <Path>D:\vioscsi\w11\amd64</Path>
    </PathAndCredentials>
    <PathAndCredentials wcm:action="add" wcm:keyValue="2">
        <Path>E:\vioscsi\w11\amd64</Path>
    </PathAndCredentials>
    <PathAndCredentials wcm:action="add" wcm:keyValue="4">
        <Path>D:\vioscsi\2k25\amd64</Path>
    </PathAndCredentials>
    <PathAndCredentials wcm:action="add" wcm:keyValue="5">
        <Path>E:\vioscsi\2k25\amd64</Path>
    </PathAndCredentials>
</DriverPaths>

As for the install software script is as follows:

function Get-DriveByFile {
    param([string]$FileName)
    # Check PSDrives first, then Volumes
    $drive = (Get-PSDrive | Where-Object { Test-Path "$($_.Name):\$FileName" }).Name
    if (-not $drive) {
        $drive = (Get-Volume | Where-Object { Test-Path "$($_.DriveLetter):\$FileName" }).DriveLetter
    }
    return $drive
}

function Install-VirtioTools {
    $drive = Get-DriveByFile "virtio-win-guest-tools.exe"
    if (-not $drive) {
        Write-Warning "VirtIO ISO not found."; return
    }

    Write-Host "Found VirtIO ISO on $drive. Trusting certificates..."
    $certFile = Get-ChildItem -Path "$($drive):\cert\*.cat" -Recurse | Select-Object -First 1
    if ($certFile) {
        certutil -addstore "TrustedPublisher" $certFile.FullName
    }

    Write-Host "Installing Guest Tools..."
    Start-Process -FilePath "$($drive):\virtio-win-guest-tools.exe" -ArgumentList "/passive", "/norestart" -Wait
    Start-Sleep -Seconds 10
}

# --- OPENSSH INSTALLATION ---

function Install-OpenSSH {
    $drive = Get-DriveByFile "OpenSSH\install-sshd.ps1"
    if (-not $drive) {
        Write-Warning "OpenSSH source not found on any drive."; return
    }

    $dest = "C:\OpenSSH"
    if (-not (Test-Path $dest)) { New-Item -ItemType Directory -Path $dest -Force }
    Copy-Item -Path "$($drive):\OpenSSH\*" -Destination $dest -Recurse -Force


    # Remove "Read-Only" attribute from all copied files
    Get-ChildItem -Path $dest -Recurse | ForEach-Object {
        if ($_.Attributes -match "ReadOnly") {
            $_.Attributes = 'Archive'
        }
    }

    # Run the official install script
    Set-Location $dest
    powershell.exe -ExecutionPolicy Bypass -File ".\install-sshd.ps1"

    $psPath = (Get-Command powershell.exe).Source
    New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value $psPath -PropertyType String -Force


    # Configure Service and Firewall
    Set-Service -Name sshd -StartupType 'Automatic'
    Start-Service sshd
    if (!(Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue)) {
        New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
    }
}

function Install-Redistribuable {
    $drive = Get-DriveByFile "vc_redist.exe"
    if (-not $drive) {
        Write-Warning "vc_redist installer not found."; return
    }
    Start-Process -FilePath "$($drive):\vc_redist.exe" -ArgumentList "/install" "/passive", "/norestart"  -Wait
    Start-Sleep -Seconds 10
}

function Install-WinFSP {
    # Searches for a file matching winfsp-*.msi
    $drive = Get-DriveByFile "winfsp.msi"
    if (-not $drive) {
        Write-Warning "WinFSP installer not found."; return
    }

    $msiPath = Get-ChildItem -Path "$($drive):\winfsp.msi" | Select-Object -First 1
    Write-Host "Installing WinFSP from $($msiPath.FullName)..."

    Copy-Item $msiPath "C:\winfsp.msi"

    # INSTALLLEVEL=1000 ensures all features (including FUSE and Developer tools) are installed
    $arguments = "/i `"C:\winfsp.msi`" ADDLOCAL=ALL /qn /norestart"

    Start-Process "msiexec.exe" -ArgumentList $arguments -Wait
    Write-Host "WinFSP installation complete."
}

Install-Redistribuable
Install-WinFSP
Install-VirtioTools
Install-OpenSSH

Set-Service -Name "VirtioFsSvc" -StartupType Automatic
Start-Service -Name "VirtioFsSvc"

# Final Shutdown
Write-Output "Setup complete. Shutting down..."
Start-Sleep -Seconds 10
shutdown.exe /s /t 0 /f

The issue is that even if the VirtioFsSvc service is running when I run incus config device add win SHARED disk source=/home/…/ path=SHARED no volume letter is assigned, unlike what is shown in the guide capture.

I don’t know what’s wrong with my setup.

PS C:\Users\admin> Get-Service -Name VirtioFsSvc

Status   Name               DisplayName
------   ----               -----------
Running  VirtioFsSvc        VirtIO-FS Service


PS C:\Users\admin> Get-Volume

DriveLetter FriendlyName FileSystemType DriveType HealthStatus OperationalStatus SizeRemaining     Size
----------- ------------ -------------- --------- ------------ ----------------- -------------     ----
            SYSTEM       FAT32          Fixed     Healthy      OK                     65.12 MB    96 MB
C           Windows      NTFS           Fixed     Healthy      OK                     15.25 GB 24.88 GB


PS C:\Users\admin>

It’s finally working, but I don’t know exactly what the solution was. What I do know is that I did the following:

  • fixed the Visual Redistributable installation
  • set the image.os configuration to contain “Windows”.
  • installed the Incus agent

I have a slight problem because I can’t remove a shared folder device while the vm is running (it works if I shutdown the vm). Doesn’t virtiofs support hot plugging ?

❯ incus config device remove winvm SHARED
Error: Failed to stop device "SHARED": Failed to detach path device after 10s