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