using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Management; using Duplicati.Library.Common.IO; namespace Duplicati.Library.Snapshots { public class HyperVGuest : IEquatable { public string Name { get; } public Guid ID { get; } public List DataPaths { get; } public HyperVGuest(string Name, Guid ID, List DataPaths) { this.Name = Name; this.ID = ID; this.DataPaths = DataPaths; } bool IEquatable.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); } return false; } 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); } } public class HyperVUtility { /// /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly ManagementScope _wmiScope; private readonly string _vmIdField; private readonly string _wmiHost = "localhost"; private readonly bool _wmiv2Namespace; /// /// The Hyper-V VSS Writer Guid /// public static readonly Guid HyperVWriterGuid = new Guid("66841cd4-6ded-4f4b-8f17-fd23f8ddc3de"); /// /// Hyper-V is supported only on Windows platform /// public bool IsHyperVInstalled { get; } /// /// Hyper-V writer is supported only on Server version of Windows /// public bool IsVSSWriterSupported { get; } /// /// Enumerated Hyper-V guests /// public List Guests { get; } public HyperVUtility() { Guests = new List(); if (!Utility.Utility.IsClientWindows) { IsHyperVInstalled = false; IsVSSWriterSupported = false; return; } //Set the namespace depending off host OS _wmiv2Namespace = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2); //Set the scope to use in WMI. V2 for Server 2012 or newer. _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"; Logging.Log.WriteProfilingMessage(LOGTAG, "WMISelect", "Using WMI provider {0}", _wmiScope.Path); IsVSSWriterSupported = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem") .Get().OfType() .Select(o => (uint)o.GetPropertyValue("ProductType")) .First() != 1; try { IsHyperVInstalled = new ManagementObjectSearcher(_wmiScope, new ObjectQuery( "SELECT * FROM meta_class")).Get().OfType() .Any(o => ((ManagementClass)o).ClassPath.ClassName.StartsWith("Msvm_")); } catch { IsHyperVInstalled = false; } if (!IsHyperVInstalled) Logging.Log.WriteInformationMessage(LOGTAG, "NoHyperVFound", "Cannot open WMI provider {0}. Hyper-V is probably not installed.", _wmiScope.Path); } /// /// Query Hyper-V for all Virtual Machines info /// /// Specify if returned data should contain VM paths /// List of Hyper-V Machines public void QueryHyperVGuestsInfo(bool bIncludePaths = false) { if (!IsHyperVInstalled) return; Guests.Clear(); var wmiQuery = _wmiv2Namespace ? "SELECT * FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'" : "SELECT * FROM Msvm_VirtualSystemSettingData WHERE SettingType = 3"; if (IsVSSWriterSupported) { using (var moCollection = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(wmiQuery)).Get()) { if (bIncludePaths) { foreach (var o in GetAllVMsPathsVSS()) { 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)); } } } 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) .OrderBy(a => a).ToList() : null)); } } } } /// /// For all Hyper-V guests it enumerate all associated paths using VSS data /// /// A collection of VMs and paths private static IEnumerable GetAllVMsPathsVSS() { using (var vssBackupComponents = new VssBackupComponents()) { var writerGUIDS = new [] { HyperVWriterGuid }; try { vssBackupComponents.SetupWriters(writerGUIDS, null); } 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)) { yield return o; } } } /// /// For given Hyper-V guest it enumerate all associated configuration files using WMI data /// /// ID of VM to get paths for /// A collection of configuration paths private List GetVMConfigPathsWMI(string vmID) { var result = new List(); 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); using (var mObject1 = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(wmiQuery)).Get().Cast().First()) if (_wmiv2Namespace) { path = SystemIO.IO_WIN.PathCombine((string)mObject1["ConfigurationDataRoot"], (string)mObject1["ConfigurationFile"]); if (File.Exists(path)) result.Add(path); 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) { path = SystemIO.IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["ConfigurationFile"]); if (File.Exists(path)) result.Add(path); path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["SuspendDataRoot"])); if (Directory.Exists(path)) result.Add(path); } } } else { path = SystemIO.IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID + ".xml"); if (File.Exists(path)) result.Add(path); path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID)); 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().Select(o => (string)o.GetPropertyValue("InstanceID")).ToList(); foreach (var snapID in snapsIDs) { path = SystemIO.IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", "") + ".xml"); if (File.Exists(path)) result.Add(path); path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", ""))); if (Directory.Exists(path)) result.Add(path); } } return result; } /// /// For given Hyper-V guest it enumerate all associated VHD files using WMI data /// /// ID of VM to get paths for /// A collection of VHD paths private List GetVMVhdPathsWMI(string vmID) { var result = new List(); using (var vm = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(string.Format("select * from Msvm_ComputerSystem where Name = '{0}'", vmID))) .Get().OfType().First()) { foreach (var sysSettings in vm.GetRelated("MsVM_VirtualSystemSettingData")) using (var systemObjCollection = ((ManagementObject)sysSettings).GetRelated(_wmiv2Namespace ? "MsVM_StorageAllocationSettingData" : "MsVM_ResourceAllocationSettingData")) { List 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) result.Add(vhd); } } using (var imgMan = new ManagementObjectSearcher(_wmiScope, new ObjectQuery("select * from MsVM_ImageManagementService")).Get().OfType().First()) { var ParentPaths = new List(); var inParams = imgMan.GetMethodParameters(_wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo"); foreach (var vhdPath in result) { inParams["Path"] = vhdPath; using (var outParams = imgMan.InvokeMethod(_wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo", inParams, null)) { if (outParams != null) { var doc = new System.Xml.XmlDocument(); doc.LoadXml((string)outParams[_wmiv2Namespace ? "SettingData" : "Info"]); var node = doc.SelectSingleNode("//PROPERTY[@NAME = 'ParentPath']/VALUE/child::text()"); if (node != null && File.Exists(node.Value)) ParentPaths.Add(node.Value); } } } result.AddRange(ParentPaths); } return result; } } }