diff --git a/Management/DynamicMgmtObject.cs b/Management/DynamicMgmtObject.cs new file mode 100644 index 00000000..33642cf7 --- /dev/null +++ b/Management/DynamicMgmtObject.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Management; + +namespace Vanara.Management +{ + /// A dynamic object to handle WMI references. + /// + /// + public class DynamicMgmtObject : DynamicObject, IDisposable + { + private readonly ManagementObject obj; + + /// Initializes a new instance of the class. + /// The object. + public DynamicMgmtObject(ManagementObject obj) => this.obj = obj ?? throw new ArgumentNullException(); + + /// Initializes a new instance of the class. + /// The scope. + /// The service name. + public DynamicMgmtObject(ManagementScope scope, string service) + { + if (!scope.IsConnected) + scope.Connect(); + + obj = scope.GetWMIService(service); + if (obj is null) throw new ArgumentNullException(); + } + + /// Initializes a new instance of the class. + /// The class. + public DynamicMgmtObject(ManagementClass wmiclass) => obj = wmiclass.CreateInstance(); + + /// Performs an implicit conversion from to . + /// The . + /// The result of the conversion. + public static implicit operator DynamicMgmtObject(ManagementObject mbo) => new(mbo); + + /// + public void Dispose() => ((IDisposable)obj).Dispose(); + + /// + public override IEnumerable GetDynamicMemberNames() => obj.Properties.Cast().Select(d => d.Name); + + /// + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (binder.Type == typeof(ManagementObject)) + { + result = obj; + return true; + } + return base.TryConvert(binder, out result); + } + + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + try { result = obj.GetPropertyValue(binder.Name); return true; } + catch (ManagementException) + { + result = binder.Name switch + { + "ClassPath" or "ManagementClassPath" => obj.ClassPath, + "Path" or "ManagementPath" => obj.Path, + "Scope" or "ManagementScope" => obj.Scope, + _ => null + }; + return result is not null; + } + } + + /// + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + if (args.Length == 1) + { + ManagementBaseObject input = null; + if (args[0] is ManagementBaseObject mbo) + { + input = mbo; + } + else if (args[0] is IDictionary eo) + { + input = obj.GetMethodParameters(binder.Name); + foreach (KeyValuePair kv in eo) + input[kv.Key] = kv.Value; + } + else if (args[0] is (string Key, object Value)[] a) + { + input = obj.GetMethodParameters(binder.Name); + foreach ((string Key, object Value) in a) + input[Key] = Value; + } + if (input is not null) + { + result = (DynamicMgmtObject)obj.InvokeMethod(binder.Name, input, null); + return true; + } + } + + try + { + result = obj.InvokeMethod(binder.Name, args); + return true; + } + catch (ManagementException) { } + return base.TryInvokeMember(binder, args, out result); + } + + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + try { obj.SetPropertyValue(binder.Name, value); return true; } + catch (ManagementException) + { + switch (binder.Name) + { + case "Path": + case "ManagementPath": + obj.Path = value as ManagementPath; + return true; + + case "Scope": + case "ManagementScope": + obj.Scope = value as ManagementScope; + return true; + + default: + return false; + } + } + } + } +} \ No newline at end of file diff --git a/Management/ManagementExtensions.cs b/Management/ManagementExtensions.cs new file mode 100644 index 00000000..cd7cf177 --- /dev/null +++ b/Management/ManagementExtensions.cs @@ -0,0 +1,226 @@ +using System; +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); + using ManagementBaseObject inParams = imgMgmtSvc.GetMethodParameters(method); + foreach ((string, object) kv in values) + inParams[kv.Item1] = kv.Item2; + + ManagementBaseObject outputParameters = imgMgmtSvc.InvokeMethod(method, inParams, null); + + 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, null); + var xml = new XmlDocument(); + xml.LoadXml(errOut.GetProp("Error")); + var errMsg = xml.DocumentElement.SelectSingleNode(@"//PROPERTY[@NAME='Message']/VALUE")?.InnerText; + throw new InvalidOperationException(errMsg); + 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(); + if (attr is null) + 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)) + { + 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() + { + var doc = new XmlDocument(); + doc.LoadXml(embeddedInstance); + + XmlNodeList nodelist = doc.SelectNodes(@"/INSTANCE/@CLASSNAME"); + 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)) + { + DataMemberAttribute attr = pi.GetCustomAttributes(false).FirstOrDefault() ?? new DataMemberAttribute() { Name = pi.Name }; + + nodelist = doc.SelectNodes($@"//PROPERTY[@NAME = '{attr.Name}']/VALUE/child::text()"); + if (attr.IsRequired && nodelist.Count != 1) + throw new FormatException(); + if (nodelist.Count == 0) + continue; + + if (pi.PropertyType.IsEnum) + { + TypeConverter cv = TypeDescriptor.GetConverter(pi.PropertyType.GetEnumUnderlyingType()); + var val = cv.ConvertFromInvariantString(nodelist[0].Value); + if (!Enum.IsDefined(pi.PropertyType, val)) + throw new FormatException(); + pi.SetValue(output, Enum.ToObject(pi.PropertyType, val)); + } + else + { + TypeConverter cv = TypeDescriptor.GetConverter(pi.PropertyType); + var val = cv.ConvertFromInvariantString(nodelist[0].Value); + pi.SetValue(output, val); + } + } + return output; + } + + /// 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; + } +} \ No newline at end of file diff --git a/Management/Vanara.Management.csproj b/Management/Vanara.Management.csproj new file mode 100644 index 00000000..852aff6b --- /dev/null +++ b/Management/Vanara.Management.csproj @@ -0,0 +1,20 @@ + + + + Extensions and helper classes for System.Management. + $(AssemblyName) + Vanara.Management + $(AssemblyName) + vanara;net-extensions;WMI + + + + + + + + + + + + \ No newline at end of file