├── XenBootFix ├── XenBootFix.rc ├── resource.h ├── scripts │ ├── sign.ps1 │ └── genfiles.ps1 ├── XenBootFix.vcxproj.filters └── RegHive.h ├── installer ├── branding │ ├── Up.ico │ ├── New.ico │ ├── info.ico │ ├── xen.ico │ ├── DlgBmp.bmp │ ├── BannrBmp.bmp │ └── exclamic.ico ├── Include.wxi ├── Folders.wxs ├── Tweaks.en-us.wxl ├── scripts │ ├── sign.ps1 │ └── genfiles.ps1 ├── TimeProvider.en-us.wxl ├── Tools.en-us.wxl ├── GuestAgent.en-us.wxl ├── Drivers │ ├── Xenhid.wxs │ ├── Xenvkbd.wxs │ ├── Xenvif.wxs │ ├── Xencons.wxs │ ├── Xeniface.wxs │ ├── Xennet.wxs │ ├── Xenvbd.wxs │ └── Xenbus.wxs ├── Tools.wxs ├── Tweaks.wxs ├── Branding.wxs ├── UI │ ├── Detected3PStorageDriversDlg.wxs │ ├── XenCustomizeDlg.wxs │ └── XenDriversUI.wxs ├── Drivers.en-us.wxl ├── TimeProvider.wxs ├── Drivers.wxs ├── XenDrivers.wixproj ├── Package.en-us.wxl ├── InstCA.wxs ├── GuestAgent.wxs ├── Driver.wxi ├── Package.wxs └── installer.sln ├── XenDriverUtils ├── NativeMethods.json ├── Logger.cs ├── scripts │ ├── sign.ps1 │ └── genfiles.ps1 ├── NativeMethods.txt ├── VersionUtils.cs ├── XenDriverUtils.csproj ├── XenOffboard.cs ├── InfFile.cs ├── XenCleanup.cs └── Copy-XenVifSettings.ps1 ├── XenClean ├── UninstallDrivers.cs ├── Logger.cs ├── UninstallRegistry.cs ├── Program.cs ├── app.manifest ├── UninstallServices.cs ├── scripts │ ├── sign.ps1 │ └── genfiles.ps1 ├── XenClean.csproj ├── UninstallDevices.cs ├── Invoke-XenClean.ps1 └── UninstallProducts.cs ├── scripts ├── xen-guest-agent │ ├── build.rs │ └── genfiles.ps1 ├── signer-ci.ps1 ├── Build-DriverSbom.ps1 ├── Compress-DriverCabinet.ps1 ├── sign.ps1 ├── Test-BuildAttestation.ps1 ├── Get-DriverSubmodule.ps1 ├── Compare-DriverSource.ps1 ├── Test-DriverInf.ps1 ├── branding-generic.ps1 ├── artifact.psm1 └── branding-ci.ps1 ├── DriverInstallCustomAction ├── scripts │ ├── sign.ps1 │ └── genfiles.ps1 ├── CustomActionUtils.cs ├── MsiLogger.cs ├── CustomAction.config ├── DriverInstallCustomAction.csproj ├── CleanupActions.cs ├── ImmediateActions.cs └── DriverActions.cs ├── .github ├── dependabot.yml └── workflows │ ├── attest.yml │ ├── build-drivers.yml │ ├── build-timeprovider.yml │ ├── build-guestagent.yml │ └── build-installer.yml ├── nuget.config ├── docs ├── offboard.md ├── coexistence.md ├── build.md └── attestation.md ├── testsign └── install.ps1 ├── .gitmodules ├── SBUpdate ├── SBUpdate.vcxproj.filters └── SBUpdate.hpp ├── README.md ├── extras └── xsa468.xml ├── .editorconfig └── .gitignore /XenBootFix/XenBootFix.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/XenBootFix/XenBootFix.rc -------------------------------------------------------------------------------- /installer/branding/Up.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/Up.ico -------------------------------------------------------------------------------- /installer/branding/New.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/New.ico -------------------------------------------------------------------------------- /installer/branding/info.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/info.ico -------------------------------------------------------------------------------- /installer/branding/xen.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/xen.ico -------------------------------------------------------------------------------- /installer/branding/DlgBmp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/DlgBmp.bmp -------------------------------------------------------------------------------- /XenDriverUtils/NativeMethods.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/CsWin32.schema.json", 3 | "public": true 4 | } 5 | -------------------------------------------------------------------------------- /installer/branding/BannrBmp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/BannrBmp.bmp -------------------------------------------------------------------------------- /installer/branding/exclamic.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcp-ng/win-pv-drivers/HEAD/installer/branding/exclamic.ico -------------------------------------------------------------------------------- /XenClean/UninstallDrivers.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using XenDriverUtils; 3 | 4 | namespace XenClean { 5 | internal static class UninstallDrivers { 6 | public static void Execute() { 7 | DriverUtils.UninstallDriverByNames(XenDeviceInfo.KnownDevices.Keys.ToArray()); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XenClean/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XenDriverUtils; 3 | 4 | namespace XenClean { 5 | internal class ConsoleLogger : Logger { 6 | public ConsoleLogger() { 7 | } 8 | 9 | public override void Write(string message) { 10 | Console.WriteLine(message); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/xen-guest-agent/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | extern crate winres; 3 | 4 | #[cfg(windows)] 5 | fn main() { 6 | let mut res = winres::WindowsResource::new(); 7 | res.set_manifest_file("manifest.xml"); 8 | include!("branding.rs"); 9 | res.compile().unwrap(); 10 | } 11 | 12 | #[cfg(unix)] 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /installer/Include.wxi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /XenClean/UninstallRegistry.cs: -------------------------------------------------------------------------------- 1 | using XenDriverUtils; 2 | 3 | namespace XenClean { 4 | internal class UninstallRegistry { 5 | public static void Execute() { 6 | XenCleanup.ResetStartOverride(); 7 | 8 | XenCleanup.XenfiltClassCleanup(); 9 | XenCleanup.ResetUnplug(); 10 | XenCleanup.ResetForceUnplug(); 11 | 12 | XenCleanup.XenfiltReset(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XenBootFix/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by XenBootFix.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /installer/Folders.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /installer/Tweaks.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XenBootFix/scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir, 5 | [Parameter(Mandatory)] 6 | [string]$Configuration, 7 | [Parameter(Mandatory)] 8 | [string]$Platform 9 | ) 10 | 11 | . "$ProjectDir\..\branding.ps1" 12 | . "$ProjectDir\..\scripts\branding-generic.ps1" 13 | . "$ProjectDir\..\scripts\sign.ps1" 14 | 15 | Set-SignerFileSignature -FilePath "$ProjectDir\$Platform\$Configuration\XenBootFix.exe" 16 | -------------------------------------------------------------------------------- /installer/scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir, 5 | [Parameter(Mandatory)] 6 | [string]$Configuration, 7 | [Parameter(Mandatory)] 8 | [string]$Platform 9 | ) 10 | 11 | . "$ProjectDir\..\branding.ps1" 12 | . "$ProjectDir\..\scripts\branding-generic.ps1" 13 | . "$ProjectDir\..\scripts\sign.ps1" 14 | 15 | Set-SignerFileSignature -FilePath "$ProjectDir\bin\$Platform\$Configuration\*\*.msi" 16 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir, 5 | [Parameter(Mandatory)] 6 | [string]$Configuration, 7 | [Parameter(Mandatory)] 8 | [string]$Platform 9 | ) 10 | 11 | . "$ProjectDir\..\branding.ps1" 12 | . "$ProjectDir\..\scripts\branding-generic.ps1" 13 | . "$ProjectDir\..\scripts\sign.ps1" 14 | 15 | Set-SignerFileSignature -FilePath "$ProjectDir\bin\$Platform\$Configuration\*\xeninst.CA.dll" 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | cooldown: 8 | default-days: 15 9 | groups: 10 | trusted: 11 | patterns: 12 | - "actions/*" 13 | - package-ecosystem: "nuget" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | cooldown: 18 | default-days: 15 19 | ignore: 20 | - dependency-name: "WixToolset.*" 21 | -------------------------------------------------------------------------------- /XenDriverUtils/Logger.cs: -------------------------------------------------------------------------------- 1 | namespace XenDriverUtils { 2 | public abstract class Logger { 3 | public abstract void Write(string message); 4 | 5 | private static Logger Instance = null; 6 | 7 | public static Logger SetLogger(Logger logger) { 8 | var old = Instance; 9 | Instance = logger; 10 | return old; 11 | } 12 | 13 | public static void Log(string message) { 14 | Instance?.Write(message); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /installer/TimeProvider.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XenDriverUtils/scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir, 5 | [Parameter(Mandatory)] 6 | [string]$Configuration, 7 | [Parameter(Mandatory)] 8 | [string]$Platform 9 | ) 10 | 11 | . "$ProjectDir\..\branding.ps1" 12 | . "$ProjectDir\..\scripts\branding-generic.ps1" 13 | . "$ProjectDir\..\scripts\sign.ps1" 14 | 15 | Set-SignerFileSignature -FilePath ` 16 | "$ProjectDir\bin\$Platform\$Configuration\*\xdutils.dll", ` 17 | "$ProjectDir\bin\$Platform\$Configuration\*\Copy-XenVifSettings.ps1" 18 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /installer/Tools.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /XenClean/Program.cs: -------------------------------------------------------------------------------- 1 | using XenClean; 2 | using XenDriverUtils; 3 | 4 | Logger.SetLogger(new ConsoleLogger()); 5 | if (XenCleanup.IsSafeMode()) { 6 | Logger.Log("Skipping Xenvif offboarding in Safe Mode"); 7 | } else { 8 | XenOffboard.BackupXenvif(); 9 | XenOffboard.PrepareRestoreXenvif(); 10 | } 11 | if (XenCleanup.IsSafeMode()) { 12 | Logger.Log("Skipping product uninstallation in Safe Mode"); 13 | } else { 14 | UninstallProducts.Execute(); 15 | } 16 | UninstallDevices.Execute(); 17 | UninstallDrivers.Execute(); 18 | UninstallServices.Execute(!XenCleanup.IsSafeMode()); 19 | UninstallRegistry.Execute(); 20 | Logger.Log("Finished, you must restart!"); 21 | -------------------------------------------------------------------------------- /installer/GuestAgent.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /XenClean/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /installer/Drivers/Xenhid.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /installer/Drivers/Xenvkbd.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /XenClean/UninstallServices.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Windows.Win32; 3 | using XenDriverUtils; 4 | 5 | namespace XenClean { 6 | internal static class UninstallServices { 7 | public static void Execute(bool removeNonDriverServices) { 8 | using var scm = PInvoke.OpenSCManager((string)null, (string)null, PInvoke.SC_MANAGER_ALL_ACCESS); 9 | if (scm.IsInvalid) { 10 | Logger.Log($"Cannot open SCM: error {Marshal.GetLastWin32Error()}"); 11 | return; 12 | } 13 | foreach (var service in XenCleanup.DeleteableServices) { 14 | if (service.Item2 || removeNonDriverServices) { 15 | XenCleanup.DeleteService(scm, service.Item1); 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/offboard.md: -------------------------------------------------------------------------------- 1 | # Driver offboarding 2 | 3 | See the CA sequencing in [Driver.wxi](/installer/Driver.wxi). 4 | 5 | ## Xenbus and Xenvbd 6 | 7 | See [CleanupActions.cs](/DriverInstallCustomAction/CleanupActions.cs). 8 | 9 | ## Xenvif 10 | 11 | Each network adapter on Windows has a separate interface configuration bound to the interface GUID, which is itself bound to the device ID. 12 | While Xenvif contains functionalities to save emulated interface settings and apply them to the PV interface, there exists no functionality in the other direction. 13 | Thus the offboarding helper script [Copy-XenVifSettings.ps1](/XenDriverUtils/Copy-XenVifSettings.ps1). 14 | 15 | It consists of two components: 16 | 17 | - A preinstall task that backs up PV interface config; 18 | - A postreboot task that reapplies the backed up configs to the emulated interfaces. 19 | -------------------------------------------------------------------------------- /XenDriverUtils/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | INVALID_HANDLE_VALUE 2 | WIN32_ERROR 3 | 4 | DiInstallDriver 5 | DiUninstallDevice 6 | 7 | SetupUninstallOEMInf 8 | SUOI_FORCEDELETE 9 | 10 | SetupOpenInfFile 11 | SetupFindFirstLine 12 | SetupFindNextLine 13 | SetupGetLineText 14 | SetupGetStringField 15 | SetupCloseInfFile 16 | 17 | SetupDiGetClassDevs 18 | SetupDiEnumDeviceInfo 19 | SetupDiGetDeviceProperty 20 | SetupDiDestroyDeviceInfoList 21 | 22 | GUID_DEVCLASS_SYSTEM 23 | GUID_DEVCLASS_SCSIADAPTER 24 | GUID_DEVCLASS_NET 25 | GUID_DEVCLASS_HIDCLASS 26 | GUID_DEVCLASS_HDC 27 | DEVPROPTYPE 28 | 29 | GlobalAddAtom 30 | GlobalFindAtom 31 | 32 | RtlGetVersion 33 | 34 | CMP_WaitNoPendingInstallEvents 35 | WAIT_EVENT 36 | 37 | OpenSCManager 38 | OpenService 39 | ControlService 40 | DeleteService 41 | SC_MANAGER_ALL_ACCESS 42 | SERVICE_ALL_ACCESS 43 | SERVICE_CONTROL_STOP 44 | -------------------------------------------------------------------------------- /installer/Drivers/Xenvif.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/scripts/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | $BrandingFile = "$ProjectDir\Branding.cs" 11 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 12 | $NewBranding = ` 13 | @" 14 | using System.Reflection; 15 | [assembly:AssemblyVersion("$(Get-PackageVersion Product)")] 16 | [assembly:AssemblyCompany("${Env:VENDOR_NAME}")] 17 | [assembly:AssemblyProduct("${Env:PRODUCT_NAME}")] 18 | [assembly:AssemblyCopyright("${Env:COPYRIGHT}")] 19 | [assembly:AssemblyTitle("Xen PV driver installation library")] 20 | "@ 21 | 22 | if ($NewBranding -ne $OldBranding) { 23 | Write-Output "Updating Branding.cs" 24 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 25 | } 26 | -------------------------------------------------------------------------------- /installer/Tools.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /installer/Tweaks.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /installer/Drivers/Xencons.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /XenDriverUtils/VersionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Windows.Win32.System.SystemInformation; 4 | 5 | namespace XenDriverUtils { 6 | public static class VersionUtils { 7 | // Unlike Environment.OSVersion, RtlGetVersion won't lie to us regardless of host manifest. 8 | public static Version GetWindowsVersion() { 9 | var versionInfo = new OSVERSIONINFOW { 10 | dwOSVersionInfoSize = (uint)Marshal.SizeOf() 11 | }; 12 | var ret = Windows.Wdk.PInvoke.RtlGetVersion(ref versionInfo); 13 | if (ret < 0) { 14 | throw new Exception($"RtlGetVersion failed with NTSTATUS {ret}"); 15 | } 16 | return new Version( 17 | (int)versionInfo.dwMajorVersion, 18 | (int)versionInfo.dwMinorVersion, 19 | (int)versionInfo.dwBuildNumber); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/signer-ci.ps1: -------------------------------------------------------------------------------- 1 | # Prepare signer info from provided environment variables. 2 | 3 | #Requires -Version 7 4 | 5 | [CmdletBinding()] 6 | param ( 7 | [Parameter(Mandatory)] 8 | [string]$OutFile 9 | ) 10 | 11 | $ErrorActionPreference = 'Stop' 12 | 13 | $pfxBytes = [System.Convert]::FromBase64String([regex]::Replace($Env:SIGNER_PFX_BASE64, '[^a-zA-Z0-9+/=]', '')) 14 | $pfxPath = Join-Path $pwd "Signer.pfx" 15 | [IO.File]::WriteAllBytes($pfxPath, $pfxBytes) 16 | try { 17 | certutil -importpfx -f -user -p "" my $pfxPath nochain 18 | if ($LASTEXITCODE -ne 0) { 19 | throw "certutil failed with error $LASTEXITCODE" 20 | } 21 | $signer = (Get-PfxCertificate -FilePath $pfxPath -NoPromptForPassword).Thumbprint 22 | } 23 | finally { 24 | Remove-Item -Force $pfxPath 25 | } 26 | Add-Content -Path $OutFile -Value "`$Env:SIGNER = '$signer'" -Force 27 | Add-Content -Path $Env:GITHUB_STEP_SUMMARY -Value "Signer thumbprint: ``$signer``" -Force 28 | -------------------------------------------------------------------------------- /testsign/install.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")] 4 | param ( 5 | [Parameter()] 6 | [switch]$NoReboot 7 | ) 8 | 9 | $ErrorActionPreference = "Stop" 10 | 11 | if (![Environment]::Is64BitProcess) { 12 | throw "Cannot install testsign certs from PowerShell x86!" 13 | } 14 | 15 | if (!$PSCmdlet.ShouldProcess("Local computer", "Set up testsigned XCP-ng driver - reduces your security")) { 16 | exit; 17 | } 18 | 19 | $System32 = [System.Environment]::SystemDirectory 20 | 21 | & "$System32\bcdedit.exe" -set testsigning on 22 | if ($LASTEXITCODE -ne 0) { 23 | throw "Cannot enable testsigning; is Secure Boot turned off?" 24 | } 25 | 26 | Get-ChildItem $PSScriptRoot\*.crt | ForEach-Object { 27 | & "$System32\certutil.exe" -addstore -f Root $_ 28 | & "$System32\certutil.exe" -addstore -f TrustedPublisher $_ 29 | } 30 | 31 | if (!$NoReboot) { 32 | Restart-Computer -Force 33 | } 34 | -------------------------------------------------------------------------------- /docs/coexistence.md: -------------------------------------------------------------------------------- 1 | # Coexistence with other Xen PV drivers and tools 2 | 3 | Our driver package is not designed to coexist with other driver packages. 4 | The following checks are made to ensure a clean and reliable installation/uninstallation: 5 | 6 | - The installer's Upgrade table checks for other installed packages and denies installation if these are detected (see [Package.wxs](/installer/Package.wxs)). [XenClean](/XenClean/UninstallProducts.cs) removes any detected Xen PV driver packages. 7 | - The [CheckIncompatibleDevices and Check3PStorageDrivers](/DriverInstallCustomAction/ImmediateActions.cs) custom actions prevent installation if existing Xen drivers, Xen vendor device or third-party storage drivers are present. 8 | If you're creating your own installer package, edit [XenDeviceInfo.cs](/XenDriverUtils/XenDeviceInfo.cs) to add your own customized device IDs if necessary. 9 | - Uninstalling the Windows Guest Tools package will remove all existing Xen PV drivers and driver configuration. 10 | -------------------------------------------------------------------------------- /installer/Branding.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /installer/Drivers/Xeniface.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /XenClean/scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir, 5 | [Parameter(Mandatory)] 6 | [string]$Configuration, 7 | [Parameter(Mandatory)] 8 | [string]$Platform 9 | ) 10 | 11 | . "$ProjectDir\..\branding.ps1" 12 | . "$ProjectDir\..\scripts\branding-generic.ps1" 13 | . "$ProjectDir\..\scripts\sign.ps1" 14 | 15 | Update-ScriptFileInfo ` 16 | "$ProjectDir\bin\$Platform\$Configuration\*\Invoke-XenClean.ps1" ` 17 | -Description "XenClean invocation script" ` 18 | -Version "$(Get-PackageVersion XenClean)" ` 19 | -Author $Env:VENDOR_NAME ` 20 | -CompanyName $Env:VENDOR_NAME ` 21 | -Copyright $Env:COPYRIGHT ` 22 | -Force 23 | 24 | # re Copy-XenVifSettings: Copied deps are copied from source (not output) and therefore need signing again. 25 | Set-SignerFileSignature -FilePath ` 26 | "$ProjectDir\bin\$Platform\$Configuration\*\XenClean.exe", ` 27 | "$ProjectDir\bin\$Platform\$Configuration\*\Invoke-XenClean.ps1", ` 28 | "$ProjectDir\bin\$Platform\$Configuration\*\Copy-XenVifSettings.ps1" 29 | -------------------------------------------------------------------------------- /installer/UI/Detected3PStorageDriversDlg.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/CustomActionUtils.cs: -------------------------------------------------------------------------------- 1 | using WixToolset.Dtf.WindowsInstaller; 2 | using Windows.Win32; 3 | using XenDriverUtils; 4 | 5 | namespace XenInstCA { 6 | internal static class CustomActionUtils { 7 | private static readonly string RebootScheduledAtomName = "WcaDeferredActionRequiresReboot"; 8 | 9 | public static void ScheduleReboot() { 10 | // Use the same method as Util.wixext's CheckRebootRequired. 11 | // Ignore errors. 12 | Logger.Log("Scheduling reboot"); 13 | _ = PInvoke.GlobalAddAtom(RebootScheduledAtomName); 14 | } 15 | 16 | public static bool IsRebootScheduled() { 17 | return PInvoke.GlobalFindAtom(RebootScheduledAtomName) != 0; 18 | } 19 | 20 | public static MessageResult ReportAction(Session session, string actionName, string message) { 21 | using var action = new Record(2); 22 | action[1] = actionName; 23 | action[2] = message; 24 | return session.Message(InstallMessage.ActionStart, action); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/Build-DriverSbom.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter()] 4 | [string[]]$Drivers = @("xenbus", "xencons", "xenhid", "xeniface", "xennet", "xenvbd", "xenvif", "xenvkbd"), 5 | [Parameter(Mandatory)] 6 | [ValidateSet("Debug", "Release")] 7 | [string]$Configuration, 8 | [Parameter(Mandatory)] 9 | [ValidateSet("x86", "x64")] 10 | [string]$Platform 11 | ) 12 | 13 | $ErrorActionPreference = "Stop" 14 | 15 | . $PSScriptRoot\..\branding.ps1 16 | . $PSScriptRoot\..\scripts\branding-generic.ps1 17 | . $PSScriptRoot\..\scripts\sign.ps1 18 | 19 | $OutputPath = "$PSScriptRoot\..\installer\output" 20 | foreach ($repo in $Drivers) { 21 | Push-Location $PSScriptRoot\..\$repo 22 | try { 23 | $DriverOutput = "$OutputPath\$Platform\$Configuration\$repo" 24 | 25 | sbom.exe generate -b $DriverOutput -bc . -D true -ps $Env:VENDOR_NAME -pn $repo -pv (Get-PackageVersion $repo) 26 | if ($LASTEXITCODE -ne 0) { 27 | throw "sbom-tool failed with error $LASTEXITCODE" 28 | } 29 | } 30 | finally { 31 | Pop-Location 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "xenbus"] 2 | path = xenbus 3 | url = https://github.com/xcp-ng/win-xenbus.git 4 | [submodule "xencons"] 5 | path = xencons 6 | url = https://github.com/xcp-ng/win-xencons.git 7 | [submodule "xenhid"] 8 | path = xenhid 9 | url = https://github.com/xcp-ng/win-xenhid.git 10 | [submodule "xeniface"] 11 | path = xeniface 12 | url = https://github.com/xcp-ng/win-xeniface.git 13 | [submodule "xennet"] 14 | path = xennet 15 | url = https://github.com/xcp-ng/win-xennet.git 16 | [submodule "xenvbd"] 17 | path = xenvbd 18 | url = https://github.com/xcp-ng/win-xenvbd.git 19 | [submodule "xenvif"] 20 | path = xenvif 21 | url = https://github.com/xcp-ng/win-xenvif.git 22 | [submodule "xenvkbd"] 23 | path = xenvkbd 24 | url = https://github.com/xcp-ng/win-xenvkbd.git 25 | [submodule "xen-guest-agent"] 26 | path = xen-guest-agent 27 | url = https://github.com/xcp-ng/xen-guest-agent.git 28 | [submodule "xenstore-win"] 29 | path = xenstore-win 30 | url = https://github.com/xcp-ng/xenstore-win.git 31 | [submodule "xentimeprovider"] 32 | path = xentimeprovider 33 | url = https://github.com/xcp-ng/win-xentimeprovider.git 34 | -------------------------------------------------------------------------------- /.github/workflows/attest.yml: -------------------------------------------------------------------------------- 1 | name: Attest artifact 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | subject-name: 7 | required: true 8 | type: string 9 | subject-digest: 10 | required: true 11 | type: string 12 | catalog-name: 13 | required: false 14 | type: string 15 | 16 | permissions: 17 | id-token: write 18 | contents: read 19 | attestations: write 20 | 21 | jobs: 22 | attest: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Attest artifact 26 | uses: actions/attest-build-provenance@v3 27 | with: 28 | subject-name: ${{inputs.subject-name}} 29 | subject-digest: ${{inputs.subject-digest}} 30 | - name: Download catalog 31 | if: ${{inputs.catalog-name != ''}} 32 | uses: actions/download-artifact@v6 33 | with: 34 | path: catalog/ 35 | name: ${{inputs.catalog-name}} 36 | - name: Attest catalog 37 | if: ${{inputs.catalog-name != ''}} 38 | uses: actions/attest-build-provenance@v3 39 | with: 40 | subject-path: catalog/${{inputs.catalog-name}}.csv 41 | -------------------------------------------------------------------------------- /XenClean/scripts/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | $BrandingFile = "$ProjectDir\Branding.cs" 11 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 12 | $NewBranding = ` 13 | @" 14 | using System.Reflection; 15 | [assembly:AssemblyVersion("$(Get-PackageVersion XenClean)")] 16 | [assembly:AssemblyInformationalVersion("$(Get-PackageVersion Product)")] 17 | [assembly:AssemblyCompany("${Env:VENDOR_NAME}")] 18 | [assembly:AssemblyProduct("${Env:PRODUCT_NAME}")] 19 | [assembly:AssemblyCopyright("${Env:COPYRIGHT}")] 20 | [assembly:AssemblyTitle("Xen PV driver cleaning tool")] 21 | 22 | namespace XenClean { 23 | internal static class VersionInfo { 24 | public const string MsiUpgradeCodeX86 = "${Env:MSI_UPGRADE_CODE_X86}"; 25 | public const string MsiUpgradeCodeX64 = "${Env:MSI_UPGRADE_CODE_X64}"; 26 | } 27 | } 28 | "@ 29 | 30 | if ($NewBranding -ne $OldBranding) { 31 | Write-Output "Updating Branding.cs" 32 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 33 | } 34 | -------------------------------------------------------------------------------- /SBUpdate/SBUpdate.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /scripts/Compress-DriverCabinet.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter()] 3 | [string]$Path = "installer/output", 4 | [Parameter()] 5 | $OutPath = "output", 6 | [Parameter()] 7 | $OutName = "drivers.cab" 8 | ) 9 | 10 | $ErrorActionPreference = "Stop" 11 | 12 | $OutPath = Convert-Path $OutPath 13 | 14 | $CabinetTemplate = @( 15 | ".OPTION EXPLICIT" 16 | ".Set DiskDirectoryTemplate=$OutPath" 17 | ".Set CabinetFileCountThreshold=0" 18 | ".Set FolderFileCountThreshold=0" 19 | ".Set FolderSizeThreshold=0" 20 | ".Set MaxCabinetSize=0" 21 | ".Set MaxDiskFileCount=0" 22 | ".Set MaxDiskSize=0" 23 | ".Set CompressionType=MSZIP" 24 | ".Set Cabinet=on" 25 | ".Set Compress=on" 26 | ) 27 | 28 | Get-ChildItem -Directory $Path | ForEach-Object { 29 | $subdir = $_.Name 30 | $CabinetTemplate += @(".Set DestinationDir=$subdir") 31 | $_ | Get-ChildItem -Recurse -File | ForEach-Object { 32 | $CabinetTemplate += @($_.FullName) 33 | } 34 | } 35 | 36 | try { 37 | $TemplateFile = New-TemporaryFile 38 | Set-Content -Path $TemplateFile -Value $CabinetTemplate 39 | 40 | makecab.exe /F $TemplateFile /D CabinetNameTemplate=$OutName 41 | } 42 | finally { 43 | Remove-Item $TemplateFile 44 | Remove-Item setup.inf, setup.rpt 45 | } 46 | -------------------------------------------------------------------------------- /XenDriverUtils/scripts/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | $BrandingFile = "$ProjectDir\Branding.cs" 11 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 12 | $NewBranding = ` 13 | @" 14 | using System.Reflection; 15 | [assembly:AssemblyVersion("$(Get-PackageVersion Product)")] 16 | [assembly:AssemblyCompany("${Env:VENDOR_NAME}")] 17 | [assembly:AssemblyProduct("${Env:PRODUCT_NAME}")] 18 | [assembly:AssemblyCopyright("${Env:COPYRIGHT}")] 19 | [assembly:AssemblyTitle("Xen PV driver utility library")] 20 | 21 | namespace XenDriverUtils { 22 | internal static class VersionInfo { 23 | public const string VendorName = "${Env:VENDOR_NAME}"; 24 | public const string ProductName = "${Env:PRODUCT_NAME}"; 25 | public const string VendorPrefix = "${Env:VENDOR_PREFIX}"; 26 | public const string VendorDeviceId = "${Env:VENDOR_DEVICE_ID}"; 27 | public const string Copyright = "${Env:COPYRIGHT}"; 28 | } 29 | } 30 | "@ 31 | 32 | if ($NewBranding -ne $OldBranding) { 33 | Write-Output "Updating Branding.cs" 34 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 35 | } 36 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/MsiLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WixToolset.Dtf.WindowsInstaller; 3 | using XenDriverUtils; 4 | 5 | namespace XenInstCA { 6 | internal class MsiSessionLogger : Logger { 7 | private readonly Session _session; 8 | 9 | public MsiSessionLogger(Session session) { 10 | _session = session; 11 | } 12 | 13 | public override void Write(string message) { 14 | _session.Log(message); 15 | } 16 | } 17 | 18 | internal class LoggerScope : IDisposable { 19 | private bool disposedValue; 20 | private Logger _old; 21 | 22 | public LoggerScope(Logger logger) { 23 | _old = Logger.SetLogger(logger); 24 | } 25 | 26 | protected virtual void Dispose(bool disposing) { 27 | if (!disposedValue) { 28 | if (disposing) { 29 | Logger.SetLogger(_old); 30 | _old = null; 31 | } 32 | disposedValue = true; 33 | } 34 | } 35 | 36 | public void Dispose() { 37 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 38 | Dispose(disposing: true); 39 | GC.SuppressFinalize(this); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /installer/Drivers.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /installer/scripts/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | $BrandingFile = "$ProjectDir\Branding.wxi" 11 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 12 | $NewBranding = @" 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | "@ 21 | 22 | foreach ($package in @("Product", "Xenbus", "Xencons", "Xenhid", "Xeniface", "Xennet", "Xenvbd", "Xenvif", "Xenvkbd", "XenClean", "XenBootFix", "XenGuestAgent", "XenTimeProvider")) { 23 | $NewBranding += @" 24 | 25 | 26 | "@ 27 | } 28 | 29 | $NewBranding += @" 30 | 31 | 32 | 33 | 34 | "@ 35 | 36 | if ($NewBranding -ne $OldBranding) { 37 | Write-Output "Updating Branding.wxi" 38 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 39 | } 40 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/CustomAction.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XenBootFix/XenBootFix.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | 31 | 32 | Resource Files 33 | 34 | 35 | -------------------------------------------------------------------------------- /scripts/sign.ps1: -------------------------------------------------------------------------------- 1 | $env:PSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') 2 | Import-Module PowerShellGet 3 | if (!(Get-PSDrive Cert)) { 4 | New-PSDrive -PSProvider Certificate -Name Cert -Root \ 5 | } 6 | 7 | function Get-SignerObject() { 8 | if (!$Env:SIGNER) { 9 | return $null 10 | } 11 | 12 | if (${Env:SIGNER}.EndsWith(".pfx", [System.StringComparison]::OrdinalIgnoreCase)) { 13 | return Get-PfxCertificate -FilePath $Env:SIGNER 14 | } 15 | else { 16 | return Get-ChildItem Cert:\CurrentUser\My\$Env:SIGNER 17 | } 18 | } 19 | 20 | function Set-SignerFileSignature() { 21 | param ( 22 | [Parameter()] 23 | [string[]]$FilePath 24 | ) 25 | 26 | $SignerObject = Get-SignerObject 27 | if (!$SignerObject) { 28 | return 29 | } 30 | 31 | $signArgs = @{ 32 | FilePath = $FilePath 33 | HashAlgorithm = "SHA256" 34 | Certificate = $SignerObject 35 | } 36 | if ($Env:TIMESTAMP_SERVER) { 37 | $signArgs["TimestampServer"] = $Env:TIMESTAMP_SERVER 38 | } 39 | 40 | Set-AuthenticodeSignature @signArgs 41 | } 42 | 43 | function Export-SignerCertificate { 44 | param ( 45 | [Parameter()] 46 | [string[]]$OutDir 47 | ) 48 | 49 | $SignerObject = Get-SignerObject 50 | if (!$SignerObject) { 51 | return 52 | } 53 | 54 | Export-Certificate -Cert $SignerObject -FilePath $OutDir\$($SignerObject.Thumbprint).crt -Type CERT -Force 55 | } 56 | -------------------------------------------------------------------------------- /installer/Drivers/Xennet.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /scripts/Test-BuildAttestation.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7 2 | 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory)] 6 | [string]$Bundle, 7 | [Parameter(Mandatory)] 8 | [string]$Path, 9 | [Parameter(Mandatory)] 10 | [string]$Repository, 11 | [Parameter()] 12 | [string]$Filter, 13 | [Parameter()] 14 | [string[]]$Include, 15 | [Parameter()] 16 | [string[]]$Exclude 17 | ) 18 | 19 | $ErrorActionPreference = 'Stop' 20 | 21 | $bundlePath = Convert-Path $Bundle 22 | 23 | # cache the trusted root for faster verification 24 | $root = New-TemporaryFile 25 | gh attestation trusted-root | Set-Content -Path $root -Encoding UTF8 26 | if ($LASTEXITCODE -ne 0) { 27 | throw "Cannot fetch trusted root" 28 | } 29 | Write-Information "Trusted root cached to $root" 30 | 31 | try { 32 | Push-Location $Path | Out-Null 33 | return (Get-ChildItem -Recurse -File -Filter $Filter -Include $Include -Exclude $Exclude | 34 | Resolve-Path -Relative | 35 | ForEach-Object { 36 | gh attestation verify $_ ` 37 | --custom-trusted-root $root.FullName ` 38 | --bundle $bundlePath ` 39 | --deny-self-hosted-runners ` 40 | --repo $Repository > $null 41 | if ($LASTEXITCODE -ne 0) { 42 | throw "Attestation failed for $_" 43 | } 44 | Write-Information "Attestation succeeded for $_" 45 | }) 46 | } 47 | finally { 48 | Pop-Location | Out-Null 49 | Remove-Item $root -Force 50 | } 51 | -------------------------------------------------------------------------------- /installer/TimeProvider.wxs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XCP-ng Windows Guest Tools 2 | 3 | This repository contains the XCP-ng Windows Guest Tools installer source code. 4 | 5 | The relevant source code may be found at these locations: 6 | 7 | * Drivers: 8 | * [Bus Device Driver](https://github.com/xcp-ng/win-xenbus) 9 | * [Interface Driver](https://github.com/xcp-ng/win-xeniface) 10 | * [Network Class Driver](https://github.com/xcp-ng/win-xenvif) 11 | * [Network Device Driver](https://github.com/xcp-ng/win-xennet) 12 | * [Storage Class Driver](https://github.com/xcp-ng/win-xenvbd) 13 | * [Console Driver](https://github.com/xcp-ng/win-xencons) 14 | * [Keyboard/Mouse Driver](https://github.com/xcp-ng/win-xenvkbd) 15 | * [HID Minidriver](https://github.com/xcp-ng/win-xenhid) 16 | * [Driver installation support library](XenDriverUtils/) 17 | * [Installer package](installer/) 18 | * [Custom WiX actions](DriverInstallCustomAction/) 19 | * [XenClean](XenClean/) 20 | * [XenBootFix](XenBootFix/) 21 | * [Xen Guest Agent](https://github.com/xcp-ng/xen-guest-agent) 22 | * [xenstore-win dependency](https://github.com/xcp-ng/xenstore-win) 23 | * [Xen Time Provider](https://github.com/xcp-ng/win-xentimeprovider) 24 | * [Developer support scripts](scripts/) 25 | * [Supplemental tools](extras/) 26 | * [Source documentation](docs/) 27 | 28 | # Usage 29 | 30 | Driver and tool projects are included as submodules of this repository: 31 | 32 | ``` 33 | git clone --recursive https://github.com/xcp-ng/win-pv-drivers.git 34 | ``` 35 | 36 | For further information, see the [source documentation](docs/). 37 | -------------------------------------------------------------------------------- /installer/Drivers/Xenvbd.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /installer/Drivers.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /XenBootFix/scripts/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | $BrandingFile = "$ProjectDir\Branding.inc" 11 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 12 | $NewBranding = @" 13 | VS_VERSION_INFO VERSIONINFO 14 | FILEVERSION $((Get-PackageVersion XenBootFix).ToString().Replace(".", ",")) 15 | PRODUCTVERSION $((Get-PackageVersion Product).ToString().Replace(".", ",")) 16 | FILEFLAGSMASK 0x3fL 17 | #ifdef _DEBUG 18 | FILEFLAGS 0x1L 19 | #else 20 | FILEFLAGS 0x0L 21 | #endif 22 | FILEOS 0x40004L 23 | FILETYPE 0x1L 24 | FILESUBTYPE 0x0L 25 | BEGIN 26 | BLOCK "StringFileInfo" 27 | BEGIN 28 | BLOCK "200004b0" 29 | BEGIN 30 | VALUE "CompanyName", "${Env:VENDOR_NAME}" 31 | VALUE "FileDescription", "Xen PV boot fix tool" 32 | VALUE "FileVersion", "$(Get-PackageVersion XenBootFix)" 33 | VALUE "InternalName", "XenBootFix.exe" 34 | VALUE "LegalCopyright", "${Env:COPYRIGHT}" 35 | VALUE "OriginalFilename", "XenBootFix.exe" 36 | VALUE "ProductName", "${Env:PRODUCT_NAME}" 37 | VALUE "ProductVersion", "$(Get-PackageVersion Product)" 38 | END 39 | END 40 | BLOCK "VarFileInfo" 41 | BEGIN 42 | VALUE "Translation", 0x2000, 1200 43 | END 44 | END 45 | 46 | "@ 47 | 48 | if ($NewBranding -ne $OldBranding) { 49 | Write-Output "Updating Branding.inc" 50 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 51 | } 52 | -------------------------------------------------------------------------------- /XenClean/XenClean.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net462 6 | 9.0 7 | x64;x86;ARM64 8 | warnings 9 | app.manifest 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /SBUpdate/SBUpdate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // Windows headers don't provide definitions for these attributes, so use our own 16 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_NON_VOLATILE = 0x00000001; 17 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_BOOTSERVICE_ACCESS = 0x00000002; 18 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_RUNTIME_ACCESS = 0x00000004; 19 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_HARDWARE_ERROR_RECORD = 0x00000008; 20 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_AUTHENTICATED_WRITE_ACCESS = 0x00000010; 21 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x00000020; 22 | static constexpr DWORD SBUPDATE_VARIABLE_ATTRIBUTE_APPEND_WRITE = 0x00000040; 23 | 24 | struct SecureBootVariable { 25 | const wchar_t *VariableName; 26 | const wchar_t *VariableGuid; 27 | const DWORD VariableAttributes; 28 | }; 29 | 30 | // win32_encryptablevolume.mof 31 | 32 | static constexpr const wchar_t *FveNamespace = L"Root\\CIMV2\\Security\\MicrosoftVolumeEncryption"; 33 | static constexpr const wchar_t *FveGetSecureBootBindingState = L"GetSecureBootBindingState"; 34 | static constexpr const wchar_t *FveDisableKeyProtectors = L"DisableKeyProtectors"; 35 | 36 | enum FveSecureBootBindingState { 37 | FveSecureBootBindingStateNotPossible, 38 | FveSecureBootBindingStateDisabledByPolicy, 39 | FveSecureBootBindingStatePossible, 40 | FveSecureBootBindingStateBound, 41 | }; 42 | -------------------------------------------------------------------------------- /XenBootFix/RegHive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class RegHive { 8 | public: 9 | RegHive(HKEY hkey, LPCWSTR subkey, LPCWSTR file) : _hkey(hkey), _subkey(subkey) { 10 | auto result = RegLoadKeyW(hkey, subkey, file); 11 | if (result != ERROR_SUCCESS) 12 | throw std::system_error(result, std::system_category(), "RegLoadKeyW"); 13 | } 14 | RegHive(const RegHive &) = delete; 15 | RegHive &operator=(const RegHive &) = delete; 16 | RegHive(RegHive &&other) noexcept { 17 | swap(*this, other); 18 | } 19 | RegHive &operator=(RegHive &&other) noexcept { 20 | if (this != &other) { 21 | dispose(); 22 | swap(*this, other); 23 | } 24 | return *this; 25 | }; 26 | ~RegHive() { 27 | dispose(); 28 | } 29 | 30 | constexpr HKEY HKey() const noexcept { 31 | return _hkey; 32 | } 33 | 34 | constexpr LPCWSTR SubKey() const noexcept { 35 | return _subkey; 36 | } 37 | 38 | int release() noexcept { 39 | dispose(); 40 | } 41 | 42 | friend void swap(RegHive &self, RegHive &other) noexcept { 43 | using std::swap; 44 | swap(self._hkey, other._hkey); 45 | swap(self._subkey, other._subkey); 46 | } 47 | 48 | private: 49 | HKEY _hkey = (HKEY)INVALID_HANDLE_VALUE; 50 | LPCWSTR _subkey = nullptr; 51 | 52 | void dispose() noexcept { 53 | if (_hkey != (HKEY)INVALID_HANDLE_VALUE) { 54 | auto result = RegUnLoadKeyW(_hkey, _subkey); 55 | if (result != ERROR_SUCCESS) 56 | wprintf(L"Cannot unload key: 0x%lx\n", result); 57 | _hkey = (HKEY)INVALID_HANDLE_VALUE; 58 | _subkey = nullptr; 59 | } 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /XenDriverUtils/XenDriverUtils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net462 5 | xdutils 6 | 9.0 7 | x64;x86;ARM64 8 | warnings 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/DriverInstallCustomAction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net462 5 | xeninst 6 | XenInstCA 7 | 9.0 8 | x64;x86;ARM64 9 | warnings 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /scripts/Get-DriverSubmodule.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Switch]$Downstream 4 | ) 5 | 6 | $submodules = "xenbus", "xencons", "xenhid", "xeniface", "xennet", "xenvbd", "xenvif", "xenvkbd" 7 | 8 | foreach ($submodule in $submodules) { 9 | # Get submodule head from parent 10 | $submodule_head_line = git ls-tree HEAD $submodule 11 | $submodule_head = ($submodule_head_line.Trim() -split '\s+')[2] 12 | 13 | # Get current branch 14 | $branch = (git -C $submodule rev-parse --abbrev-ref HEAD) 15 | 16 | # Compare with parent repo's commit 17 | $commits_since_parent = git -C $submodule log --oneline "$submodule_head..HEAD" 18 | 19 | # Compare with master 20 | $downstream_commits = $null 21 | if ($Downstream) { 22 | # Use git cherry to find commits on the branch that are not on master. 23 | # Equivalent commits (by patch-id) are marked with '-' 24 | # Unique commits are marked with '+' 25 | $cherry_output = git -C $submodule cherry master $branch 26 | $downstream_commits = foreach ($line in $cherry_output) { 27 | if (-not($line)) { 28 | continue 29 | } 30 | $sign, $sha = $line.Split(' ', 2) 31 | $oneline = git -C $submodule log -1 --pretty="format:%h %s" $sha 32 | "$sign $oneline" 33 | } 34 | } 35 | 36 | if (($commits_since_parent) -or ($downstream_commits)) { 37 | Write-Output "--- Submodule: $submodule ---" 38 | Write-Output "Current branch: $branch" 39 | Write-Output "" 40 | 41 | if (!$Downstream -and $commits_since_parent) { 42 | Write-Output "Uncommitted changes from ${submodule_head}:" 43 | $commits_since_parent 44 | Write-Output "" 45 | } 46 | 47 | if ($Downstream -and $downstream_commits) { 48 | Write-Output "Downstream changes in ${branch}:" 49 | $downstream_commits 50 | Write-Output "" 51 | } 52 | 53 | Write-Output "" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scripts/Compare-DriverSource.ps1: -------------------------------------------------------------------------------- 1 | # Script to compare driver include/ and src/common/ directories against xenbus 2 | 3 | param ( 4 | [Parameter()] 5 | [string[]]$Drivers = @("xencons", "xenhid", "xeniface", "xennet", "xenvbd", "xenvif", "xenvkbd"), 6 | [Parameter()] 7 | $ReferenceDriver = "xenbus", 8 | [Parameter()] 9 | $SubDirs = @("include", "src/common"), 10 | [Parameter()] 11 | [switch]$ShowDiff 12 | ) 13 | 14 | foreach ($driver in $Drivers) { 15 | $hasDifference = $false 16 | Write-Host "--- Comparing $driver against $ReferenceDriver ---" 17 | 18 | foreach ($subDir in $SubDirs) { 19 | $referencePath = Join-Path -Path $ReferenceDriver -ChildPath $subDir 20 | $targetPath = Join-Path -Path $driver -ChildPath $subDir 21 | 22 | if (-not (Test-Path -Path $referencePath -PathType Container)) { continue } 23 | if (-not (Test-Path -Path $targetPath -PathType Container)) { continue } 24 | 25 | $referenceFiles = Get-ChildItem -Path $referencePath -File 26 | if ($null -eq $referenceFiles) { 27 | continue 28 | } 29 | 30 | foreach ($refFile in $referenceFiles) { 31 | $targetFile = Join-Path -Path $targetPath -ChildPath $refFile.Name 32 | 33 | if (Test-Path -Path $targetFile -PathType Leaf) { 34 | $diffOutput = git diff --no-index --quiet --exit-code $refFile.FullName $targetFile 2>&1 35 | if ($LASTEXITCODE -ne 0) { 36 | $hasDifference = $true 37 | Write-Host "[$driver] MISMATCH in '$($refFile.FullName)' vs '$targetFile'" 38 | 39 | if ($ShowDiff) { 40 | $diffOutput = git diff --no-index $refFile.FullName $targetFile 2>&1 41 | # Print the diff output 42 | Write-Host ($diffOutput | Out-String) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | if (-not $hasDifference) { 50 | Write-Host "[$driver] OK: No content differences found in common files." 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /installer/XenDrivers.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | x86;x64;ARM64 4 | XenTools-$(Platform) 5 | 1103 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | branding 27 | 28 | 29 | output 30 | 31 | 32 | xenguestagent 33 | 34 | 35 | xentimeprovider 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /XenDriverUtils/XenOffboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | 6 | namespace XenDriverUtils { 7 | public class XenOffboard { 8 | enum ScriptMode { 9 | Backup, 10 | Restore, 11 | } 12 | 13 | enum ExecutionMode { 14 | Install, 15 | Invoke, 16 | } 17 | 18 | enum DeviceType { 19 | Paravirtualized, 20 | Emulated, 21 | } 22 | 23 | static void RunCopyXenvifScript(ScriptMode mode, ExecutionMode execMode, DeviceType deviceType) { 24 | var dllPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 25 | var scriptPath = Path.Combine(dllPath, "Copy-XenVifSettings.ps1"); 26 | var powershellPath = Path.Combine(Environment.SystemDirectory, "WindowsPowerShell\\v1.0\\powershell.exe"); 27 | 28 | var startInfo = new ProcessStartInfo() { 29 | FileName = powershellPath, 30 | Arguments = $"-ExecutionPolicy Bypass -File \"{scriptPath}\" -{mode} -{execMode} -{deviceType}", 31 | UseShellExecute = false, 32 | RedirectStandardOutput = true, 33 | RedirectStandardError = true, 34 | CreateNoWindow = true 35 | }; 36 | 37 | using var process = Process.Start(startInfo); 38 | process.WaitForExit(); 39 | 40 | var output = process.StandardOutput.ReadToEnd(); 41 | var error = process.StandardError.ReadToEnd(); 42 | 43 | if (process.ExitCode != 0) { 44 | Logger.Log($"Copy-XenVifSettings.ps1 error {process.ExitCode}: {error}"); 45 | throw new Exception($"Copy-XenVifSettings.ps1 error {process.ExitCode}"); 46 | } 47 | 48 | Logger.Log($"Copy-XenVifSettings.ps1 output: {output}"); 49 | } 50 | 51 | public static void BackupXenvif() { 52 | Logger.Log($"Backing up Xenvif settings"); 53 | RunCopyXenvifScript(ScriptMode.Backup, ExecutionMode.Invoke, DeviceType.Paravirtualized); 54 | } 55 | 56 | public static void PrepareRestoreXenvif() { 57 | Logger.Log($"Scheduling Xenvif restore"); 58 | RunCopyXenvifScript(ScriptMode.Restore, ExecutionMode.Install, DeviceType.Emulated); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /XenClean/UninstallDevices.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Windows.Win32; 5 | using Windows.Win32.Foundation; 6 | using Windows.Win32.Devices.DeviceAndDriverInstallation; 7 | using XenDriverUtils; 8 | 9 | namespace XenClean { 10 | internal static class UninstallDevices { 11 | static readonly List UninstallOrder = new() { 12 | "Xenvbd", 13 | "Xennet", 14 | "Xenvif", 15 | "Xenhid", 16 | "Xenvkbd", 17 | "Xencons", 18 | "Xeniface", 19 | "Xendevice", 20 | "Xenclass", 21 | "Xenbus", 22 | }; 23 | 24 | static void RemoveDevices(XenDeviceInfo xenInfo) { 25 | var devInfo = PInvoke.SetupDiGetClassDevs( 26 | xenInfo.ClassGuid, 27 | null, 28 | HWND.Null, 29 | xenInfo.ClassGuid.HasValue ? 0 : SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES); 30 | bool needsReboot = false; 31 | 32 | foreach (var devInfoData in DriverUtils.EnumerateDevices(devInfo)) { 33 | List compatibleIds = DriverUtils.GetDeviceHardwareAndCompatibleIds(devInfo, devInfoData); 34 | if (compatibleIds.All(x => !xenInfo.MatchesId(x, checkKnown: true, checkIncompatible: true))) { 35 | continue; 36 | } 37 | 38 | var instanceId = DriverUtils.GetDeviceInstanceId(devInfo, devInfoData); 39 | if (instanceId != null) { 40 | Logger.Log($"Found device: {instanceId}"); 41 | } else { 42 | Logger.Log($"Found device"); 43 | } 44 | 45 | try { 46 | DriverUtils.UninstallDevice(devInfo, devInfoData, out var thisNeedsReboot); 47 | needsReboot |= thisNeedsReboot; 48 | } catch (Exception ex) { 49 | Logger.Log($"Cannot uninstall device: {ex.Message}"); 50 | } 51 | } 52 | } 53 | 54 | public static void Execute() { 55 | foreach (var driver in UninstallOrder) { 56 | var xenInfo = XenDeviceInfo.KnownDevices[driver]; 57 | Logger.Log($"Uninstalling {driver}"); 58 | RemoveDevices(xenInfo); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /installer/Package.en-us.wxl: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /installer/InstCA.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /installer/GuestAgent.wxs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 40 | 45 | 46 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /installer/Drivers/Xenbus.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /XenClean/Invoke-XenClean.ps1: -------------------------------------------------------------------------------- 1 | 2 | <#PSScriptInfo 3 | 4 | .VERSION 1.0 5 | 6 | .GUID 5948fb9f-c421-4e12-82db-383de1663dca 7 | 8 | .AUTHOR Xen Project 9 | 10 | .COMPANYNAME 11 | 12 | .COPYRIGHT 13 | 14 | .TAGS 15 | 16 | .LICENSEURI 17 | 18 | .PROJECTURI 19 | 20 | .ICONURI 21 | 22 | .EXTERNALMODULEDEPENDENCIES 23 | 24 | .REQUIREDSCRIPTS 25 | 26 | .EXTERNALSCRIPTDEPENDENCIES 27 | 28 | .RELEASENOTES 29 | 30 | 31 | #> 32 | 33 | <# 34 | 35 | .DESCRIPTION 36 | XenClean invocation script 37 | 38 | #> 39 | 40 | #Requires -RunAsAdministrator 41 | 42 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")] 43 | param ( 44 | [Parameter()] 45 | [switch]$NoReboot 46 | ) 47 | 48 | $ErrorActionPreference = "Stop" 49 | 50 | if (![Environment]::Is64BitProcess) { 51 | throw "XenClean cannot run in PowerShell x86!" 52 | } 53 | 54 | function Export-XenSettings { 55 | param($LogPath, $Suffix) 56 | 57 | Write-Host "Saving settings$Suffix..." 58 | 59 | & "$System32\pnputil.exe" /enum-drivers > "$LogPath/drivers$Suffix.log" 60 | if ([System.Environment]::OSVersion.Version -ge [version]::Parse("10.0.18362")) { 61 | & "$System32\pnputil.exe" /enum-devices /relations > "$LogPath/devices$Suffix.log" 62 | } 63 | 64 | & "$System32\reg.exe" export "HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e96a-e325-11ce-bfc1-08002be10318}" "$LogPath/hdc$Suffix.reg" /y >> "$LogPath/export$Suffix.log" 65 | & "$System32\reg.exe" export "HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e97d-e325-11ce-bfc1-08002be10318}" "$LogPath/system$Suffix.reg" /y >> "$LogPath/export$Suffix.log" 66 | foreach ($service in @("XEN", "xenbus", "xencons", "xenfilt", "xenhid", "xeniface", "xennet", "xenvbd", "xenvif", "xenvkbd", "Tcpip", "Tcpip6")) { 67 | & "$System32\reg.exe" export "HKLM\SYSTEM\CurrentControlSet\Services\$Service" "$LogPath/service-$Service$Suffix.reg" /y >> "$LogPath/export$Suffix.log" 68 | } 69 | & "$System32\reg.exe" export "HKLM\SOFTWARE\XenOffboard" "$LogPath/xenoffboard$Suffix.reg" /y >> "$LogPath/export$Suffix.log" 70 | } 71 | 72 | if (!$PSCmdlet.ShouldProcess("Local computer", "Remove Xen drivers and tools")) { 73 | exit; 74 | } 75 | 76 | $System32 = [System.Environment]::SystemDirectory 77 | $DirName = "xenclean-$(Get-Date -Format FileDateTime)" 78 | $LogPath = "$env:TEMP\$DirName" 79 | New-Item -ItemType Directory -Path $LogPath -Force 80 | 81 | Export-XenSettings -LogPath $LogPath -Suffix "-pre" 82 | Write-Host "Running XenClean, be patient..." 83 | & "$PSScriptRoot\bin\XenClean.exe" > "$LogPath/xenclean.log" 84 | Export-XenSettings -LogPath $LogPath -Suffix "-post" 85 | 86 | Copy-Item -Recurse -ErrorAction SilentlyContinue $LogPath $Env:SystemDrive\$DirName 87 | 88 | if (!$NoReboot) { 89 | Restart-Computer -Force 90 | } 91 | -------------------------------------------------------------------------------- /scripts/Test-DriverInf.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter()] 4 | [string[]]$Drivers = @("xenbus", "xencons", "xenhid", "xeniface", "xennet", "xenvbd", "xenvif", "xenvkbd"), 5 | [Parameter(Mandatory)] 6 | [ValidateSet("Debug", "Release")] 7 | [string]$Configuration, 8 | [Parameter(Mandatory)] 9 | [ValidateSet("x86", "x64")] 10 | [string]$Platform, 11 | [Parameter()] 12 | [string]$OutputPath = "$PSScriptRoot\..\installer\output", 13 | [Parameter()] 14 | [string]$DriverRequirementsBuild, 15 | [Parameter()] 16 | [string]$RuleVersion, 17 | [Parameter()] 18 | [ValidateSet("", "WHQL", "Universal", "Windows")] 19 | [string]$PrintErrors, 20 | [Parameter()] 21 | [switch]$Detailed 22 | ) 23 | 24 | $ErrorActionPreference = 'Stop' 25 | 26 | $result = @{} 27 | $output = @{} 28 | 29 | $PrintErrorsMap = @{ 30 | WHQL = "/h" 31 | Universal = "/u" 32 | Windows = "/w" 33 | } 34 | 35 | foreach ($driver in $Drivers) { 36 | foreach ($mode in @("/h", "/u", "/w")) { 37 | $inf = "$OutputPath\$Platform\$Configuration\$driver\$driver.inf" 38 | 39 | if ($null -eq $result[$driver]) { 40 | $result[$driver] = @{} 41 | } 42 | if ($null -eq $output[$driver]) { 43 | $output[$driver] = @{} 44 | } 45 | 46 | if (!(Test-Path $inf)) { 47 | throw "Inf '$inf' doesn't exist" 48 | } 49 | 50 | $params = @() 51 | if ($DriverRequirementsBuild) { 52 | $params += @("/wbuild", $DriverRequirementsBuild) 53 | } 54 | if ($RuleVersion) { 55 | $params += @("/rulever", $RuleVersion) 56 | } 57 | if ($Detailed) { 58 | $params += @("/v") 59 | } 60 | 61 | $output[$driver][$mode] = (& "${Env:WindowsSdkDir}Tools\${Env:WindowsSDKLibVersion}\$Platform\infverif.exe" $mode $inf @params) 62 | $result[$driver][$mode] = $LASTEXITCODE 63 | } 64 | } 65 | 66 | $dnml = ($Drivers | Select-Object -ExpandProperty Length | Measure-Object -Maximum).Maximum 67 | Write-Host (" " * ($dnml + 1) + "WHQL Univ Wind") 68 | foreach ($driver in $Drivers) { 69 | $line = $driver.PadRight($dnml) + " " 70 | foreach ($mode in @("/h", "/u", "/w")) { 71 | if ($result[$driver][$mode] -eq 0) { 72 | $line += ".... " 73 | } 74 | else { 75 | $line += "Fail " 76 | } 77 | } 78 | Write-Host $line 79 | } 80 | if ($PrintErrors) { 81 | Write-Host 82 | $mode = $PrintErrorsMap[$PrintErrors] 83 | foreach ($driver in $Drivers) { 84 | if ($result[$driver][$mode] -ne 0) { 85 | Write-Host "InfVerif failures for ${driver}:" 86 | $output[$driver][$mode] | Write-Host 87 | Write-Host 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/CleanupActions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.Win32; 3 | using WixToolset.Dtf.WindowsInstaller; 4 | using XenDriverUtils; 5 | 6 | namespace XenInstCA { 7 | public class CleanupActions { 8 | [CustomAction] 9 | public static ActionResult XenbusCleanup(Session session) { 10 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 11 | XenCleanup.XenfiltClassCleanup(); 12 | XenCleanup.ResetUnplug(); 13 | XenCleanup.ResetForceUnplug(); 14 | return ActionResult.Success; 15 | } 16 | 17 | [CustomAction] 18 | public static ActionResult XenvbdCleanup(Session session) { 19 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 20 | XenCleanup.ResetStartOverride(); 21 | return ActionResult.Success; 22 | } 23 | 24 | [CustomAction] 25 | public static ActionResult XenfiltReset(Session session) { 26 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 27 | XenCleanup.XenfiltReset(); 28 | return ActionResult.Success; 29 | } 30 | 31 | [CustomAction] 32 | public static ActionResult XenServiceDelete(Session session) { 33 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 34 | 35 | if (!session.CustomActionData.TryGetValue("Services", out var services)) 36 | return ActionResult.Success; 37 | var serviceList = services.Split(','); 38 | 39 | using var scm = PInvoke.OpenSCManager((string)null, (string)null, PInvoke.SC_MANAGER_ALL_ACCESS); 40 | foreach (var serviceName in serviceList) { 41 | XenCleanup.DeleteService(scm, serviceName); 42 | } 43 | 44 | return ActionResult.Success; 45 | } 46 | 47 | [CustomAction] 48 | public static ActionResult XenvifBackup(Session session) { 49 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 50 | if (XenCleanup.IsSafeMode()) { 51 | Logger.Log("Skipping Xenvif backup in Safe Mode"); 52 | } else { 53 | XenOffboard.BackupXenvif(); 54 | } 55 | return ActionResult.Success; 56 | } 57 | 58 | [CustomAction] 59 | public static ActionResult XenvifPrepareRestore(Session session) { 60 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 61 | if (XenCleanup.IsSafeMode()) { 62 | Logger.Log("Skipping Xenvif restore in Safe Mode"); 63 | } else { 64 | XenOffboard.PrepareRestoreXenvif(); 65 | } 66 | return ActionResult.Success; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /XenClean/UninstallProducts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Management; 6 | using XenDriverUtils; 7 | 8 | namespace XenClean { 9 | internal static class UninstallProducts { 10 | static readonly List KnownUpgradeCodes = new() { 11 | // x64 drivers version 6.6.557 from XS 6.6.90 12 | // must be uninstalled in the correct order 13 | // Citrix XenServer Tools Installer 14 | "{21EF141F-9126-42DA-93CD-B50442047420}", 15 | // Citrix XenServer Windows Guest Agent 16 | "{48E5492C-6843-452E-97A2-A5FE2D24B141}", 17 | // Citrix XenServer VSS Provider 18 | "{D8709720-65B7-4CD9-9F51-68DB592B604D}", 19 | // Citrix Xen Windows x64 PV Drivers 20 | "{53858014-F814-49A1-9D63-CA2578432E73}", 21 | 22 | // Citrix uses the same package code as the Citrix XenServer Windows Guest Agent 23 | // in their multi-package XS 6.6 drivers for their 7.1 series drivers 24 | // (aka. 48E5492C-6843-452E-97A2-A5FE2D24B141) 25 | // XCP-ng 8.2 x64 also uses the same upgrade code. 26 | 27 | // Citrix Hypervisor/XS8 28 | "{AF9B2559-3E91-4206-98C2-F560009FF7F1}", 29 | 30 | // generic x86 (does not work due to check in Invoke-XenClean) 31 | "{10828840-D8A9-4953-B44A-1F1D3CD7ECB0}", 32 | // generic x64 33 | "{D60FED1E-316C-41B0-B7A5-E44951A82618}", 34 | 35 | // ours 36 | VersionInfo.MsiUpgradeCodeX86, 37 | VersionInfo.MsiUpgradeCodeX64, 38 | }; 39 | 40 | public static void Execute() { 41 | foreach (var upgradeCode in KnownUpgradeCodes) { 42 | Logger.Log($"Trying to uninstall products with upgrade code {upgradeCode}"); 43 | var moSearcher = new ManagementObjectSearcher( 44 | $"SELECT ProductCode FROM Win32_Property WHERE Property='UpgradeCode' AND Value='{upgradeCode}'"); 45 | var moObjects = moSearcher.Get(); 46 | var msiexecPath = Path.Combine(Environment.SystemDirectory, "msiexec.exe"); 47 | foreach (var moObject in moObjects) { 48 | Logger.Log($"Uninstalling product {moObject["ProductCode"]}"); 49 | using var msiexecProcess = Process.Start( 50 | msiexecPath, 51 | $"/x \"{moObject["ProductCode"]}\" /passive /norestart"); 52 | msiexecProcess.WaitForExit(); 53 | if (msiexecProcess.ExitCode != 0) { 54 | Logger.Log($"Msiexec exited with code {msiexecProcess.ExitCode}"); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /installer/Driver.wxi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /scripts/xen-guest-agent/genfiles.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory)] 4 | [string]$ProjectDir 5 | ) 6 | 7 | . "$ProjectDir\..\branding.ps1" 8 | . "$ProjectDir\..\scripts\branding-generic.ps1" 9 | 10 | function Update-BrandingFile { 11 | $BrandingFile = "$ProjectDir\xen-guest-agent\branding.rs" 12 | $OldBranding = Get-Content -Raw $BrandingFile -ErrorAction Ignore 13 | 14 | $FileVersion = Get-PackageVersion XenGuestAgent 15 | $NewFileVersionValue = [string]::Format("0x{0:x}", ` 16 | ([uint64]$FileVersion.Major -shl 48) -bor ` 17 | ([uint64]$FileVersion.Minor -shl 32) -bor ` 18 | ([uint64]$FileVersion.Build -shl 16) -bor ` 19 | [uint64]$FileVersion.Revision) 20 | 21 | $ProductVersion = Get-PackageVersion Product 22 | $NewProductVersionValue = [string]::Format("0x{0:x}", ` 23 | ([uint64]$ProductVersion.Major -shl 48) -bor ` 24 | ([uint64]$ProductVersion.Minor -shl 32) -bor ` 25 | ([uint64]$ProductVersion.Build -shl 16) -bor ` 26 | [uint64]$ProductVersion.Revision) 27 | 28 | $NewBranding = @" 29 | loop { 30 | res.set_version_info(winres::VersionInfo::FILEVERSION, ${NewFileVersionValue}); 31 | res.set_version_info(winres::VersionInfo::PRODUCTVERSION, ${NewProductVersionValue}); 32 | 33 | let crate_version = env!("CARGO_PKG_VERSION"); 34 | let file_description = format!("Xen Guest Agent (v{crate_version}, Rust-based)"); 35 | 36 | res.set("CompanyName", "${Env:VENDOR_NAME}"); 37 | res.set("FileDescription", &file_description); 38 | res.set("FileVersion", "$($FileVersion.ToString())"); 39 | res.set("InternalName", "xen-guest-agent.exe"); 40 | res.set("LegalCopyright", "${Env:COPYRIGHT}"); 41 | res.set("OriginalFilename", "xen-guest-agent.exe"); 42 | res.set("ProductName", "${Env:PRODUCT_NAME}"); 43 | res.set("ProductVersion", "$($ProductVersion.ToString())"); 44 | 45 | break; 46 | } 47 | "@ 48 | 49 | if ($NewBranding -ne $OldBranding) { 50 | Write-Output "Updating branding.rs" 51 | Set-Content -Path $BrandingFile -Value $NewBranding -NoNewline 52 | } 53 | } 54 | 55 | function Update-AgentVersionFile { 56 | $VersionFile = "$ProjectDir\publishers\publisher-xenstore\src\version.rs" 57 | $OldVersion = Get-Content -Raw $VersionFile -ErrorAction Ignore 58 | 59 | $Version = Get-PackageVersion Product 60 | 61 | $NewVersion = @" 62 | pub(crate) const AGENT_VERSION_MAJOR: &str = "$($Version.Major)"; // XO does not show version at all if 0 63 | pub(crate) const AGENT_VERSION_MINOR: &str = "$($Version.Minor)"; 64 | pub(crate) const AGENT_VERSION_MICRO: &str = "$($Version.Build)"; // XAPI exposes "-1" if missing 65 | pub(crate) const AGENT_VERSION_BUILD: &str = "$($Version.Revision)"; 66 | "@ 67 | 68 | if ($NewVersion -ne $OldVersion) { 69 | Write-Output "Updating $VersionFile" 70 | Set-Content -Path $VersionFile -Value $NewVersion -NoNewline 71 | } 72 | } 73 | 74 | Update-BrandingFile 75 | Update-AgentVersionFile 76 | -------------------------------------------------------------------------------- /scripts/branding-generic.ps1: -------------------------------------------------------------------------------- 1 | function Get-PackageVersion { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Position = 1)] 5 | [string]$PackageName = 'Product', 6 | # Don't fill in 0 for non-existent build/revision numbers. 7 | [Parameter()] 8 | [switch]$Raw 9 | ) 10 | $VersionString = $PackageVersions[$PackageName] 11 | if (!$VersionString) { 12 | throw "Cannot get $PackageName version" 13 | } 14 | $RawVersion = [version]::Parse($VersionString) 15 | if ($Raw) { 16 | return $RawVersion 17 | } 18 | else { 19 | $build = if ($RawVersion.Build -eq -1) { 0 } else { $RawVersion.Build } 20 | $rev = if ($RawVersion.Revision -eq -1) { 0 } else { $RawVersion.Revision } 21 | return [version]::new($RawVersion.Major, $RawVersion.Minor, $build, $rev) 22 | } 23 | } 24 | 25 | if (!$Env:VENDOR_NAME) { 26 | $Env:VENDOR_NAME = 'Xen Project' 27 | } 28 | 29 | if (!$Env:VENDOR_PREFIX) { 30 | $Env:VENDOR_PREFIX = 'XP' 31 | } 32 | 33 | if (!$Env:PRODUCT_NAME) { 34 | $Env:PRODUCT_NAME = 'Xen' 35 | } 36 | 37 | # xeniface 38 | if (!$Env:OBJECT_PREFIX) { 39 | $Env:OBJECT_PREFIX = 'XenProject' 40 | } 41 | 42 | if (!$Env:COPYRIGHT) { 43 | $Env:COPYRIGHT = 'Copyright (c) Xen Project.' 44 | } 45 | 46 | if ($null -eq $PackageVersions) { 47 | $PackageVersions = @{ 48 | } 49 | } 50 | if ($null -eq $PackageVersions['Product']) { 51 | $PackageVersions['Product'] = '9.1.0.0' 52 | } 53 | if ($null -eq $PackageVersions['xenbus']) { 54 | $PackageVersions['xenbus'] = $PackageVersions['Product'] 55 | } 56 | if ($null -eq $PackageVersions['xencons']) { 57 | $PackageVersions['xencons'] = $PackageVersions['Product'] 58 | } 59 | if ($null -eq $PackageVersions['xenhid']) { 60 | $PackageVersions['xenhid'] = $PackageVersions['Product'] 61 | } 62 | if ($null -eq $PackageVersions['xeniface']) { 63 | $PackageVersions['xeniface'] = $PackageVersions['Product'] 64 | } 65 | if ($null -eq $PackageVersions['xennet']) { 66 | $PackageVersions['xennet'] = $PackageVersions['Product'] 67 | } 68 | if ($null -eq $PackageVersions['xenvbd']) { 69 | $PackageVersions['xenvbd'] = $PackageVersions['Product'] 70 | } 71 | if ($null -eq $PackageVersions['xenvif']) { 72 | $PackageVersions['xenvif'] = $PackageVersions['Product'] 73 | } 74 | if ($null -eq $PackageVersions['xenvkbd']) { 75 | $PackageVersions['xenvkbd'] = $PackageVersions['Product'] 76 | } 77 | if ($null -eq $PackageVersions['XenClean']) { 78 | $PackageVersions['XenClean'] = $PackageVersions['Product'] 79 | } 80 | if ($null -eq $PackageVersions['XenBootFix']) { 81 | $PackageVersions['XenBootFix'] = $PackageVersions['Product'] 82 | } 83 | if ($null -eq $PackageVersions['XenGuestAgent']) { 84 | $PackageVersions['XenGuestAgent'] = $PackageVersions['Product'] 85 | } 86 | if ($null -eq $PackageVersions['XenTimeProvider']) { 87 | $PackageVersions['XenTimeProvider'] = $PackageVersions['Product'] 88 | } 89 | 90 | if (!$Env:MSI_UPGRADE_CODE_X86) { 91 | $Env:MSI_UPGRADE_CODE_X86 = '{10828840-D8A9-4953-B44A-1F1D3CD7ECB0}' 92 | } 93 | 94 | if (!$Env:MSI_UPGRADE_CODE_X64) { 95 | $Env:MSI_UPGRADE_CODE_X64 = '{D60FED1E-316C-41B0-B7A5-E44951A82618}' 96 | } 97 | -------------------------------------------------------------------------------- /scripts/artifact.psm1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | function Get-ArtifactCatalog { 4 | [CmdletBinding()] 5 | param ( 6 | [Parameter(Mandatory)] 7 | [string]$Path, 8 | [Parameter()] 9 | [string]$Filter, 10 | [Parameter()] 11 | [string[]]$Include, 12 | [Parameter()] 13 | [string[]]$Exclude 14 | ) 15 | 16 | try { 17 | Push-Location $Path | Out-Null 18 | return (Get-ChildItem -Recurse -File -Filter $Filter -Include $Include -Exclude $Exclude | 19 | Resolve-Path -Relative | 20 | ForEach-Object { 21 | [PSCustomObject]@{ 22 | Path = $_ 23 | AuthenticodeHash = (Get-AppLockerFileInformation $_).Hash.ToString() 24 | } 25 | }) 26 | } 27 | finally { 28 | Pop-Location | Out-Null 29 | } 30 | } 31 | 32 | function Compare-Artifact { 33 | [CmdletBinding()] 34 | param ( 35 | [Parameter(Mandatory)] 36 | [string]$TrustedPath, 37 | [Parameter(Mandatory)] 38 | [string]$ComparePath, 39 | [Parameter()] 40 | [string]$Filter, 41 | [Parameter()] 42 | [string[]]$Include, 43 | [Parameter()] 44 | [string[]]$Exclude 45 | ) 46 | 47 | $trustedCatalog = Get-ArtifactCatalog -Path $TrustedPath -Filter $Filter -Include $Include -Exclude $Exclude 48 | $compareCatalog = Get-ArtifactCatalog -Path $ComparePath -Filter $Filter -Include $Include -Exclude $Exclude 49 | 50 | $differences = Compare-Object ` 51 | -ReferenceObject $trustedCatalog ` 52 | -DifferenceObject $compareCatalog ` 53 | -Property Path, AuthenticodeHash 54 | if ($differences) { 55 | Write-Warning "The two catalogs differ" 56 | $differences | Write-Warning 57 | return $false 58 | } 59 | 60 | return $true 61 | } 62 | 63 | function Test-ArtifactCatalog { 64 | [CmdletBinding()] 65 | param ( 66 | [Parameter(Mandatory)] 67 | [object[]]$TrustedCatalog, 68 | [Parameter(Mandatory)] 69 | [string]$ComparePath, 70 | [Parameter(Mandatory)] 71 | [string]$CatalogPrefix, 72 | [Parameter()] 73 | [string]$Filter, 74 | [Parameter()] 75 | [string[]]$Include, 76 | [Parameter()] 77 | [string[]]$Exclude, 78 | [Parameter()] 79 | [switch]$IncludeEqual 80 | ) 81 | 82 | $filteredTrustedCatalog = $TrustedCatalog | Where-Object { 83 | $_.Path.StartsWith($CatalogPrefix, [System.StringComparison]::OrdinalIgnoreCase) 84 | } 85 | 86 | $compareCatalog = Get-ArtifactCatalog -Path $ComparePath -Filter $Filter -Include $Include -Exclude $Exclude | 87 | ForEach-Object { 88 | $compareFile = Join-Path $CatalogPrefix $_.Path.TrimStart([char]'.', [System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) 89 | Write-Verbose "$($_.Path) -> $compareFile" 90 | [PSCustomObject]@{ 91 | Path = $compareFile 92 | AuthenticodeHash = $_.AuthenticodeHash 93 | } 94 | } 95 | 96 | Compare-Object ` 97 | -ReferenceObject $filteredTrustedCatalog ` 98 | -DifferenceObject $compareCatalog ` 99 | -Property Path, AuthenticodeHash ` 100 | -IncludeEqual:$IncludeEqual 101 | } 102 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/ImmediateActions.cs: -------------------------------------------------------------------------------- 1 | using Windows.Win32; 2 | using Windows.Win32.Foundation; 3 | using Windows.Win32.Devices.DeviceAndDriverInstallation; 4 | using WixToolset.Dtf.WindowsInstaller; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using XenDriverUtils; 9 | 10 | namespace XenInstCA { 11 | public static class ImmediateActions { 12 | [CustomAction] 13 | public static ActionResult CheckWindowsVersion(Session session) { 14 | var minSupportedProperty = session["XenMinSupportedVersion"]; 15 | session.Log($"minSupportedProperty {minSupportedProperty}"); 16 | if (!Version.TryParse(minSupportedProperty, out var minSupported)) { 17 | return ActionResult.Success; 18 | } 19 | session.Log($"minSupported {minSupported}"); 20 | 21 | var currentVersion = VersionUtils.GetWindowsVersion(); 22 | session.Log($"currentVersion {currentVersion}"); 23 | if (currentVersion < minSupported) { 24 | session["XenWindowsNotSupported"] = currentVersion.ToString(); 25 | } 26 | return ActionResult.Success; 27 | } 28 | 29 | [CustomAction] 30 | public static ActionResult CheckReboot(Session session) { 31 | if (CustomActionUtils.IsRebootScheduled()) { 32 | session.SetMode(InstallRunMode.RebootAtEnd, true); 33 | } 34 | return ActionResult.Success; 35 | } 36 | 37 | [CustomAction] 38 | public static ActionResult CheckIncompatibleDevices(Session session) { 39 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 40 | 41 | var incompatibilities = new List(); 42 | 43 | var devInfo = PInvoke.SetupDiGetClassDevs( 44 | (Guid?)null, 45 | null, 46 | HWND.Null, 47 | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES); 48 | foreach (var devInfoData in DriverUtils.EnumerateDevices(devInfo)) { 49 | List deviceIds = DriverUtils.GetDeviceHardwareAndCompatibleIds(devInfo, devInfoData); 50 | bool found = false; 51 | foreach (var xenClass in XenDeviceInfo.KnownDevices.Values) { 52 | if (deviceIds.Any(x => xenClass.MatchesId(x, checkKnown: false, checkIncompatible: true))) { 53 | found = true; 54 | break; 55 | } 56 | } 57 | if (!found) { 58 | continue; 59 | } 60 | 61 | Logger.Log($"Found device with incompatible IDs: {string.Join(",", deviceIds)}"); 62 | var instanceId = DriverUtils.GetDeviceInstanceId(devInfo, devInfoData); 63 | if (instanceId != null) { 64 | Logger.Log($"Adding incompatible instance ID {instanceId}"); 65 | incompatibilities.Add(instanceId); 66 | } else { 67 | incompatibilities.Add("(unknown)"); 68 | } 69 | } 70 | 71 | session["IncompatibleDevices"] = string.Join(",", incompatibilities); 72 | return ActionResult.Success; 73 | } 74 | 75 | [CustomAction] 76 | public static ActionResult Check3PStorageDrivers(Session session) { 77 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 78 | 79 | var found3PDrivers = XenCleanup.Find3PStorageDrivers(); 80 | session["ThirdPartyStorageDrivers"] = string.Join(",", found3PDrivers.Select(x => x.DriverInfPath)); 81 | return ActionResult.Success; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /installer/Package.wxs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /installer/UI/XenCustomizeDlg.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /extras/xsa468.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10.0.0.0 4 | {2E07F7E4-194C-4D20-B7C9-6F44A6C5A234} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 0 89 | {1A785F78-1622-4A2E-8866-34A6F87CF9F7} 90 | {1A785F78-1622-4A2E-8866-34A6F87CF9F7} 91 | 92 | -------------------------------------------------------------------------------- /installer/UI/XenDriversUI.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /scripts/branding-ci.ps1: -------------------------------------------------------------------------------- 1 | # Prepare branding info from provided environment variables. 2 | # This script should be attestation-safe. Try our best to sanitize the environment. 3 | # Use a highly-constrained parameter range. 4 | 5 | #Requires -Version 7 6 | 7 | [CmdletBinding()] 8 | param ( 9 | [Parameter(Mandatory)] 10 | [string]$OutFile, 11 | [Parameter()] 12 | [switch]$AddSigner 13 | ) 14 | 15 | $ErrorActionPreference = 'Stop' 16 | 17 | function Out-SafeString { 18 | [CmdletBinding()] 19 | param ( 20 | [Parameter()] 21 | [string]$InputObject, 22 | [Parameter(Mandatory)] 23 | [ValidateSet("Version", "VendorPrefix", "PathSafe", "Freeform", "Hex", "Guid", "Base64", "OneOrEmpty")] 24 | [string]$PatternType 25 | ) 26 | 27 | Write-Verbose "InputObject '$InputObject'" 28 | Write-Verbose "PatternType '$PatternType'" 29 | 30 | # Allow basic punctuations only. All strings must be single-quote safe. 31 | $AllowedPatterns = @{ 32 | "Version" = '^[0-9]+(\.[0-9]+){0,3}$' 33 | "VendorPrefix" = '^[a-z][a-z0-9]{0,3}$' 34 | "PathSafe" = '^[a-z0-9.,-_ ]*$' 35 | "Freeform" = '^[a-z0-9().,-_ ]*$' 36 | "Hex" = '^[a-f0-9]*$' 37 | "Guid" = '^\{[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}$' 38 | "Base64" = '^[a-z0-9+/=\r\n\t ]*$' 39 | "OneOrEmpty" = '^1?$' 40 | } 41 | 42 | if ($InputObject -isnot [string]) { 43 | throw "Invalid input" 44 | } 45 | $Pattern = $AllowedPatterns[$PatternType] 46 | if (!$PatternType) { 47 | throw "Invalid pattern type $PatternType" 48 | } 49 | 50 | if (!($InputObject -imatch $Pattern)) { 51 | throw "Invalid input for pattern type $PatternType" 52 | } 53 | 54 | return $InputObject 55 | } 56 | 57 | # Branding file is emitted as step summary. Don't put any secrets here! 58 | $content = @" 59 | `$Env:VENDOR_NAME = '$(Out-SafeString -PatternType PathSafe -InputObject $Env:VENDOR_NAME)' 60 | `$Env:PRODUCT_NAME = '$(Out-SafeString -PatternType PathSafe -InputObject $Env:PRODUCT_NAME)' 61 | `$Env:VENDOR_PREFIX = '$(Out-SafeString -PatternType VendorPrefix -InputObject $Env:VENDOR_PREFIX)' 62 | `$Env:COPYRIGHT = '$(Out-SafeString -PatternType Freeform -InputObject $Env:COPYRIGHT)' 63 | 64 | `$Env:FORCE_ACTIVATE = '$(Out-SafeString -PatternType OneOrEmpty -InputObject $Env:FORCE_ACTIVATE)' 65 | `$Env:FORCE_UNPLUG = '$(Out-SafeString -PatternType OneOrEmpty -InputObject $Env:FORCE_UNPLUG)' 66 | 67 | `$PackageVersions = @{ 68 | Product = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_Product)' 69 | xenbus = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xenbus)' 70 | xencons = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xencons)' 71 | xenhid = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xenhid)' 72 | xeniface = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xeniface)' 73 | xennet = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xennet)' 74 | xenvbd = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xenvbd)' 75 | xenvif = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xenvif)' 76 | xenvkbd = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_xenvkbd)' 77 | XenClean = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_XenClean)' 78 | XenBootFix = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_XenBootFix)' 79 | XenGuestAgent = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_XenGuestAgent)' 80 | XenTimeProvider = '$(Out-SafeString -PatternType Version -InputObject $Env:PackageVersions_XenTimeProvider)' 81 | } 82 | 83 | `$Env:MSI_UPGRADE_CODE_X86 = '$(Out-SafeString -PatternType Guid -InputObject $Env:MSI_UPGRADE_CODE_X86)' 84 | `$Env:MSI_UPGRADE_CODE_X64 = '$(Out-SafeString -PatternType Guid -InputObject $Env:MSI_UPGRADE_CODE_X64)' 85 | "@ 86 | Set-Content -Path $OutFile -Value $content -Force 87 | 88 | if ($AddSigner) { 89 | # Doesn't hurt to verify again. 90 | Out-SafeString -PatternType Base64 -InputObject $Env:SIGNER_PFX_BASE64 | Out-Null 91 | & "$PSScriptRoot\signer-ci.ps1" -OutFile $OutFile 92 | } 93 | 94 | Add-Content -Path $Env:GITHUB_STEP_SUMMARY -Value "Branding:", "``````", $content, "``````" -Force 95 | -------------------------------------------------------------------------------- /.github/workflows/build-drivers.yml: -------------------------------------------------------------------------------- 1 | name: build-drivers 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build-drivers: 9 | runs-on: windows-2022 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | - name: Add MSBuild 16 | uses: microsoft/setup-msbuild@v2 17 | - name: Configure branding 18 | run: .\scripts\branding-ci.ps1 -OutFile .\branding.ps1 19 | env: 20 | VENDOR_NAME: ${{vars.VENDOR_NAME}} 21 | PRODUCT_NAME: ${{vars.PRODUCT_NAME}} 22 | VENDOR_PREFIX: ${{vars.VENDOR_PREFIX}} 23 | COPYRIGHT: ${{vars.COPYRIGHT}} 24 | FORCE_ACTIVATE: ${{vars.FORCE_ACTIVATE}} 25 | FORCE_UNPLUG: ${{vars.FORCE_UNPLUG}} 26 | PackageVersions_Product: ${{vars.PackageVersions_Product}}.${{github.run_number}} 27 | PackageVersions_xenbus: ${{vars.PackageVersions_xenbus}}.${{github.run_number}} 28 | PackageVersions_xencons: ${{vars.PackageVersions_xencons}}.${{github.run_number}} 29 | PackageVersions_xenhid: ${{vars.PackageVersions_xenhid}}.${{github.run_number}} 30 | PackageVersions_xeniface: ${{vars.PackageVersions_xeniface}}.${{github.run_number}} 31 | PackageVersions_xennet: ${{vars.PackageVersions_xennet}}.${{github.run_number}} 32 | PackageVersions_xenvbd: ${{vars.PackageVersions_xenvbd}}.${{github.run_number}} 33 | PackageVersions_xenvif: ${{vars.PackageVersions_xenvif}}.${{github.run_number}} 34 | PackageVersions_xenvkbd: ${{vars.PackageVersions_xenvkbd}}.${{github.run_number}} 35 | PackageVersions_XenClean: ${{vars.PackageVersions_XenClean}}.${{github.run_number}} 36 | PackageVersions_XenBootFix: ${{vars.PackageVersions_XenBootFix}}.${{github.run_number}} 37 | PackageVersions_XenGuestAgent: ${{vars.PackageVersions_XenGuestAgent}}.${{github.run_number}} 38 | PackageVersions_XenTimeProvider: ${{vars.PackageVersions_XenTimeProvider}}.${{github.run_number}} 39 | MSI_UPGRADE_CODE_X86: ${{vars.MSI_UPGRADE_CODE_X86}} 40 | MSI_UPGRADE_CODE_X64: ${{vars.MSI_UPGRADE_CODE_X64}} 41 | - name: Build drivers 42 | run: .\build-drivers.ps1 -Configuration Release -Platform x64 43 | - name: Catalog drivers 44 | run: | 45 | Import-Module .\scripts\artifact.psm1 -Force 46 | Get-ArtifactCatalog -Path .\installer\output -Include *.sys, *.dll, *.exe, *.inf | 47 | Export-Csv -NoTypeInformation drivers-catalog.csv 48 | - name: Upload drivers 49 | id: upload 50 | uses: actions/upload-artifact@v5 51 | with: 52 | path: installer/output/ 53 | name: drivers 54 | - name: Upload drivers catalog 55 | uses: actions/upload-artifact@v5 56 | with: 57 | path: drivers-catalog.csv 58 | name: drivers-catalog 59 | outputs: 60 | subject-name: drivers 61 | subject-digest: sha256:${{steps.upload.outputs.artifact-digest}} 62 | catalog-name: drivers-catalog 63 | 64 | attest-drivers: 65 | needs: [build-drivers] 66 | uses: ./.github/workflows/attest.yml 67 | permissions: 68 | id-token: write 69 | contents: read 70 | attestations: write 71 | with: 72 | subject-name: ${{needs.build-drivers.outputs.subject-name}} 73 | subject-digest: ${{needs.build-drivers.outputs.subject-digest}} 74 | catalog-name: ${{needs.build-drivers.outputs.catalog-name}} 75 | 76 | selfsign-drivers: 77 | needs: [build-drivers] 78 | runs-on: windows-latest 79 | steps: 80 | # for the sign scripts 81 | - name: Checkout 82 | uses: actions/checkout@v4 83 | - name: Download artifact 84 | uses: actions/download-artifact@v6 85 | with: 86 | path: installer/output/ 87 | name: drivers 88 | - name: Install certificate 89 | run: .\scripts\signer-ci.ps1 -OutFile .\branding.ps1 90 | env: 91 | SIGNER_PFX_BASE64: ${{secrets.SIGNER_PFX_BASE64}} 92 | - name: Sign artifact 93 | run: | 94 | . .\branding.ps1 95 | . .\scripts\sign.ps1 96 | Set-SignerFileSignature (Get-ChildItem .\installer\output -File -Recurse -Include *.sys, *.dll, *.exe, *.cat) 97 | - name: Clean up certificates 98 | if: always() 99 | run: Remove-Item Cert:\CurrentUser\My\* -ErrorAction SilentlyContinue 100 | - name: Upload artifact 101 | id: upload 102 | uses: actions/upload-artifact@v5 103 | with: 104 | path: installer/output/ 105 | name: drivers-signed 106 | -------------------------------------------------------------------------------- /.github/workflows/build-timeprovider.yml: -------------------------------------------------------------------------------- 1 | name: build-timeprovider 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build-timeprovider: 9 | runs-on: windows-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | - name: Add MSBuild 16 | uses: microsoft/setup-msbuild@v2 17 | - name: Configure branding 18 | run: .\scripts\branding-ci.ps1 -OutFile .\branding.ps1 19 | env: 20 | VENDOR_NAME: ${{vars.VENDOR_NAME}} 21 | PRODUCT_NAME: ${{vars.PRODUCT_NAME}} 22 | VENDOR_PREFIX: ${{vars.VENDOR_PREFIX}} 23 | COPYRIGHT: ${{vars.COPYRIGHT}} 24 | FORCE_ACTIVATE: ${{vars.FORCE_ACTIVATE}} 25 | FORCE_UNPLUG: ${{vars.FORCE_UNPLUG}} 26 | PackageVersions_Product: ${{vars.PackageVersions_Product}}.${{github.run_number}} 27 | PackageVersions_xenbus: ${{vars.PackageVersions_xenbus}}.${{github.run_number}} 28 | PackageVersions_xencons: ${{vars.PackageVersions_xencons}}.${{github.run_number}} 29 | PackageVersions_xenhid: ${{vars.PackageVersions_xenhid}}.${{github.run_number}} 30 | PackageVersions_xeniface: ${{vars.PackageVersions_xeniface}}.${{github.run_number}} 31 | PackageVersions_xennet: ${{vars.PackageVersions_xennet}}.${{github.run_number}} 32 | PackageVersions_xenvbd: ${{vars.PackageVersions_xenvbd}}.${{github.run_number}} 33 | PackageVersions_xenvif: ${{vars.PackageVersions_xenvif}}.${{github.run_number}} 34 | PackageVersions_xenvkbd: ${{vars.PackageVersions_xenvkbd}}.${{github.run_number}} 35 | PackageVersions_XenClean: ${{vars.PackageVersions_XenClean}}.${{github.run_number}} 36 | PackageVersions_XenBootFix: ${{vars.PackageVersions_XenBootFix}}.${{github.run_number}} 37 | PackageVersions_XenGuestAgent: ${{vars.PackageVersions_XenGuestAgent}}.${{github.run_number}} 38 | PackageVersions_XenTimeProvider: ${{vars.PackageVersions_XenTimeProvider}}.${{github.run_number}} 39 | MSI_UPGRADE_CODE_X86: ${{vars.MSI_UPGRADE_CODE_X86}} 40 | MSI_UPGRADE_CODE_X64: ${{vars.MSI_UPGRADE_CODE_X64}} 41 | - name: Build time provider 42 | run: .\build-timeprovider.ps1 -Configuration Release -Platform x64 43 | - name: Catalog time provider 44 | run: | 45 | Import-Module .\scripts\artifact.psm1 -Force 46 | Get-ArtifactCatalog -Path .\xentimeprovider\x64\Release -Include *.dll | 47 | Export-Csv -NoTypeInformation timeprovider-catalog.csv 48 | - name: Upload time provider 49 | id: upload 50 | uses: actions/upload-artifact@v5 51 | with: 52 | path: xentimeprovider/x64/Release/ 53 | name: timeprovider 54 | - name: Upload timeprovider catalog 55 | uses: actions/upload-artifact@v5 56 | with: 57 | path: timeprovider-catalog.csv 58 | name: timeprovider-catalog 59 | outputs: 60 | subject-name: timeprovider 61 | subject-digest: sha256:${{steps.upload.outputs.artifact-digest}} 62 | catalog-name: timeprovider-catalog 63 | 64 | attest-timeprovider: 65 | needs: [build-timeprovider] 66 | uses: ./.github/workflows/attest.yml 67 | permissions: 68 | id-token: write 69 | contents: read 70 | attestations: write 71 | with: 72 | subject-name: ${{needs.build-timeprovider.outputs.subject-name}} 73 | subject-digest: ${{needs.build-timeprovider.outputs.subject-digest}} 74 | catalog-name: ${{needs.build-timeprovider.outputs.catalog-name}} 75 | 76 | selfsign-timeprovider: 77 | needs: [build-timeprovider] 78 | runs-on: windows-latest 79 | steps: 80 | # for the sign scripts 81 | - name: Checkout 82 | uses: actions/checkout@v4 83 | - name: Download artifact 84 | uses: actions/download-artifact@v6 85 | with: 86 | path: xentimeprovider/x64/Release/ 87 | name: timeprovider 88 | - name: Install certificate 89 | run: .\scripts\signer-ci.ps1 -OutFile .\branding.ps1 90 | env: 91 | SIGNER_PFX_BASE64: ${{secrets.SIGNER_PFX_BASE64}} 92 | - name: Sign artifact 93 | run: | 94 | . .\branding.ps1 95 | . .\scripts\sign.ps1 96 | Set-SignerFileSignature (Get-ChildItem .\xentimeprovider\x64\Release -File -Recurse -Include *.dll) 97 | - name: Clean up certificates 98 | if: always() 99 | run: Remove-Item Cert:\CurrentUser\My\* -ErrorAction SilentlyContinue 100 | - name: Upload artifact 101 | id: upload 102 | uses: actions/upload-artifact@v5 103 | with: 104 | path: xentimeprovider/x64/Release/ 105 | name: timeprovider-signed 106 | -------------------------------------------------------------------------------- /docs/build.md: -------------------------------------------------------------------------------- 1 | # Build guide 2 | 3 | ## Requirements 4 | 5 | * Visual Studio 2022 with the following features: 6 | * Desktop development with C++ workload 7 | * .NET desktop development workload 8 | * .NET Framework 4.6.2 targeting pack 9 | * MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest) 10 | * Windows Driver Kit extensions for Visual Studio (listed simply as "Windows Driver Kit" in the Visual Studio Installer) 11 | * Windows SDK for Windows 10/11 (tested with 10.0.22621 and 10.0.26100) 12 | * Windows Driver Kit matching your Windows SDK 13 | * Git for Windows 14 | * Rustup and latest Rust stable 15 | * [Microsoft SBOM Tool](https://github.com/microsoft/sbom-tool) available as `sbom.exe` in PATH (installable via WinGet) 16 | 17 | Windows SDK and WDK dependencies can be found on [Microsoft Learn](https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads). 18 | 19 | ## Branding and configuration 20 | 21 | You customize the driver and installer package by creating `branding.ps1` inside the repository root. 22 | 23 | See below for an example of the `branding.ps1` file. 24 | 25 | ```powershell 26 | $Env:VENDOR_NAME = 'Xen Project' 27 | $Env:PRODUCT_NAME = 'Xen' 28 | $Env:VENDOR_PREFIX = 'XP' 29 | $Env:COPYRIGHT = 'Copyright (c) Xen Project.' 30 | 31 | # These version numbers are used in multiple places. 32 | # You must have at least 2 version components (major.minor). 33 | # The third and fourth components will be 0-filled if absent, except in drivers where the revision will be the build time prefixed with "1". 34 | # For compatibility reasons, it's not recommended to have any version component exceed 255.255.65535.65535. 35 | $PackageVersions = @{ 36 | Product = '9.0.9000.0' 37 | xenbus = '9.0.9001.0' # defaults to product version 38 | xencons = '9.0.9002.0' # defaults to product version 39 | xenhid = '9.0.9003.0' # defaults to product version 40 | xeniface = '9.0.9004.0' # defaults to product version 41 | xennet = '9.0.9005.0' # defaults to product version 42 | xenvbd = '9.0.9006.0' # defaults to product version 43 | xenvif = '9.0.9007.0' # defaults to product version 44 | xenvkbd = '9.0.9008.0' # defaults to product version 45 | XenClean = '9.0.9009.0' # defaults to product version 46 | XenBootFix = '9.0.9010.0' # defaults to product version 47 | XenGuestAgent = '9.0.9011.0' # defaults to product version 48 | XenTimeProvider = '9.0.9012.0' # defaults to product version 49 | } 50 | 51 | # These variables influence the UpgradeCode property of generated MSI packages. 52 | # To avoid conflict between installers of different vendors, you must change 53 | # these values to a random GUID if building your own installer. 54 | $Env:MSI_UPGRADE_CODE_X86 = '{GUIDHERE-GUID-HERE-GUID-HEREGUIDHERE}' 55 | $Env:MSI_UPGRADE_CODE_X64 = '{GUIDHERE-GUID-HERE-GUID-HEREGUIDHERE}' 56 | 57 | $Env:SIGNER = "" 58 | ``` 59 | 60 | Specify `$Env:SIGNER` in `branding.ps1` to choose a specific signing certificate thumbprint or PFX path if necessary. 61 | (A test certificate will be used for the drivers otherwise) 62 | 63 | ## Building drivers 64 | 65 | Using the x64 Native Tools Command Prompt for VS 2022, navigate to this repository and build the drivers: 66 | 67 | ```powershell 68 | .\build-drivers.ps1 -Configuration Release -Platform x64 69 | ``` 70 | 71 | Output drivers will be collected in `installer\output`. 72 | 73 | If you need to sign the drivers externally (e.g. WHQL signatures), you must replace the drivers found here with your own signed binaries. 74 | These binaries should be located at `installer\output\\\` for each driver included in the package. 75 | 76 | ## Building the Rust-based Windows guest agent 77 | 78 | Simply run the command: 79 | 80 | ```powershell 81 | .\build-guestagent.ps1 -Configuration release 82 | ``` 83 | 84 | The binaries are located at `xen-guest-agent\target\`. 85 | 86 | ## Building the Xen time provider 87 | 88 | Run the command: 89 | 90 | ```powershell 91 | .\build-timeprovider.ps1 -Configuration Release 92 | ``` 93 | 94 | The binaries are located at `xentimeprovider\\`. 95 | 96 | ## Building the installer and release package 97 | 98 | Next, build the installer and XenClean packages: 99 | 100 | ```powershell 101 | .\build-installer.ps1 -Configuration Release -Platform x64 -Target Rebuild 102 | ``` 103 | 104 | By default, output files will be dropped in the `output\--` directory. 105 | You may specify `-ExportSymbols` or `-ExportCertificate` to include the debug symbols and signer certificate (along with testsigning scripts) in your output package respectively. 106 | -------------------------------------------------------------------------------- /docs/attestation.md: -------------------------------------------------------------------------------- 1 | # Artifact attestation 2 | 3 | Artifact attestation helps establish a link between the built binaries and their corresponding source code. 4 | In the context of XCP-ng Windows Guest Tools (WinPV for short), our artifact attestation aims to extend this link to signed binaries (WHQL-signed drivers, EV-signed guest agent, etc.) 5 | With this, even signed binaries benefit from an attested build pipeline. 6 | 7 | ## How to verify binaries 8 | 9 | ### Step 1: Obtain attested catalog 10 | 11 | Each release includes an attested catalog (`drivers-catalog.csv`, `guestagent-catalog.csv`). 12 | You can find them in the Assets section of each release. 13 | 14 | ### Step 2: Verify catalog attestation 15 | 16 | Use the GitHub CLI to verify the downloaded catalog for correspondence with its source code. 17 | For example: 18 | 19 | ``` 20 | $ gh attestation verify drivers-catalog.csv -R xcp-ng/win-pv-drivers --deny-self-hosted-runners 21 | Loaded digest sha256:... for file://drivers-catalog.csv 22 | Loaded 1 attestation from GitHub API 23 | 24 | The following policy criteria will be enforced: 25 | - Predicate type must match:..................... https://slsa.dev/provenance/v1 26 | - Source Repository Owner URI must match:........ https://github.com/xcp-ng 27 | - Source Repository URI must match:.............. https://github.com/xcp-ng/win-pv-drivers 28 | - Subject Alternative Name must match regex:..... (?i)^https://github.com/xcp-ng/win-pv-drivers/ 29 | - OIDC Issuer must match:........................ https://token.actions.githubusercontent.com 30 | - Action workflow Runner Environment must match : github-hosted 31 | 32 | ✓ Verification succeeded! 33 | 34 | The following 1 attestation matched the policy criteria 35 | 36 | - Attestation #1 37 | - Build repo:..... xcp-ng/win-pv-drivers 38 | - Build workflow:. .github/workflows/build-installer.yml@... 39 | - Signer repo:.... xcp-ng/win-pv-drivers 40 | - Signer workflow: .github/workflows/attest.yml@... 41 | ``` 42 | 43 | Alternatively, you can verify the catalog by using the attestation link provided with each release. 44 | 45 | ### Step 3: Verify final binaries 46 | 47 | Use the [artifact.psm1](/scripts/artifact.psm1) PowerShell module to verify the signed binaries: 48 | 49 | ```powershell 50 | > Import-Module scripts\artifact.psm1 -Force 51 | 52 | > Test-ArtifactCatalog -TrustedCatalog (Import-Csv .\drivers-catalog.csv) -ComparePath .\drivers-signed\xenbus -CatalogPrefix .\xenbus\x64\Release -Include *.sys, *.dll, *.exe, *.inf 53 | ``` 54 | 55 | **Tip**: You can verify parts of the catalog by choosing `CatalogPrefix` accordingly. 56 | 57 | ## Implementation details 58 | 59 | WinPV's build and attestation machinery is provided by [GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds). 60 | 61 | Our build pipeline consists of 3 workflows: 62 | [build-drivers](/.github/workflows/build-drivers.yml), 63 | [build-guestagent](/.github/workflows/build-guestagent.yml) and 64 | [build-installer](/.github/workflows/build-installer.yml). 65 | The first two provide both unsigned (but attested) and testsigned binaries using a certificate supplied through secrets; 66 | the latter provides a complete installer using testsigned binaries. 67 | 68 | Here are the general build steps for each workflow: 69 | 70 | 1. Inject branding information 71 | 2. Execute build script 72 | 3. Generate catalog 73 | 4. Attest build and catalog 74 | 5. Sign build 75 | 76 | See below for explanations of specific elements of the build steps. 77 | 78 | ### Branding information and attestation safety 79 | 80 | In normal builds, branding is injected via [branding.ps1](/docs/build.md). 81 | However, this route is not secure against malicious instructions injected via the branding file. 82 | 83 | To mitigate this issue, we inject branding via a [sanitization script](/scripts/branding-ci.ps1), which verifies that branding variables follow an authorized pattern. Signing certificates are injected via a [similar script](/scripts/signer-ci.ps1). 84 | 85 | ### Catalog generation 86 | 87 | Standard attestation protects an artifact's flat hash. 88 | Once a binary is attested with GitHub, it cannot be modified whatsoever without breaking the attestation, including re-signing the binary with another certificate. 89 | 90 | To extend the attestation to re-signed binaries, we use a *catalog file* (not the same as Windows `.cat` catalogs). 91 | Catalog files are simple CSV files containing the relative path of each artifact and its Authenticode hash. 92 | We use Authenticode hashes instead of flat hashes to avoid breaking the attestation when re-signing. 93 | The catalog file itself is directly attested with GitHub Actions every build. 94 | 95 | ### Catalog verification 96 | 97 | `Test-ArtifactCatalog` provides catalog filtering, prefix stripping and path normalization features. 98 | See `artifact.psm1` for details. 99 | -------------------------------------------------------------------------------- /.github/workflows/build-guestagent.yml: -------------------------------------------------------------------------------- 1 | name: build-guestagent 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build-guestagent: 9 | runs-on: windows-latest 10 | env: 11 | X_CARGO_AUDITABLE_VERSION: 0.7.2 12 | steps: 13 | - name: Restore cargo auditable 14 | id: restore-cargo-auditable 15 | uses: actions/cache/restore@v4 16 | with: 17 | path: ~\.cargo\bin\ 18 | key: ${{runner.os}}-cargo-auditable-${{env.X_CARGO_AUDITABLE_VERSION}} 19 | - name: Install cargo auditable 20 | if: steps.restore-cargo-auditable.outputs.cache-hit != 'true' 21 | run: cargo install cargo-auditable@${{env.X_CARGO_AUDITABLE_VERSION}} --locked 22 | - name: Save cargo auditable 23 | if: steps.restore-cargo-auditable.outputs.cache-hit != 'true' 24 | uses: actions/cache/save@v4 25 | with: 26 | path: ~\.cargo\bin\ 27 | key: ${{steps.restore-cargo-auditable.outputs.cache-primary-key}} 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | with: 31 | submodules: true 32 | - name: Configure branding 33 | run: .\scripts\branding-ci.ps1 -OutFile .\branding.ps1 34 | env: 35 | VENDOR_NAME: ${{vars.VENDOR_NAME}} 36 | PRODUCT_NAME: ${{vars.PRODUCT_NAME}} 37 | VENDOR_PREFIX: ${{vars.VENDOR_PREFIX}} 38 | COPYRIGHT: ${{vars.COPYRIGHT}} 39 | PackageVersions_Product: ${{vars.PackageVersions_Product}}.${{github.run_number}} 40 | PackageVersions_xenbus: ${{vars.PackageVersions_xenbus}}.${{github.run_number}} 41 | PackageVersions_xencons: ${{vars.PackageVersions_xencons}}.${{github.run_number}} 42 | PackageVersions_xenhid: ${{vars.PackageVersions_xenhid}}.${{github.run_number}} 43 | PackageVersions_xeniface: ${{vars.PackageVersions_xeniface}}.${{github.run_number}} 44 | PackageVersions_xennet: ${{vars.PackageVersions_xennet}}.${{github.run_number}} 45 | PackageVersions_xenvbd: ${{vars.PackageVersions_xenvbd}}.${{github.run_number}} 46 | PackageVersions_xenvif: ${{vars.PackageVersions_xenvif}}.${{github.run_number}} 47 | PackageVersions_xenvkbd: ${{vars.PackageVersions_xenvkbd}}.${{github.run_number}} 48 | PackageVersions_XenClean: ${{vars.PackageVersions_XenClean}}.${{github.run_number}} 49 | PackageVersions_XenBootFix: ${{vars.PackageVersions_XenBootFix}}.${{github.run_number}} 50 | PackageVersions_XenGuestAgent: ${{vars.PackageVersions_XenGuestAgent}}.${{github.run_number}} 51 | PackageVersions_XenTimeProvider: ${{vars.PackageVersions_XenTimeProvider}}.${{github.run_number}} 52 | MSI_UPGRADE_CODE_X86: ${{vars.MSI_UPGRADE_CODE_X86}} 53 | MSI_UPGRADE_CODE_X64: ${{vars.MSI_UPGRADE_CODE_X64}} 54 | - name: Build guest agent 55 | run: .\build-guestagent.ps1 -Configuration release 56 | - name: Catalog guest agent 57 | run: | 58 | Import-Module .\scripts\artifact.psm1 -Force 59 | Get-ArtifactCatalog -Path .\xen-guest-agent\target\release -Include *.exe | 60 | Export-Csv -NoTypeInformation guestagent-catalog.csv 61 | - name: Upload artifact 62 | id: upload 63 | uses: actions/upload-artifact@v5 64 | with: 65 | name: guestagent 66 | path: | 67 | xen-guest-agent/target/release/*.exe 68 | xen-guest-agent/target/release/*.pdb 69 | - name: Upload guest agent catalog 70 | uses: actions/upload-artifact@v5 71 | with: 72 | path: guestagent-catalog.csv 73 | name: guestagent-catalog 74 | outputs: 75 | subject-name: guestagent 76 | subject-digest: sha256:${{steps.upload.outputs.artifact-digest}} 77 | catalog-name: guestagent-catalog 78 | 79 | attest-guestagent: 80 | needs: [build-guestagent] 81 | uses: ./.github/workflows/attest.yml 82 | permissions: 83 | id-token: write 84 | contents: read 85 | attestations: write 86 | with: 87 | subject-name: ${{needs.build-guestagent.outputs.subject-name}} 88 | subject-digest: ${{needs.build-guestagent.outputs.subject-digest}} 89 | catalog-name: ${{needs.build-guestagent.outputs.catalog-name}} 90 | 91 | selfsign-guestagent: 92 | needs: [build-guestagent] 93 | runs-on: windows-latest 94 | steps: 95 | # for the sign scripts 96 | - name: Checkout 97 | uses: actions/checkout@v4 98 | - name: Download artifact 99 | uses: actions/download-artifact@v6 100 | with: 101 | path: xen-guest-agent/target/release/ 102 | name: guestagent 103 | - name: Install certificate 104 | run: .\scripts\signer-ci.ps1 -OutFile .\branding.ps1 105 | env: 106 | SIGNER_PFX_BASE64: ${{secrets.SIGNER_PFX_BASE64}} 107 | - name: Sign artifact 108 | run: | 109 | . .\branding.ps1 110 | . .\scripts\sign.ps1 111 | Set-SignerFileSignature (Get-ChildItem .\xen-guest-agent\target\release -File -Recurse -Include *.dll, *.exe) 112 | - name: Clean up certificates 113 | if: always() 114 | run: Remove-Item Cert:\CurrentUser\My\* -ErrorAction SilentlyContinue 115 | - name: Upload artifact 116 | id: upload 117 | uses: actions/upload-artifact@v5 118 | with: 119 | path: xen-guest-agent/target/release/ 120 | name: guestagent-signed 121 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{cs,vb}] 2 | #### Naming styles #### 3 | 4 | # Naming rules 5 | 6 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 7 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 8 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 9 | 10 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 11 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 12 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 13 | 14 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 15 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 16 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 17 | 18 | # Symbol specifications 19 | 20 | dotnet_naming_symbols.interface.applicable_kinds = interface 21 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 22 | dotnet_naming_symbols.interface.required_modifiers = 23 | 24 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 25 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 26 | dotnet_naming_symbols.types.required_modifiers = 27 | 28 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 29 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 30 | dotnet_naming_symbols.non_field_members.required_modifiers = 31 | 32 | # Naming styles 33 | 34 | dotnet_naming_style.begins_with_i.required_prefix = I 35 | dotnet_naming_style.begins_with_i.required_suffix = 36 | dotnet_naming_style.begins_with_i.word_separator = 37 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 38 | 39 | dotnet_naming_style.pascal_case.required_prefix = 40 | dotnet_naming_style.pascal_case.required_suffix = 41 | dotnet_naming_style.pascal_case.word_separator = 42 | dotnet_naming_style.pascal_case.capitalization = pascal_case 43 | 44 | dotnet_naming_style.pascal_case.required_prefix = 45 | dotnet_naming_style.pascal_case.required_suffix = 46 | dotnet_naming_style.pascal_case.word_separator = 47 | dotnet_naming_style.pascal_case.capitalization = pascal_case 48 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 49 | tab_width = 4 50 | indent_size = 4 51 | end_of_line = crlf 52 | dotnet_style_coalesce_expression = true:suggestion 53 | dotnet_style_null_propagation = true:suggestion 54 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 55 | dotnet_style_prefer_auto_properties = true:silent 56 | dotnet_style_object_initializer = true:suggestion 57 | dotnet_style_collection_initializer = true:suggestion 58 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 59 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 60 | dotnet_style_prefer_conditional_expression_over_return = true:silent 61 | dotnet_style_explicit_tuple_names = true:suggestion 62 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 63 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 64 | dotnet_style_prefer_compound_assignment = true:suggestion 65 | dotnet_style_prefer_simplified_interpolation = true:suggestion 66 | dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion 67 | dotnet_style_namespace_match_folder = true:suggestion 68 | 69 | [*.cs] 70 | csharp_indent_labels = no_change 71 | csharp_space_around_binary_operators = before_and_after 72 | csharp_using_directive_placement = outside_namespace:silent 73 | csharp_prefer_simple_using_statement = true:suggestion 74 | csharp_prefer_braces = true:silent 75 | csharp_style_namespace_declarations = block_scoped:silent 76 | csharp_style_prefer_method_group_conversion = true:silent 77 | csharp_style_prefer_top_level_statements = true:silent 78 | csharp_style_prefer_primary_constructors = true:suggestion 79 | csharp_prefer_system_threading_lock = true:suggestion 80 | csharp_style_expression_bodied_methods = false:silent 81 | csharp_style_expression_bodied_constructors = false:silent 82 | csharp_style_expression_bodied_operators = false:silent 83 | csharp_style_expression_bodied_properties = true:silent 84 | csharp_style_expression_bodied_indexers = true:silent 85 | csharp_style_expression_bodied_accessors = true:silent 86 | csharp_style_expression_bodied_lambdas = true:silent 87 | csharp_style_expression_bodied_local_functions = false:silent 88 | csharp_style_throw_expression = true:suggestion 89 | csharp_style_prefer_null_check_over_type_check = true:suggestion 90 | csharp_prefer_simple_default_expression = true:suggestion 91 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 92 | csharp_style_prefer_index_operator = true:suggestion 93 | csharp_style_prefer_range_operator = true:suggestion 94 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 95 | csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion 96 | csharp_style_prefer_tuple_swap = true:suggestion 97 | csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion 98 | csharp_style_prefer_utf8_string_literals = true:suggestion 99 | csharp_style_inlined_variable_declaration = true:suggestion 100 | csharp_new_line_before_open_brace = none 101 | csharp_new_line_before_else = false 102 | csharp_new_line_before_catch = false 103 | csharp_new_line_before_finally = false 104 | -------------------------------------------------------------------------------- /XenDriverUtils/InfFile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Runtime.InteropServices; 6 | using Windows.Win32; 7 | using Windows.Win32.Devices.DeviceAndDriverInstallation; 8 | using Windows.Win32.Foundation; 9 | 10 | namespace XenDriverUtils { 11 | public class InfFile : SafeHandleMinusOneIsInvalid { 12 | public InfFile(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) { 13 | handle = existingHandle; 14 | } 15 | 16 | public static InfFile Open(string FileName, string InfClass, INF_STYLE InfStyle, out uint ErrorLine) { 17 | unsafe { 18 | var infHandle = (IntPtr)PInvoke.SetupOpenInfFile(FileName, InfClass, InfStyle, out ErrorLine); 19 | if (infHandle == HANDLE.INVALID_HANDLE_VALUE) { 20 | var err = Marshal.GetLastWin32Error(); 21 | throw new Win32Exception(err, $"SetupOpenInfFile {err}"); 22 | } 23 | return new InfFile(infHandle, true); 24 | } 25 | } 26 | 27 | private INFCONTEXT FindFirstLine(string section, string key) { 28 | if (string.IsNullOrEmpty(section)) { 29 | throw new ArgumentNullException("Need valid section name"); 30 | } 31 | unsafe { 32 | if (!PInvoke.SetupFindFirstLine(handle.ToPointer(), section, key, out var context)) { 33 | var err = Marshal.GetLastWin32Error(); 34 | throw new Win32Exception(err, $"Inf cannot find line {section}/{key} {err}"); 35 | } 36 | return context; 37 | } 38 | } 39 | 40 | private INFCONTEXT FindNextLine(INFCONTEXT contextIn) { 41 | unsafe { 42 | if (!PInvoke.SetupFindNextLine(contextIn, out var contextOut)) { 43 | var err = Marshal.GetLastWin32Error(); 44 | throw new Win32Exception(err, $"Inf cannot find next line {err}"); 45 | } 46 | return contextOut; 47 | } 48 | } 49 | 50 | private string GetLineText(ref INFCONTEXT context) { 51 | unsafe { 52 | fixed (INFCONTEXT* pContext = &context) { 53 | uint requiredChars; 54 | if (!PInvoke.SetupGetLineText(pContext, null, null, null, null, 0, &requiredChars)) { 55 | var err = Marshal.GetLastWin32Error(); 56 | throw new Win32Exception(err, $"Inf cannot get line size {err}"); 57 | } 58 | var mem = Marshal.AllocHGlobal((int)requiredChars * sizeof(char)); 59 | try { 60 | if (!PInvoke.SetupGetLineText( 61 | pContext, 62 | null, 63 | null, 64 | null, 65 | new PWSTR((char*)mem.ToPointer()), 66 | requiredChars, 67 | null)) { 68 | var err = Marshal.GetLastWin32Error(); 69 | throw new Win32Exception(err, $"Inf cannot get line {err}"); 70 | } 71 | return Marshal.PtrToStringUni(mem); 72 | } finally { 73 | Marshal.FreeHGlobal(mem); 74 | } 75 | } 76 | } 77 | } 78 | 79 | private string GetStringField(ref INFCONTEXT context, uint fieldIndex) { 80 | unsafe { 81 | fixed (INFCONTEXT* pContext = &context) { 82 | uint requiredSize; 83 | if (!PInvoke.SetupGetStringField(pContext, fieldIndex, null, 0, &requiredSize)) { 84 | var err = Marshal.GetLastWin32Error(); 85 | throw new Win32Exception(err, $"Inf cannot get field size {err}"); 86 | } 87 | var mem = Marshal.AllocHGlobal((int)requiredSize * sizeof(char)); 88 | try { 89 | if (!PInvoke.SetupGetStringField( 90 | pContext, 91 | fieldIndex, 92 | new PWSTR((char*)mem.ToPointer()), 93 | requiredSize, 94 | null)) { 95 | var err = Marshal.GetLastWin32Error(); 96 | throw new Win32Exception(err, $"Inf cannot get field {err}"); 97 | } 98 | return Marshal.PtrToStringUni(mem); 99 | } finally { 100 | Marshal.FreeHGlobal(mem); 101 | } 102 | } 103 | } 104 | } 105 | 106 | public string GetStringField(string section, string key, uint fieldIndex) { 107 | try { 108 | var line = FindFirstLine(section, key); 109 | return GetStringField(ref line, fieldIndex); 110 | } catch (Exception ex) { 111 | throw new KeyNotFoundException($"{section}/{key}", ex); 112 | } 113 | } 114 | 115 | protected override bool ReleaseHandle() { 116 | if (!IsInvalid) { 117 | unsafe { 118 | PInvoke.SetupCloseInfFile(handle.ToPointer()); 119 | } 120 | } 121 | return true; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /.github/workflows/build-installer.yml: -------------------------------------------------------------------------------- 1 | name: build-installer 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build-drivers: 8 | uses: ./.github/workflows/build-drivers.yml 9 | permissions: 10 | id-token: write 11 | contents: read 12 | attestations: write 13 | secrets: inherit 14 | 15 | build-guestagent: 16 | uses: ./.github/workflows/build-guestagent.yml 17 | permissions: 18 | id-token: write 19 | contents: read 20 | attestations: write 21 | secrets: inherit 22 | 23 | build-timeprovider: 24 | uses: ./.github/workflows/build-timeprovider.yml 25 | permissions: 26 | id-token: write 27 | contents: read 28 | attestations: write 29 | secrets: inherit 30 | 31 | build-installer: 32 | needs: 33 | - build-drivers 34 | - build-guestagent 35 | - build-timeprovider 36 | runs-on: windows-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | - name: Add SBOM tools 41 | run: | 42 | New-Item -Type Directory -Path $Env:RUNNER_TEMP/tools/ 43 | 44 | Invoke-WebRequest -Uri https://github.com/microsoft/sbom-tool/releases/download/v4.1.4/sbom-tool-win-x64.exe -OutFile $Env:RUNNER_TEMP\tools\sbom.exe 45 | if ((Get-FileHash $Env:RUNNER_TEMP\tools\sbom.exe -Algorithm SHA256).Hash -ine "9ba208cbec21c93b6d1ba01f50681a47d9d14d302f59b33e505b220b89aa0363") { 46 | throw "sbom-tool failed verification" 47 | } 48 | 49 | Invoke-WebRequest -Uri https://github.com/anchore/syft/releases/download/v1.38.0/syft_1.38.0_windows_amd64.zip -OutFile $Env:RUNNER_TEMP\tools\syft.zip 50 | if ((Get-FileHash $Env:RUNNER_TEMP\tools\syft.zip -Algorithm SHA256).Hash -ine "11a3de4c82b001dae3b69e336f1b89391b676b3e74cead73790c97ff57445941") { 51 | throw "syft failed verification" 52 | } 53 | Expand-Archive $Env:RUNNER_TEMP\tools\syft.zip -DestinationPath $Env:RUNNER_TEMP\tools\syft -Force 54 | Copy-Item $Env:RUNNER_TEMP\tools\syft\syft.exe $Env:RUNNER_TEMP\tools\syft.exe -Force 55 | 56 | Add-Content -Path $Env:GITHUB_PATH -Value "$Env:RUNNER_TEMP/tools" -Force 57 | - name: Download Wix 58 | run: | 59 | $ErrorActionPreference = 'Stop' 60 | Remove-Item -Path .\deps -Recurse -Force -ErrorAction SilentlyContinue 61 | Invoke-WebRequest -Uri $Env:WIX_ZIP_URI -OutFile .\wix.zip 62 | $wixZipHash = (Get-FileHash .\wix.zip).Hash 63 | if ($wixZipHash -ine $Env:WIX_ZIP_SHA256) { 64 | throw "wix.zip failed verification" 65 | } 66 | Add-Content -Path $Env:GITHUB_STEP_SUMMARY -Value "wix.zip: ``$wixZipHash``" -Force 67 | Expand-Archive -Path .\wix.zip -DestinationPath .\deps\wix\ -Force 68 | env: 69 | # Dependency download links hardcoded for transparency. 70 | WIX_ZIP_URI: https://github.com/xcp-ng/wix-builder/releases/download/v6.0.1-xcpng.0/artifacts.zip 71 | WIX_ZIP_SHA256: 4d93cd40e778c1b9bebee7b0f2b1232c8e87f470a50eac212e2ed3bebad6e5b8 72 | - name: Download drivers 73 | uses: actions/download-artifact@v6 74 | with: 75 | name: drivers-signed 76 | path: installer/output/ 77 | - name: Download guest agent 78 | uses: actions/download-artifact@v6 79 | with: 80 | name: guestagent-signed 81 | path: xen-guest-agent/target/release/ 82 | - name: Download time provider 83 | uses: actions/download-artifact@v6 84 | with: 85 | name: timeprovider-signed 86 | path: xentimeprovider/x64/Release/ 87 | - name: Add MSBuild 88 | uses: microsoft/setup-msbuild@v2 89 | - name: Configure branding 90 | run: .\scripts\branding-ci.ps1 -AddSigner -OutFile .\branding.ps1 91 | env: 92 | VENDOR_NAME: ${{vars.VENDOR_NAME}} 93 | PRODUCT_NAME: ${{vars.PRODUCT_NAME}} 94 | VENDOR_PREFIX: ${{vars.VENDOR_PREFIX}} 95 | COPYRIGHT: ${{vars.COPYRIGHT}} 96 | PackageVersions_Product: ${{vars.PackageVersions_Product}}.${{github.run_number}} 97 | PackageVersions_xenbus: ${{vars.PackageVersions_xenbus}}.${{github.run_number}} 98 | PackageVersions_xencons: ${{vars.PackageVersions_xencons}}.${{github.run_number}} 99 | PackageVersions_xenhid: ${{vars.PackageVersions_xenhid}}.${{github.run_number}} 100 | PackageVersions_xeniface: ${{vars.PackageVersions_xeniface}}.${{github.run_number}} 101 | PackageVersions_xennet: ${{vars.PackageVersions_xennet}}.${{github.run_number}} 102 | PackageVersions_xenvbd: ${{vars.PackageVersions_xenvbd}}.${{github.run_number}} 103 | PackageVersions_xenvif: ${{vars.PackageVersions_xenvif}}.${{github.run_number}} 104 | PackageVersions_xenvkbd: ${{vars.PackageVersions_xenvkbd}}.${{github.run_number}} 105 | PackageVersions_XenClean: ${{vars.PackageVersions_XenClean}}.${{github.run_number}} 106 | PackageVersions_XenBootFix: ${{vars.PackageVersions_XenBootFix}}.${{github.run_number}} 107 | PackageVersions_XenGuestAgent: ${{vars.PackageVersions_XenGuestAgent}}.${{github.run_number}} 108 | PackageVersions_XenTimeProvider: ${{vars.PackageVersions_XenTimeProvider}}.${{github.run_number}} 109 | MSI_UPGRADE_CODE_X86: ${{vars.MSI_UPGRADE_CODE_X86}} 110 | MSI_UPGRADE_CODE_X64: ${{vars.MSI_UPGRADE_CODE_X64}} 111 | # Signer cert must be injected here since unlike the drivers/guestagent, 112 | # we can't externally sign the installer package 113 | SIGNER_PFX_BASE64: ${{secrets.SIGNER_PFX_BASE64}} 114 | - name: Build installer 115 | id: build 116 | # this workflow is always testsigned, so -ExportCertificate is appropriate 117 | run: .\build-installer.ps1 -Configuration Release -Platform x64 -ExportSymbols -ExportExtras -ExportCertificate -Sbom 118 | - name: Clean up certificates 119 | if: always() 120 | run: Remove-Item Cert:\CurrentUser\My\* -ErrorAction SilentlyContinue 121 | - name: Upload installer 122 | id: upload 123 | uses: actions/upload-artifact@v5 124 | with: 125 | path: output/ 126 | name: installer 127 | - name: Upload XenClean 128 | uses: actions/upload-artifact@v5 129 | with: 130 | path: output/${{steps.build.outputs.ReleaseTag}}/package/XenClean/ 131 | name: XenClean 132 | outputs: 133 | subject-name: installer 134 | subject-digest: sha256:${{steps.upload.outputs.artifact-digest}} 135 | 136 | attest-installer: 137 | needs: [build-installer] 138 | uses: ./.github/workflows/attest.yml 139 | permissions: 140 | id-token: write 141 | contents: read 142 | attestations: write 143 | with: 144 | subject-name: ${{needs.build-installer.outputs.subject-name}} 145 | subject-digest: ${{needs.build-installer.outputs.subject-digest}} 146 | -------------------------------------------------------------------------------- /installer/installer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35312.102 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "XenDrivers", "XenDrivers.wixproj", "{5EBD6640-0CBA-435D-BEBC-93182D18213D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DriverInstallCustomAction", "..\DriverInstallCustomAction\DriverInstallCustomAction.csproj", "{EF921460-97E4-4EF0-92D5-BD9ABA124022}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XenDriverUtils", "..\XenDriverUtils\XenDriverUtils.csproj", "{1118970F-0929-4D5F-BDB2-54B2EDF42EF6}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XenClean", "..\XenClean\XenClean.csproj", "{0A953795-5150-4B83-85F9-FB906C8A6297}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{84018E1C-644B-466C-9788-939A31987B36}" 15 | ProjectSection(SolutionItems) = preProject 16 | ..\branding.ps1 = ..\branding.ps1 17 | EndProjectSection 18 | EndProject 19 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XenBootFix", "..\XenBootFix\XenBootFix.vcxproj", "{018433AB-F981-4625-8E23-6B3F22B7E5CF}" 20 | EndProject 21 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SBUpdate", "..\SBUpdate\SBUpdate.vcxproj", "{0972C789-7FA6-4ECC-A40B-71192B1BBF29}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|ARM64 = Debug|ARM64 26 | Debug|x64 = Debug|x64 27 | Debug|x86 = Debug|x86 28 | Release|ARM64 = Release|ARM64 29 | Release|x64 = Release|x64 30 | Release|x86 = Release|x86 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|ARM64.ActiveCfg = Debug|ARM64 34 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|ARM64.Build.0 = Debug|ARM64 35 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|x64.ActiveCfg = Debug|x64 36 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|x64.Build.0 = Debug|x64 37 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|x86.ActiveCfg = Debug|x86 38 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Debug|x86.Build.0 = Debug|x86 39 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|ARM64.ActiveCfg = Release|ARM64 40 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|ARM64.Build.0 = Release|ARM64 41 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|x64.ActiveCfg = Release|x64 42 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|x64.Build.0 = Release|x64 43 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|x86.ActiveCfg = Release|x86 44 | {5EBD6640-0CBA-435D-BEBC-93182D18213D}.Release|x86.Build.0 = Release|x86 45 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|ARM64.ActiveCfg = Debug|ARM64 46 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|ARM64.Build.0 = Debug|ARM64 47 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|x64.ActiveCfg = Debug|x64 48 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|x64.Build.0 = Debug|x64 49 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|x86.ActiveCfg = Debug|x86 50 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Debug|x86.Build.0 = Debug|x86 51 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|ARM64.ActiveCfg = Release|ARM64 52 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|ARM64.Build.0 = Release|ARM64 53 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|x64.ActiveCfg = Release|x64 54 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|x64.Build.0 = Release|x64 55 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|x86.ActiveCfg = Release|x86 56 | {EF921460-97E4-4EF0-92D5-BD9ABA124022}.Release|x86.Build.0 = Release|x86 57 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|ARM64.ActiveCfg = Debug|ARM64 58 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|ARM64.Build.0 = Debug|ARM64 59 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|x64.ActiveCfg = Debug|x64 60 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|x64.Build.0 = Debug|x64 61 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|x86.ActiveCfg = Debug|x86 62 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Debug|x86.Build.0 = Debug|x86 63 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|ARM64.ActiveCfg = Release|ARM64 64 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|ARM64.Build.0 = Release|ARM64 65 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|x64.ActiveCfg = Release|x64 66 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|x64.Build.0 = Release|x64 67 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|x86.ActiveCfg = Release|x86 68 | {1118970F-0929-4D5F-BDB2-54B2EDF42EF6}.Release|x86.Build.0 = Release|x86 69 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|ARM64.ActiveCfg = Debug|ARM64 70 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|ARM64.Build.0 = Debug|ARM64 71 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|x64.ActiveCfg = Debug|x64 72 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|x64.Build.0 = Debug|x64 73 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|x86.ActiveCfg = Debug|x86 74 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Debug|x86.Build.0 = Debug|x86 75 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|ARM64.ActiveCfg = Release|ARM64 76 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|ARM64.Build.0 = Release|ARM64 77 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|x64.ActiveCfg = Release|x64 78 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|x64.Build.0 = Release|x64 79 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|x86.ActiveCfg = Release|x86 80 | {0A953795-5150-4B83-85F9-FB906C8A6297}.Release|x86.Build.0 = Release|x86 81 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|ARM64.ActiveCfg = Debug|x64 82 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|ARM64.Build.0 = Debug|x64 83 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|x64.ActiveCfg = Debug|x64 84 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|x64.Build.0 = Debug|x64 85 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|x86.ActiveCfg = Debug|Win32 86 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Debug|x86.Build.0 = Debug|Win32 87 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|ARM64.ActiveCfg = Release|x64 88 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|ARM64.Build.0 = Release|x64 89 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|x64.ActiveCfg = Release|x64 90 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|x64.Build.0 = Release|x64 91 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|x86.ActiveCfg = Release|Win32 92 | {018433AB-F981-4625-8E23-6B3F22B7E5CF}.Release|x86.Build.0 = Release|Win32 93 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Debug|ARM64.ActiveCfg = Debug|x64 94 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Debug|x64.ActiveCfg = Debug|x64 95 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Debug|x86.ActiveCfg = Debug|Win32 96 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Release|ARM64.ActiveCfg = Release|x64 97 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Release|x64.ActiveCfg = Release|x64 98 | {0972C789-7FA6-4ECC-A40B-71192B1BBF29}.Release|x86.ActiveCfg = Release|Win32 99 | EndGlobalSection 100 | GlobalSection(SolutionProperties) = preSolution 101 | HideSolutionNode = FALSE 102 | EndGlobalSection 103 | GlobalSection(ExtensibilityGlobals) = postSolution 104 | SolutionGuid = {E3EC35C1-B06B-42BF-BAF1-4AF0840DBB1A} 105 | EndGlobalSection 106 | EndGlobal 107 | -------------------------------------------------------------------------------- /DriverInstallCustomAction/DriverActions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using Windows.Win32; 6 | using Windows.Win32.Devices.DeviceAndDriverInstallation; 7 | using Windows.Win32.Foundation; 8 | using WixToolset.Dtf.WindowsInstaller; 9 | using XenDriverUtils; 10 | 11 | namespace XenInstCA { 12 | internal class DriverData { 13 | public string DriverName { get; set; } 14 | public string InfPath { get; set; } 15 | } 16 | 17 | public class DriverActions { 18 | private static DriverData GetDriverData(Session session) { 19 | if (!session.CustomActionData.TryGetValue("Driver", out var driverName)) return null; 20 | if (!session.CustomActionData.TryGetValue("Inf", out var infPath)) return null; 21 | Logger.Log($"driverName {driverName} infPath {infPath}"); 22 | return new DriverData() { 23 | DriverName = driverName, 24 | InfPath = infPath, 25 | }; 26 | } 27 | 28 | [CustomAction] 29 | public static ActionResult DriverInstall(Session session) { 30 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 31 | 32 | var driver = GetDriverData(session); 33 | if (driver == null) { 34 | return ActionResult.Success; 35 | } 36 | 37 | if (CustomActionUtils.ReportAction( 38 | session, 39 | $"{driver.DriverName}Install", 40 | driver.DriverName) == MessageResult.Cancel) { 41 | return ActionResult.UserExit; 42 | } 43 | Logger.Log($"Installing {driver.DriverName} inf {driver.InfPath}"); 44 | 45 | DriverUtils.InstallDriver(driver.InfPath, out var needsReboot); 46 | if (needsReboot) { 47 | CustomActionUtils.ScheduleReboot(); 48 | } 49 | return ActionResult.Success; 50 | } 51 | 52 | [CustomAction] 53 | public static ActionResult DriverInstallRollback(Session session) { 54 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 55 | DriverUninstall(session); 56 | return ActionResult.Success; 57 | } 58 | 59 | [CustomAction] 60 | public static ActionResult DriverUninstall(Session session) { 61 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 62 | 63 | bool needsReboot = false; 64 | var driver = GetDriverData(session); 65 | if (driver == null) { 66 | return ActionResult.Success; 67 | } 68 | 69 | if (CustomActionUtils.ReportAction( 70 | session, 71 | $"{driver.DriverName}Uninstall", 72 | driver.DriverName) == MessageResult.Cancel) { 73 | return ActionResult.UserExit; 74 | } 75 | 76 | Logger.Log($"Uninstalling {driver.DriverName} inf {driver.InfPath}"); 77 | if (!XenDeviceInfo.KnownDevices.TryGetValue(driver.DriverName, out var xenInfo)) { 78 | Logger.Log($"Unknown driver {driver.DriverName}"); 79 | return ActionResult.Success; 80 | } 81 | 82 | var devInfo = PInvoke.SetupDiGetClassDevs( 83 | xenInfo.ClassGuid, 84 | null, 85 | HWND.Null, 86 | xenInfo.ClassGuid.HasValue ? 0 : SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES); 87 | var collectedInfPaths = new HashSet(StringComparer.OrdinalIgnoreCase); 88 | 89 | foreach (var devInfoData in DriverUtils.EnumerateDevices(devInfo)) { 90 | List hardwareIds = DriverUtils.GetDeviceHardwareAndCompatibleIds(devInfo, devInfoData); 91 | if (hardwareIds.All(x => !xenInfo.MatchesId(x, checkKnown: true, checkIncompatible: false))) { 92 | continue; 93 | } 94 | 95 | var instanceId = DriverUtils.GetDeviceInstanceId(devInfo, devInfoData); 96 | if (instanceId != null) { 97 | Logger.Log($"Found {driver.DriverName} device: {instanceId}"); 98 | } else { 99 | Logger.Log($"Found {driver.DriverName} device"); 100 | } 101 | 102 | var infName = DriverUtils.GetDeviceDriverInfPath(devInfo, devInfoData); 103 | Logger.Log($"Current inf path: {infName}"); 104 | if (!string.IsNullOrEmpty(infName) 105 | && infName.StartsWith("oem", StringComparison.OrdinalIgnoreCase)) { 106 | collectedInfPaths.Add(infName); 107 | } 108 | 109 | try { 110 | DriverUtils.UninstallDevice(devInfo, devInfoData, out var thisNeedsReboot); 111 | needsReboot |= thisNeedsReboot; 112 | } catch (Exception ex) { 113 | Logger.Log($"Cannot uninstall device: {ex.Message}"); 114 | } 115 | } 116 | 117 | if (collectedInfPaths.Count > 0) { 118 | foreach (var oemInfName in collectedInfPaths) { 119 | try { 120 | DriverUtils.UninstallDriver(oemInfName); 121 | } catch (Exception ex) { 122 | Logger.Log($"Cannot uninstall driver {oemInfName}: {ex.Message}"); 123 | } 124 | } 125 | } 126 | // Why uninstall everything during DriverInstall? 127 | // Some older drivers (e.g. old XCP-ng drivers) don't like it when downgraded from a newer version. 128 | // Remove them all just to be sure. 129 | // We should arguably require running XenClean first but this covers cases where older drivers are installed 130 | // after ours. 131 | DriverUtils.UninstallDriverByNames(driver.DriverName); 132 | 133 | if (needsReboot) { 134 | CustomActionUtils.ScheduleReboot(); 135 | } 136 | return ActionResult.Success; 137 | } 138 | 139 | [CustomAction] 140 | public static ActionResult DriverUninstallRollback(Session session) { 141 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 142 | DriverInstall(session); 143 | return ActionResult.Success; 144 | } 145 | 146 | [CustomAction] 147 | public static ActionResult DriverWaitInstallFinish(Session session) { 148 | using var logScope = new LoggerScope(new MsiSessionLogger(session)); 149 | CustomActionUtils.ReportAction(session, $"XenWaitDriverInstall", ""); 150 | // wait for up to 1 minute until all pending installations are done 151 | for (int i = 0; i < 5; i++) { 152 | // wait twice just to be sure that Windows doesn't decide to install something a second time 153 | DriverUtils.WaitNoPendingInstallEvents(5000); 154 | Thread.Sleep(2000); 155 | if (DriverUtils.WaitNoPendingInstallEvents(5000)) { 156 | break; 157 | } 158 | } 159 | return ActionResult.Success; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # CodeQL 366 | *.sarif 367 | *.dvl.xml 368 | 369 | .build_number 370 | /branding.ps1 371 | Branding.wxi 372 | Branding.cs 373 | /package/ 374 | /XenBootFix/Branding.inc 375 | /package-* 376 | output/ 377 | deps/ 378 | -------------------------------------------------------------------------------- /XenDriverUtils/XenCleanup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using Windows.Win32; 7 | using Windows.Win32.Foundation; 8 | 9 | namespace XenDriverUtils { 10 | public class ThirdPartyStorageDriver { 11 | public string DriverInfPath { get; set; } 12 | public string Service { get; set; } 13 | } 14 | 15 | public class XenCleanup { 16 | static readonly IReadOnlyList StorageClasses = new List() { 17 | PInvoke.GUID_DEVCLASS_HDC, 18 | PInvoke.GUID_DEVCLASS_SCSIADAPTER, 19 | }; 20 | 21 | public static List Find3PStorageDrivers() { 22 | var found3PDrivers = new List(); 23 | foreach (var classGuid in StorageClasses) { 24 | var devInfo = PInvoke.SetupDiGetClassDevs(classGuid, null, HWND.Null, 0); 25 | foreach (var devInfoData in DriverUtils.EnumerateDevices(devInfo) 26 | .Where(x => DriverUtils.GetDeviceEnumeratorName(devInfo, x) == "PCI")) { 27 | var driverPath = DriverUtils.GetDeviceDriverInfPath(devInfo, devInfoData); 28 | if (driverPath.StartsWith("oem", StringComparison.OrdinalIgnoreCase)) { 29 | found3PDrivers.Add(new ThirdPartyStorageDriver() { 30 | DriverInfPath = driverPath, 31 | Service = DriverUtils.GetDeviceService(devInfo, devInfoData), 32 | }); 33 | } 34 | } 35 | } 36 | return found3PDrivers; 37 | } 38 | 39 | private static readonly List XenfiltClasses = new() { 40 | PInvoke.GUID_DEVCLASS_HDC, 41 | PInvoke.GUID_DEVCLASS_SYSTEM, 42 | }; 43 | 44 | private static readonly List FilterValueList = new() { 45 | "LowerFilters", 46 | "UpperFilters", 47 | }; 48 | 49 | private static readonly List FilterNameList = new() { 50 | "xenfilt", 51 | "scsifilt", 52 | }; 53 | 54 | public static void XenfiltClassCleanup() { 55 | using var classKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Class", true); 56 | if (classKey == null) { 57 | return; 58 | } 59 | foreach (var classGuid in XenfiltClasses) { 60 | using var classSubkey = classKey.OpenSubKey(classGuid.ToString("B"), true); 61 | if (classSubkey == null) { 62 | continue; 63 | } 64 | foreach (var filterValue in FilterValueList) { 65 | try { 66 | if (classSubkey.GetValueKind(filterValue) == RegistryValueKind.MultiString) { 67 | var filters = (string[])classSubkey.GetValue(filterValue); 68 | Logger.Log($"Class filters for {classGuid}: {string.Join(",", filters)}"); 69 | var newFilters = filters.Where(x => !FilterNameList 70 | .Contains(x, StringComparer.OrdinalIgnoreCase)) 71 | .ToArray(); 72 | Logger.Log($"New filters for {classGuid}: {string.Join(",", newFilters)}"); 73 | classSubkey.SetValue(filterValue, newFilters, RegistryValueKind.MultiString); 74 | } 75 | } catch { 76 | } 77 | } 78 | } 79 | } 80 | 81 | private static readonly List OverridesToDelete = new() { 82 | "stornvme", 83 | }; 84 | 85 | public static void ResetStartOverride() { 86 | try { 87 | foreach (var overrideName in OverridesToDelete.Concat(Find3PStorageDrivers().Select(x => x.Service))) { 88 | Logger.Log($"Resetting {overrideName} StartOverride"); 89 | Registry.LocalMachine.DeleteSubKey( 90 | $"SYSTEM\\CurrentControlSet\\Services\\{overrideName}\\StartOverride", 91 | false); 92 | } 93 | } catch (Exception ex) { 94 | Logger.Log($"Cannot delete StartOverride subkey: {ex.Message}"); 95 | } 96 | } 97 | 98 | private static readonly List XenfiltParametersToDelete = new() { 99 | "ActiveDeviceID", 100 | "ActiveInstanceID", 101 | "ActiveLocationInformation", 102 | }; 103 | 104 | public static void XenfiltReset() { 105 | using var paramKey = Registry.LocalMachine.OpenSubKey( 106 | "SYSTEM\\CurrentControlSet\\Services\\xenfilt\\Parameters", 107 | true); 108 | if (paramKey == null) { 109 | return; 110 | } 111 | foreach (var paramName in XenfiltParametersToDelete) { 112 | try { 113 | paramKey.DeleteValue(paramName); 114 | Logger.Log($"Deleted xenfilt parameter {paramName}"); 115 | } catch { 116 | } 117 | } 118 | } 119 | 120 | public static void ResetUnplug() { 121 | try { 122 | using var key = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\XEN", true); 123 | if (key == null) { 124 | return; 125 | } 126 | Logger.Log("Resetting Unplug key"); 127 | key.DeleteSubKey("Unplug"); 128 | } catch { 129 | } 130 | } 131 | 132 | public static void ResetForceUnplug() { 133 | try { 134 | using var key = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\XEN", true); 135 | if (key == null) { 136 | return; 137 | } 138 | Logger.Log("Resetting Unplug key"); 139 | key.DeleteSubKey("ForceUnplug"); 140 | } catch { 141 | } 142 | } 143 | 144 | // (ServiceName, IsDriverService) 145 | // other code may want to use this list too, so make it public 146 | public static readonly List> DeleteableServices = new() { 147 | new("xenagent", true), 148 | new("xenbus", true), 149 | new("xenbus_monitor", true), 150 | new("xencons", true), 151 | new("xencons_monitor", true), 152 | new("xendisk", true), 153 | new("xenfilt", true), 154 | new("xenhid", true), 155 | new("xeniface", true), 156 | new("xennet", true), 157 | new("xenvbd", true), 158 | new("xenvif", true), 159 | new("xenvkbd", true), 160 | new("XenInstall", false), 161 | new("XenSvc", false), 162 | }; 163 | 164 | public static void DeleteService(CloseServiceHandleSafeHandle scm, string serviceName, bool stop = true) { 165 | if (!DeleteableServices.Any(x => string.Equals(x.Item1, serviceName, StringComparison.OrdinalIgnoreCase))) { 166 | Logger.Log($"Refusing to delete service {serviceName}"); 167 | return; 168 | } 169 | Logger.Log($"Deleting service {serviceName}"); 170 | 171 | using var service = PInvoke.OpenService(scm, serviceName, PInvoke.SERVICE_ALL_ACCESS); 172 | if (service.IsInvalid) { 173 | Logger.Log($"OpenService {serviceName} error {Marshal.GetLastWin32Error()}"); 174 | return; 175 | } 176 | 177 | if (stop) { 178 | if (PInvoke.ControlService(service, PInvoke.SERVICE_CONTROL_STOP, out var status)) { 179 | Logger.Log($"Service {serviceName} stopped"); 180 | } else { 181 | Logger.Log($"ControlService({serviceName}, SERVICE_CONTROL_STOP) error {Marshal.GetLastWin32Error()}"); 182 | } 183 | } 184 | 185 | if (PInvoke.DeleteService(service)) { 186 | Logger.Log($"Service {serviceName} deleted"); 187 | } else { 188 | Logger.Log($"DeleteService({serviceName}) error {Marshal.GetLastWin32Error()}"); 189 | } 190 | } 191 | 192 | public static bool IsSafeMode() { 193 | return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SAFEBOOT_OPTION")); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /XenDriverUtils/Copy-XenVifSettings.ps1: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-2-Clause 2 | 3 | # Attention, this script is branding-agnostic! 4 | 5 | #Requires -RunAsAdministrator 6 | 7 | [CmdletBinding(SupportsShouldProcess)] 8 | param( 9 | # Run the task now. 10 | [Parameter(Mandatory, ParameterSetName = "Invoke")] 11 | [switch]$Invoke, 12 | # Install scheduled task to run on reboot. 13 | [Parameter(Mandatory, ParameterSetName = "Install")] 14 | [switch]$Install, 15 | 16 | [Parameter()] 17 | [switch]$Backup, 18 | [Parameter()] 19 | [switch]$Restore, 20 | 21 | [Parameter()] 22 | [switch]$Paravirtualized, 23 | [Parameter()] 24 | [switch]$Emulated, 25 | 26 | # Delete script after running. 27 | [Parameter(ParameterSetName = "Invoke")] 28 | [switch]$SelfDestruct 29 | ) 30 | 31 | $ErrorActionPreference = "Stop" 32 | 33 | if (![Environment]::Is64BitProcess) { 34 | throw "Cannot run this script from PowerShell x86!" 35 | } 36 | 37 | $Script:ScheduledTaskName = "Copy-XenVifSettings" 38 | $Script:InstallPath = "$env:ProgramFiles\Copy-XenVifSettings.ps1" 39 | $Script:PowershellPath = Join-Path ([System.Environment]::SystemDirectory) "WindowsPowerShell\v1.0\powershell.exe" 40 | 41 | function Backup-XenVifSettings { 42 | [CmdletBinding(SupportsShouldProcess)] 43 | param ( 44 | [Parameter(Mandatory)][string]$InterfaceGuid, 45 | [Parameter(Mandatory)][string]$PermanentAddress 46 | ) 47 | 48 | Write-Verbose "Backing up $PermanentAddress = $InterfaceGuid" 49 | 50 | Remove-Item -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip -Force -Recurse -ErrorAction SilentlyContinue -WhatIf:$WhatIfPreference 51 | New-Item -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip -Force -WhatIf:$WhatIfPreference 52 | Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\$InterfaceGuid -ErrorAction Continue | ` 53 | Select-Object -Property * -ExcludeProperty PSPath, PSParentPath, PSChildName, PSDrive, PSProvider | ` 54 | Set-ItemProperty -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip -ErrorAction Continue -WhatIf:$WhatIfPreference 55 | 56 | Remove-Item -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip6 -Force -Recurse -ErrorAction SilentlyContinue -WhatIf:$WhatIfPreference 57 | New-Item -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip6 -Force -WhatIf:$WhatIfPreference 58 | Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\$InterfaceGuid -ErrorAction Continue | ` 59 | Select-Object -Property * -ExcludeProperty PSPath, PSParentPath, PSChildName, PSDrive, PSProvider | ` 60 | Set-ItemProperty -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip6 -ErrorAction Continue -WhatIf:$WhatIfPreference 61 | } 62 | 63 | function Restore-XenVifSettings { 64 | [CmdletBinding(SupportsShouldProcess)] 65 | param ( 66 | [Parameter(Mandatory)][string]$InterfaceGuid, 67 | [Parameter(Mandatory)][string]$PermanentAddress 68 | ) 69 | 70 | $Restored = $false 71 | 72 | if (Test-Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip) { 73 | Write-Verbose "$PermanentAddress = $InterfaceGuid has Tcpip" 74 | 75 | Get-ItemProperty -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip -ErrorAction Continue | ` 76 | Select-Object -Property * -ExcludeProperty PSPath, PSParentPath, PSChildName, PSDrive, PSProvider | ` 77 | Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\$InterfaceGuid -ErrorAction Continue -WhatIf:$WhatIfPreference 78 | 79 | $Restored = $true 80 | } 81 | 82 | if (Test-Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip6) { 83 | Write-Verbose "$PermanentAddress = $InterfaceGuid has Tcpip6" 84 | 85 | Get-ItemProperty -Path HKLM:\SOFTWARE\XenOffboard\Xenvif\$PermanentAddress\Tcpip6 -ErrorAction Continue | ` 86 | Select-Object -Property * -ExcludeProperty PSPath, PSParentPath, PSChildName, PSDrive, PSProvider | ` 87 | Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\$InterfaceGuid -ErrorAction Continue -WhatIf:$WhatIfPreference 88 | 89 | $Restored = $true 90 | } 91 | 92 | if ($Restored) { 93 | # reload settings from registry 94 | Get-NetAdapter | Where-Object InterfaceGuid -ieq $InterfaceGuid | ` 95 | Disable-NetAdapter -Confirm:$false -PassThru -WhatIf:$WhatIfPreference | ` 96 | Enable-NetAdapter -Confirm:$false -WhatIf:$WhatIfPreference 97 | } 98 | } 99 | 100 | if ($Backup -and $Restore) { 101 | throw "-Backup and -Restore are mutually exclusive" 102 | } 103 | elseif (!$Backup -and !$Restore) { 104 | throw "Must specify an action" 105 | } 106 | 107 | if ($Paravirtualized -and $Emulated) { 108 | throw "-Paravirtualized and -Emulated are mutually exclusive" 109 | } 110 | elseif (!$Paravirtualized -and !$Emulated) { 111 | throw "Must specify a device type" 112 | } 113 | 114 | if ($Invoke) { 115 | try { 116 | $MatchingInterfaces = if ($Paravirtualized) { 117 | Get-NetAdapter | Where-Object PnPDeviceID -like "XENVIF\*" 118 | } 119 | elseif ($Emulated) { 120 | Get-NetAdapter | Where-Object PnPDeviceID -like "PCI\*" 121 | } 122 | 123 | if ($Backup) { 124 | Remove-Item -Path HKLM:\SOFTWARE\XenOffboard\Xenvif -Force -Recurse -ErrorAction SilentlyContinue -WhatIf:$WhatIfPreference 125 | $MatchingInterfaces | ForEach-Object { 126 | if ($_.InterfaceGuid -ne $null -and $_.PermanentAddress -ne $null) { 127 | Backup-XenVifSettings -InterfaceGuid $_.InterfaceGuid -PermanentAddress $_.PermanentAddress -WhatIf:$WhatIfPreference 128 | } 129 | } 130 | } 131 | elseif ($Restore) { 132 | $MatchingInterfaces | ForEach-Object { 133 | if ($_.InterfaceGuid -ne $null -and $_.PermanentAddress -ne $null) { 134 | Restore-XenVifSettings -InterfaceGuid $_.InterfaceGuid -PermanentAddress $_.PermanentAddress -WhatIf:$WhatIfPreference 135 | } 136 | } 137 | } 138 | } 139 | finally { 140 | if ($SelfDestruct) { 141 | Get-ScheduledTask -TaskName $Script:ScheduledTaskName -ErrorAction SilentlyContinue | ` 142 | Unregister-ScheduledTask -Confirm:$false -WhatIf:$WhatIfPreference -ErrorAction SilentlyContinue 143 | Remove-Item $PSCommandPath -Force -WhatIf:$WhatIfPreference 144 | } 145 | } 146 | } 147 | 148 | elseif ($Install) { 149 | Write-Verbose "Current path: $PSCommandPath" 150 | Write-Verbose "Install path: $Script:InstallPath" 151 | 152 | if ((Convert-Path $PSCommandPath -ErrorAction SilentlyContinue) -ieq (Convert-Path $Script:InstallPath -ErrorAction SilentlyContinue)) { 153 | throw "Cannot install from already-installed script, abandoning" 154 | } 155 | 156 | Copy-Item $PSCommandPath -Destination $Script:InstallPath -Force -WhatIf:$WhatIfPreference 157 | 158 | $existingTask = Get-ScheduledTask -TaskName $Script:ScheduledTaskName -ErrorAction SilentlyContinue 159 | if ($null -ne $existingTask) { 160 | Write-Verbose "Scheduled task is already installed, reinstalling" 161 | $existingTask | Unregister-ScheduledTask -Confirm:$false -WhatIf:$WhatIfPreference 162 | } 163 | 164 | $cmdArgs = @( 165 | "-Invoke", 166 | "-SelfDestruct" 167 | ) 168 | 169 | if ($Backup) { 170 | $cmdArgs += @("-Backup") 171 | } 172 | elseif ($Restore) { 173 | $cmdArgs += @("-Restore") 174 | } 175 | 176 | if ($Paravirtualized) { 177 | $cmdArgs += @("-Paravirtualized") 178 | } 179 | elseif ($Emulated) { 180 | $cmdArgs += @("-Emulated") 181 | } 182 | 183 | $argString = "-NoProfile -NonInteractive -ExecutionPolicy Bypass `"& '$Script:InstallPath' $($cmdArgs -join ' ')`"" 184 | Write-Verbose "Task executable: $Script:PowershellPath" 185 | Write-Verbose "Task arguments: $argString" 186 | 187 | $task = New-ScheduledTask ` 188 | -Trigger (New-ScheduledTaskTrigger -AtStartup) ` 189 | -Action (New-ScheduledTaskAction -Execute $Script:PowershellPath -Argument $argString) ` 190 | -Principal (New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -RunLevel Highest -LogonType ServiceAccount) ` 191 | -Settings (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 5)) 192 | if ($PSCmdlet.ShouldProcess($Script:ScheduledTaskName, "Create scheduled task")) { 193 | $task | Register-ScheduledTask -TaskName $Script:ScheduledTaskName 194 | } 195 | } 196 | --------------------------------------------------------------------------------