using System.ComponentModel; using System.Linq; using System.Management; using System.Reflection; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using System.Xml; namespace Vanara.Management; /// Extension methods to work more easily with . public static class ManagementExtensions { internal enum JobState : ushort { New = 2, Starting = 3, Running = 4, Suspended = 5, ShuttingDown = 6, Completed = 7, Terminated = 8, Killed = 9, Exception = 10, Service = 11, QueryPending = 12, CompletedWithWarnings = 32768 } /// Calls a service method that returns a Job asynchronously. /// The scope. /// The cancellation token. /// The progress. /// The service. /// The method. /// The values. /// The resulting . public static async Task CallJobMethodAsync(this ManagementScope scope, CancellationToken cancellationToken, IProgress? progress, string service, string method, params (string, object)[] values) => await Task.Factory.StartNew(() => { if (!scope.IsConnected) scope.Connect(); using ManagementObject imgMgmtSvc = scope.GetWMIService(service) ?? throw new ArgumentException("Unable to find service.", nameof(service)); using ManagementBaseObject inParams = imgMgmtSvc.GetMethodParameters(method); foreach ((string, object) kv in values) inParams[kv.Item1] = kv.Item2; ManagementBaseObject outputParameters = imgMgmtSvc.InvokeMethod(method, inParams, new()); const int sleepDur = 500; if (outputParameters.IsAsync()) { // The method invoked an asynchronous operation. Get the Job object and wait for it to complete. Then we can check its result. using ManagementObject job = new((string)outputParameters["Job"]) { Scope = scope }; while (!job.GetProp("JobState").IsJobComplete()) { if (progress is not null) { try { progress.Report(job.GetProp("PercentComplete")); } catch { } } Task.Delay(sleepDur); // ManagementObjects are offline objects. Call Get() on the object to have its current property state. job.Get(); } switch (job.GetProp("JobState")) { case JobState.Terminated: case JobState.Killed: throw new ThreadInterruptedException(); case JobState.Exception: ManagementBaseObject errOut = job.InvokeMethod("GetError", null!, new()); var xml = new XmlDocument(); xml.LoadXml(errOut.GetProp("Error")); if (xml.DocumentElement is null) throw new InvalidOperationException("Error XML is invalid."); var errMsg = xml.DocumentElement.SelectSingleNode(@"//PROPERTY[@NAME='Message']/VALUE")?.InnerText; var ex = new InvalidOperationException(errMsg); #if DEBUG foreach (var p in job.Properties.Cast()) ex.Data.Add(p.Name, p.Value); #endif throw ex; case JobState.Completed: case JobState.CompletedWithWarnings: outputParameters.SetPropertyValue("ReturnValue", 0); break; default: break; } } progress?.Report(100); return outputParameters; }, cancellationToken); /// Gets the embedded instance string usable by WMI /// The type of the instance. /// The instance. /// Name of the server. /// Embedded instance string usable by WMI. /// Generic type does not have a DataContract attribute. public static string GetInstanceText(T instance, string serverName = ".") { DataContractAttribute attr = typeof(T).GetCustomAttributes(false).FirstOrDefault() ?? throw new InvalidOperationException("Generic type does not have a DataContract attribute."); var path = new ManagementPath() { Server = serverName, NamespacePath = attr.Namespace!, ClassName = attr.Name! }; using var settingsClass = new ManagementClass(path); using ManagementObject settingsInstance = settingsClass.CreateInstance(); foreach (PropertyInfo pi in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (pi.GetCustomAttributes().Any() || !pi.CanRead) continue; DataMemberAttribute mattr = pi.GetCustomAttributes(false).FirstOrDefault() ?? new DataMemberAttribute() { Name = pi.Name }; var val = pi.PropertyType.IsEnum ? Convert.ChangeType(pi.GetValue(instance), pi.PropertyType.GetEnumUnderlyingType()) : pi.GetValue(instance); settingsInstance.SetPropertyValue(mattr.Name!, val!); } return settingsInstance.GetText(TextFormat.WmiDtd20); } /// Gets the specified property value of from . /// The property type /// The object. /// The property name. /// The property value. public static T GetProp(this ManagementBaseObject obj, string prop) => typeof(T).IsEnum ? (T)Enum.ToObject(typeof(T), obj[prop]) : (T)obj[prop]; /// Gets the result from a return value or throws the appropriate exception. /// The method output object. /// if set to throws all exceptions including those for 4096 and 32768. /// on success; otherwise . public static bool GetResultOrThrow(this ManagementBaseObject output, bool throwAll = false) => output.GetProp("ReturnValue") switch { 0 => true, 4096 => throwAll ? throw new SynchronizationLockException() : false, 32768 => throwAll ? throw new Exception() : false, 32769 => throw new UnauthorizedAccessException(), 32770 => throw new NotSupportedException(), 32773 => throw new ArgumentException(), 32779 => throw new System.IO.FileNotFoundException(), 32778 => throw new OutOfMemoryException(), 32772 => throw new TimeoutException(), //Status is unknown(32771) //System is in use(32774) //Invalid state for this operation(32775) //Incorrect data type(32776) //System is not available(32777) _ => throw new Exception(), }; /// Gets the specifid WMI service from a scope. /// The scope. /// The service path. /// The service object. public static ManagementObject? GetWMIService(this ManagementScope scope, string path) { using ManagementClass imageManagementServiceClass = new(path) { Scope = scope }; return imageManagementServiceClass.GetInstances().Cast().FirstOrDefault(); } /// /// Parses an embedded instance returned from the server and creates a new instance of with that information. /// /// The type to fill with information from . /// The embedded instance. /// An instance of with the data contained in the embedded instance. /// If there was a problem parsing the embedded instance. /// If either param is null. public static T Parse(string? embeddedInstance) where T : class, new() { if (embeddedInstance is null) throw new FormatException(); var doc = new XmlDocument(); doc.LoadXml(embeddedInstance); XmlNodeList nodelist = doc.SelectNodes(@"/INSTANCE/@CLASSNAME") ?? throw new FormatException(); var className = typeof(T).GetCustomAttributes(false).FirstOrDefault()?.Name ?? typeof(T).Name; if (nodelist.Count != 1 || nodelist[0]?.Value != className) { throw new FormatException(); } var output = new T(); foreach (PropertyInfo pi in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (pi.GetCustomAttributes().Any() || !pi.CanWrite) continue; DataMemberAttribute attr = pi.GetCustomAttributes(false).FirstOrDefault() ?? new DataMemberAttribute() { Name = pi.Name }; if (pi.PropertyType.IsArray) { if (doc.SelectSingleNode($@"//PROPERTY.ARRAY[@NAME = '{attr.Name}']") is not null) { var array = doc.SelectNodes($@"//PROPERTY.ARRAY[@NAME = '{attr.Name}']/VALUE.ARRAY/VALUE")?.Cast().Select(n => n.InnerText).ToArray() ?? new string[0]; var ret = Array.CreateInstance(pi.PropertyType.GetElementType()!, array.Length); for (int i = 0; i < array.Length; i++) ret.SetValue(GetVal(pi.PropertyType.GetElementType()!, array[i]), i); pi.SetValue(output, ret); } else if (attr.IsRequired) throw new FormatException(); } else { nodelist = doc.SelectNodes($@"//PROPERTY[@NAME = '{attr.Name}']/VALUE/child::text()") ?? throw new FormatException(); if (attr.IsRequired && nodelist.Count != 1) throw new FormatException(); if (nodelist.Count == 0) continue; pi.SetValue(output, GetVal(pi.PropertyType, nodelist[0]?.Value)); } } return output; static object? GetVal(Type type, string? value) { if (value is null) return null; if (type == typeof(string)) return value; if (type.IsEnum) { TypeConverter cv = TypeDescriptor.GetConverter(type.GetEnumUnderlyingType()); var val = (value is not null ? cv.ConvertFromInvariantString(value) : null) ?? throw new FormatException(); if (!Enum.IsDefined(type, val)) throw new FormatException(); return Enum.ToObject(type, val); } else { TypeConverter cv = TypeDescriptor.GetConverter(type); return cv.ConvertFromInvariantString(value); } } } /// Converts a string in CIM_DATETIME format to a . /// The CIM_DATETIME string in 'yyyymmddHHMMSS.mmmmmmsUUU' format. /// A value in GMT equivalent to or if unable to process. public static DateTime? CimToDateTime(string? cimdate) { if (cimdate is null || !(cimdate.Length is 14 or 21 or 25)) return null; var dts = cimdate; if (dts.Length == 14) dts += ".000000"; if (dts.Length == 21) dts += "+000"; if (!short.TryParse(dts.Substring(21, 4), out var utcOffset)) return null; dts = $"{dts.Substring(0, 21)}{(utcOffset < 0 ? '-' : '+')}{Math.Abs(utcOffset) / 60:D2}:{Math.Abs(utcOffset) % 60:D2}"; if (!DateTime.TryParseExact(dts, "yyyyMMddHHmmss.FFFFFFFzzz", null, System.Globalization.DateTimeStyles.None, out var dt)) return null; return dt; } /// Converts a value to CIM_DATETIME format. /// The value. /// A CIM_DATETIME string in 'yyyymmddHHMMSS.mmmmmmsUUU' format. public static string DateTimeToCim(this DateTime dt) => $"{dt.ToUniversalTime():yyyyMMddHHmmss.FFFFFFF}+000"; /// Verifies whether a job is completed. /// An object that represents the JobState of the job. /// True if the job is completed, False otherwise. internal static bool IsJobComplete(this JobState jobStateObj) => jobStateObj is JobState.Completed or JobState.CompletedWithWarnings or JobState.Terminated or JobState.Exception or JobState.Killed; /// Verifies whether a job succeeded. /// An object representing the JobState of the job. /// true if the job succeeded; otherwise, false. internal static bool IsJobSuccessful(this JobState jobStateObj) => jobStateObj is JobState.Completed or JobState.CompletedWithWarnings; private static bool IsAsync(this ManagementBaseObject output) => output.GetProp("ReturnValue") == 4096; }