2025-02-06 16:57:12 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-09-18 16:02:01 +02:00
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
using System ;
2016-09-23 09:30:28 +02:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Management ;
2024-05-30 00:49:23 +02:00
using System.Runtime.Versioning ;
2024-09-18 16:02:01 +02:00
using Duplicati.Library.Common.IO ;
2025-02-19 14:00:14 +01:00
namespace Duplicati.Library.Snapshots.Windows
2016-09-23 09:30:28 +02:00
{
2016-10-22 23:08:27 +02:00
public class HyperVGuest : IEquatable < HyperVGuest >
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
public string Name { get ; }
2016-10-22 23:08:27 +02:00
public Guid ID { get ; }
2016-10-06 00:26:54 +02:00
public List < string > DataPaths { get ; }
2016-09-23 09:30:28 +02:00
2016-10-22 23:08:27 +02:00
public HyperVGuest ( string Name , Guid ID , List < string > DataPaths )
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
this . Name = Name ;
this . ID = ID ;
this . DataPaths = DataPaths ;
2016-09-23 09:30:28 +02:00
}
2016-10-22 23:08:27 +02:00
bool IEquatable < HyperVGuest >. Equals ( HyperVGuest other )
{
return ID . Equals ( other . ID );
}
public override int GetHashCode ()
{
return ID . GetHashCode ();
}
public override bool Equals ( object obj )
{
HyperVGuest guest = obj as HyperVGuest ;
if ( guest != null )
{
return Equals ( guest );
}
2018-10-27 12:27:08 +02:00
return false ;
2016-10-22 23:08:27 +02:00
}
public static bool operator ==( HyperVGuest guest1 , HyperVGuest guest2 )
{
if ( object . ReferenceEquals ( guest1 , guest2 )) return true ;
if ( object . ReferenceEquals ( guest1 , null )) return false ;
if ( object . ReferenceEquals ( guest2 , null )) return false ;
return guest1 . Equals ( guest2 );
}
public static bool operator !=( HyperVGuest guest1 , HyperVGuest guest2 )
{
if ( object . ReferenceEquals ( guest1 , guest2 )) return false ;
if ( object . ReferenceEquals ( guest1 , null )) return true ;
if ( object . ReferenceEquals ( guest2 , null )) return true ;
return ! guest1 . Equals ( guest2 );
}
2016-10-06 00:26:54 +02:00
}
2016-09-23 09:30:28 +02:00
2024-05-30 00:49:23 +02:00
[SupportedOSPlatform("windows")]
2016-10-06 00:26:54 +02:00
public class HyperVUtility
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < HyperVUtility >();
2025-02-06 16:57:12 +01:00
/// <summary>
/// The System.IO abstraction for the Windows platform
/// </summary>
private static readonly ISystemIO IO_WIN = SystemIO . IO_OS ;
2016-09-23 09:30:28 +02:00
private readonly ManagementScope _wmiScope ;
private readonly string _vmIdField ;
2016-10-06 00:26:54 +02:00
private readonly string _wmiHost = "localhost" ;
2016-09-23 09:30:28 +02:00
private readonly bool _wmiv2Namespace ;
2016-10-16 22:36:29 +02:00
/// <summary>
/// The Hyper-V VSS Writer Guid
/// </summary>
public static readonly Guid HyperVWriterGuid = new Guid ( "66841cd4-6ded-4f4b-8f17-fd23f8ddc3de" );
/// <summary>
/// Hyper-V is supported only on Windows platform
/// </summary>
2016-10-09 15:10:14 +02:00
public bool IsHyperVInstalled { get ; }
2016-10-16 22:36:29 +02:00
/// <summary>
/// Hyper-V writer is supported only on Server version of Windows
/// </summary>
public bool IsVSSWriterSupported { get ; }
2018-10-27 12:27:08 +02:00
2016-10-16 22:36:29 +02:00
/// <summary>
/// Enumerated Hyper-V guests
/// </summary>
2024-09-18 16:02:01 +02:00
public List < HyperVGuest > Guests { get ; }
2016-09-23 09:30:28 +02:00
public HyperVUtility ()
{
2018-06-27 19:36:10 +02:00
Guests = new List < HyperVGuest >();
2016-10-16 22:36:29 +02:00
2024-05-30 02:14:05 +02:00
if (! OperatingSystem . IsWindows ())
2016-10-09 15:10:14 +02:00
{
IsHyperVInstalled = false ;
2016-10-16 22:36:29 +02:00
IsVSSWriterSupported = false ;
2016-10-09 15:10:14 +02:00
return ;
}
2016-09-23 09:30:28 +02:00
//Set the namespace depending off host OS
2024-09-18 16:02:01 +02:00
_wmiv2Namespace = OperatingSystem . IsWindowsVersionAtLeast ( 6 , 2 );
2016-10-06 00:26:54 +02:00
//Set the scope to use in WMI. V2 for Server 2012 or newer.
2016-09-23 09:30:28 +02:00
_wmiScope = _wmiv2Namespace
? new ManagementScope ( string . Format ( "\\\\{0}\\root\\virtualization\\v2" , _wmiHost ))
: new ManagementScope ( string . Format ( "\\\\{0}\\root\\virtualization" , _wmiHost ));
//Set the VM ID Selector Field for the WMI Query
_vmIdField = _wmiv2Namespace ? "VirtualSystemIdentifier" : "SystemName" ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "WMISelect" , "Using WMI provider {0}" , _wmiScope . Path );
2016-10-16 22:36:29 +02:00
IsVSSWriterSupported = new ManagementObjectSearcher ( "SELECT * FROM Win32_OperatingSystem" )
. Get (). OfType < ManagementObject >()
2018-06-27 19:36:10 +02:00
. Select ( o => ( uint ) o . GetPropertyValue ( "ProductType" ))
. First () != 1 ;
2016-10-16 22:36:29 +02:00
2016-10-09 15:10:14 +02:00
try
{
2018-06-27 19:36:10 +02:00
IsHyperVInstalled = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery (
2018-06-29 00:29:15 +02:00
"SELECT * FROM meta_class" )). Get (). OfType < ManagementObject >()
2018-11-21 21:08:13 -08:00
. Any ( o => (( ManagementClass ) o ). ClassPath . ClassName . StartsWith ( "Msvm_" , StringComparison . Ordinal ));
2016-10-09 15:10:14 +02:00
}
catch { IsHyperVInstalled = false ; }
if (! IsHyperVInstalled )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "NoHyperVFound" , "Cannot open WMI provider {0}. Hyper-V is probably not installed." , _wmiScope . Path );
2016-09-23 09:30:28 +02:00
}
/// <summary>
2016-10-16 22:36:29 +02:00
/// Query Hyper-V for all Virtual Machines info
2016-09-23 09:30:28 +02:00
/// </summary>
2016-10-16 22:36:29 +02:00
/// <param name="bIncludePaths">Specify if returned data should contain VM paths</param>
2025-02-19 14:00:14 +01:00
/// <param name="provider">The provider to use for VSS</param>
2016-09-23 09:30:28 +02:00
/// <returns>List of Hyper-V Machines</returns>
2025-02-19 14:00:14 +01:00
public void QueryHyperVGuestsInfo ( SnapshotProvider provider , bool bIncludePaths = false )
2016-09-23 09:30:28 +02:00
{
2016-10-16 22:36:29 +02:00
if (! IsHyperVInstalled )
return ;
2016-10-09 15:10:14 +02:00
2018-06-27 19:36:10 +02:00
Guests . Clear ();
2016-09-23 09:50:39 +02:00
var wmiQuery = _wmiv2Namespace
2016-10-06 00:26:54 +02:00
? "SELECT * FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'"
: "SELECT * FROM Msvm_VirtualSystemSettingData WHERE SettingType = 3" ;
2016-11-02 00:02:34 +01:00
2024-09-18 16:02:01 +02:00
if ( IsVSSWriterSupported )
{
using ( var moCollection = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( wmiQuery )). Get ())
2018-10-27 12:27:08 +02:00
{
if ( bIncludePaths )
{
2025-02-19 14:00:14 +01:00
foreach ( var o in GetAllVMsPathsVSS ( provider ))
2018-10-27 12:27:08 +02:00
{
foreach ( var mObject in moCollection )
{
if (( string ) mObject [ _vmIdField ] == o . Name )
{
Guests . Add ( new HyperVGuest (( string ) mObject [ "ElementName" ], new Guid (( string ) mObject [ _vmIdField ]), o . Paths ));
}
}
}
}
else
{
foreach ( var mObject in moCollection )
Guests . Add ( new HyperVGuest (( string ) mObject [ "ElementName" ], new Guid (( string ) mObject [ _vmIdField ]), null ));
}
}
2024-09-18 16:02:01 +02:00
}
else
{
using ( var moCollection = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( wmiQuery )). Get ())
{
foreach ( var mObject in moCollection )
{
Guests . Add ( new HyperVGuest (( string ) mObject [ "ElementName" ], new Guid (( string ) mObject [ _vmIdField ]), bIncludePaths ?
GetVMVhdPathsWMI (( string ) mObject [ _vmIdField ])
. Union ( GetVMConfigPathsWMI (( string ) mObject [ _vmIdField ]))
. ToList ()
. ConvertAll ( m => m [ 0 ]. ToString (). ToUpperInvariant () + m . Substring ( 1 ))
. Distinct ( Utility . Utility . ClientFilenameStringComparer )
2016-10-16 22:36:29 +02:00
. OrderBy ( a => a ). ToList () : null ));
2018-10-27 12:27:08 +02:00
}
}
}
2016-10-16 22:36:29 +02:00
}
2016-09-23 09:30:28 +02:00
2016-10-16 22:36:29 +02:00
/// <summary>
/// For all Hyper-V guests it enumerate all associated paths using VSS data
/// </summary>
/// <returns>A collection of VMs and paths</returns>
2025-02-19 14:00:14 +01:00
private static IEnumerable < WriterMetaData > GetAllVMsPathsVSS ( SnapshotProvider provider )
2016-10-16 22:36:29 +02:00
{
2025-02-19 14:00:14 +01:00
using ( var vssBackupComponents = new SnapshotManager ( provider ))
2016-11-02 00:02:34 +01:00
{
2024-09-18 16:02:01 +02:00
var writerGUIDS = new [] { HyperVWriterGuid };
try
{
vssBackupComponents . SetupWriters ( writerGUIDS , null );
2016-10-06 00:26:54 +02:00
}
2024-09-18 16:02:01 +02:00
catch ( Exception )
{
throw new Interface . UserInformationException ( "Microsoft Hyper-V VSS Writer not found - cannot backup Hyper-V machines." , "NoHyperVVssWriter" );
}
foreach ( var o in vssBackupComponents . ParseWriterMetaData ( writerGUIDS ))
{
2018-10-27 12:27:08 +02:00
yield return o ;
2016-10-16 22:36:29 +02:00
}
}
2016-09-23 09:30:28 +02:00
}
/// <summary>
2016-10-16 22:36:29 +02:00
/// For given Hyper-V guest it enumerate all associated configuration files using WMI data
2016-09-23 09:30:28 +02:00
/// </summary>
2016-10-16 22:36:29 +02:00
/// <param name="vmID">ID of VM to get paths for</param>
2016-10-06 00:26:54 +02:00
/// <returns>A collection of configuration paths</returns>
2016-10-16 22:36:29 +02:00
private List < string > GetVMConfigPathsWMI ( string vmID )
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
var result = new List < string >();
string path ;
var wmiQuery = _wmiv2Namespace
? string . Format ( "select * from Msvm_VirtualSystemSettingData where {0}='{1}'" , _vmIdField , vmID )
: string . Format ( "select * from Msvm_VirtualSystemGlobalSettingData where {0}='{1}'" , _vmIdField , vmID );
2016-09-23 09:30:28 +02:00
2016-10-06 00:26:54 +02:00
using ( var mObject1 = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( wmiQuery )). Get (). Cast < ManagementObject >(). First ())
if ( _wmiv2Namespace )
{
2025-02-06 16:57:12 +01:00
path = IO_WIN . PathCombine (( string ) mObject1 [ "ConfigurationDataRoot" ], ( string ) mObject1 [ "ConfigurationFile" ]);
2016-10-06 00:26:54 +02:00
if ( File . Exists ( path ))
result . Add ( path );
2016-09-23 09:30:28 +02:00
2016-10-06 00:26:54 +02:00
using ( var snaps = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( string . Format (
"SELECT * FROM Msvm_VirtualSystemSettingData where VirtualSystemType='Microsoft:Hyper-V:Snapshot:Realized' and {0}='{1}'" ,
_vmIdField , vmID ))). Get ())
{
foreach ( var snap in snaps )
{
2025-02-06 16:57:12 +01:00
path = IO_WIN . PathCombine (( string ) snap [ "ConfigurationDataRoot" ], ( string ) snap [ "ConfigurationFile" ]);
2016-10-06 00:26:54 +02:00
if ( File . Exists ( path ))
result . Add ( path );
2025-02-06 16:57:12 +01:00
path = Util . AppendDirSeparator ( IO_WIN . PathCombine (( string ) snap [ "ConfigurationDataRoot" ], ( string ) snap [ "SuspendDataRoot" ]));
2016-10-06 00:26:54 +02:00
if ( Directory . Exists ( path ))
result . Add ( path );
}
}
}
else
{
2025-02-06 16:57:12 +01:00
path = IO_WIN . PathCombine (( string ) mObject1 [ "ExternalDataRoot" ], "Virtual Machines" , vmID + ".xml" );
2016-10-06 00:26:54 +02:00
if ( File . Exists ( path ))
result . Add ( path );
2025-02-06 16:57:12 +01:00
path = Util . AppendDirSeparator ( IO_WIN . PathCombine (( string ) mObject1 [ "ExternalDataRoot" ], "Virtual Machines" , vmID ));
2016-10-06 00:26:54 +02:00
if ( Directory . Exists ( path ))
result . Add ( path );
var snapsIDs = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( string . Format (
"SELECT * FROM Msvm_VirtualSystemSettingData where SettingType=5 and {0}='{1}'" ,
_vmIdField , vmID ))). Get (). OfType < ManagementObject >(). Select ( o => ( string ) o . GetPropertyValue ( "InstanceID" )). ToList ();
foreach ( var snapID in snapsIDs )
{
2025-02-06 16:57:12 +01:00
path = IO_WIN . PathCombine (( string ) mObject1 [ "SnapshotDataRoot" ], "Snapshots" , snapID . Replace ( "Microsoft:" , "" ) + ".xml" );
2016-10-06 00:26:54 +02:00
if ( File . Exists ( path ))
result . Add ( path );
2025-02-06 16:57:12 +01:00
path = Util . AppendDirSeparator ( IO_WIN . PathCombine (( string ) mObject1 [ "SnapshotDataRoot" ], "Snapshots" , snapID . Replace ( "Microsoft:" , "" )));
2016-10-06 00:26:54 +02:00
if ( Directory . Exists ( path ))
result . Add ( path );
}
}
2016-09-23 09:30:28 +02:00
2016-10-16 22:36:29 +02:00
return result ;
2016-09-23 09:30:28 +02:00
}
/// <summary>
2016-10-16 22:36:29 +02:00
/// For given Hyper-V guest it enumerate all associated VHD files using WMI data
2016-09-23 09:30:28 +02:00
/// </summary>
2016-10-16 22:36:29 +02:00
/// <param name="vmID">ID of VM to get paths for</param>
2016-10-06 00:26:54 +02:00
/// <returns>A collection of VHD paths</returns>
2016-10-16 22:36:29 +02:00
private List < string > GetVMVhdPathsWMI ( string vmID )
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
var result = new List < string >();
using ( var vm = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( string . Format ( "select * from Msvm_ComputerSystem where Name = '{0}'" , vmID )))
. Get (). OfType < ManagementObject >(). First ())
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
foreach ( var sysSettings in vm . GetRelated ( "MsVM_VirtualSystemSettingData" ))
using ( var systemObjCollection = (( ManagementObject ) sysSettings ). GetRelated ( _wmiv2Namespace ? "MsVM_StorageAllocationSettingData" : "MsVM_ResourceAllocationSettingData" ))
{
List < string > tempvhd ;
if ( _wmiv2Namespace )
tempvhd = ( from ManagementBaseObject systemBaseObj in systemObjCollection
where (( UInt16 ) systemBaseObj [ "ResourceType" ] == 31
&& ( string ) systemBaseObj [ "ResourceSubType" ] == "Microsoft:Hyper-V:Virtual Hard Disk" )
select (( string []) systemBaseObj [ "HostResource" ])[ 0 ]). ToList ();
else
tempvhd = ( from ManagementBaseObject systemBaseObj in systemObjCollection
where (( UInt16 ) systemBaseObj [ "ResourceType" ] == 21
&& ( string ) systemBaseObj [ "ResourceSubType" ] == "Microsoft Virtual Hard Disk" )
select (( string []) systemBaseObj [ "Connection" ])[ 0 ]). ToList ();
foreach ( var vhd in tempvhd )
2022-03-20 13:49:52 +11:00
{
if ( File . Exists ( vhd ))
{
result . Add ( vhd );
}
else
{
Logging . Log . WriteWarningMessage ( LOGTAG , "HyperVInvalidVhd" , null , "Invalid VHD file detected, file does not exist: {0}" , vhd );
}
}
2016-10-06 00:26:54 +02:00
}
2016-09-23 09:30:28 +02:00
}
2016-10-06 00:26:54 +02:00
using ( var imgMan = new ManagementObjectSearcher ( _wmiScope , new ObjectQuery ( "select * from MsVM_ImageManagementService" )). Get (). OfType < ManagementObject >(). First ())
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
var ParentPaths = new List < string >();
var inParams = imgMan . GetMethodParameters ( _wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo" );
2016-09-23 09:30:28 +02:00
2016-10-06 00:26:54 +02:00
foreach ( var vhdPath in result )
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
inParams [ "Path" ] = vhdPath ;
using ( var outParams = imgMan . InvokeMethod ( _wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo" , inParams , null ))
2016-09-23 09:30:28 +02:00
{
2016-10-06 00:26:54 +02:00
if ( outParams != null )
{
var doc = new System . Xml . XmlDocument ();
2022-02-04 23:05:23 +11:00
var propertyValue = ( string ) outParams [ _wmiv2Namespace ? "SettingData" : "Info" ];
2016-10-06 00:26:54 +02:00
2022-02-04 23:05:23 +11:00
if ( propertyValue != null )
{
doc . LoadXml ( propertyValue );
var node = doc . SelectSingleNode ( "//PROPERTY[@NAME = 'ParentPath']/VALUE/child::text()" );
if ( node != null && File . Exists ( node . Value ))
ParentPaths . Add ( node . Value );
}
2016-10-06 00:26:54 +02:00
}
2016-09-23 09:30:28 +02:00
}
}
2016-10-06 00:26:54 +02:00
result . AddRange ( ParentPaths );
2016-09-23 09:30:28 +02:00
}
2016-10-16 22:36:29 +02:00
return result ;
2016-10-06 00:26:54 +02:00
}
2016-09-23 09:30:28 +02:00
}
2016-10-06 00:26:54 +02:00
}