From b00d4eddb5b6abb23c502b5c44bdb7300dbdc258 Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 1 Dec 2024 14:11:54 +0100 Subject: [PATCH] 38 getdeviceproperties need setpropertyvalue method has an array overload (#40) Changes WmiMethodParameters class to work with string arrays and WmiObjects. --- .github/workflows/build.yml | 8 +-- WmiLight.Native/WmiLight.Native.rc | 14 ++-- WmiLight.Native/WmiLight.Native.vcxproj | 24 +++---- WmiLight.UnitTests/Win32_PnPEntity.cs | 80 +++++++++++++++++++++++ WmiLight.UnitTestsGenerator/Program.cs | 1 + WmiLight/Internal/NativeMethods.cs | 13 +++- WmiLight/Wbem/WbemClassObject.cs | 63 ++++++++++++++++++ WmiLight/WmiClass.cs | 11 +++- WmiLight/WmiConnection.cs | 4 +- WmiLight/WmiLight.csproj | 2 +- WmiLight/WmiMethodParameters.cs | 63 +++--------------- WmiLight/WmiMethodParametersDefinition.cs | 8 ++- WmiLight/WmiObject.cs | 4 +- 13 files changed, 206 insertions(+), 89 deletions(-) create mode 100644 WmiLight.UnitTests/Win32_PnPEntity.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ce1e48..968edf0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: dotnet-version: 8.x - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1.1.3 + uses: microsoft/setup-msbuild@v2 - name: Restore NuGet packages run: dotnet restore @@ -35,18 +35,18 @@ jobs: run: | echo "${{ secrets.PRIVATE_KEY }}" > WmiLight\private_key.base64 certutil -decode WmiLight\private_key.base64 WmiLight\private_key.snk - rm WmiLight\private_key.base64 -v + rm WmiLight\private_key.base64 - name: Build .Net Lib run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} WmiLight\WmiLight.csproj - name: Cleanup Private Key - run: rm WmiLight\private_key.snk -v + run: rm WmiLight\private_key.snk - name: Build Native dynamic Lib (x64) # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:ConfigurationType=DynamicLibrary /p:Platform=x64 WmiLight.Native\WmiLight.Native.vcxproj - + - name: Build Native dynamic Lib (x86) # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:ConfigurationType=DynamicLibrary /p:Platform=x86 WmiLight.Native\WmiLight.Native.vcxproj diff --git a/WmiLight.Native/WmiLight.Native.rc b/WmiLight.Native/WmiLight.Native.rc index 0823430..1bb0384 100644 --- a/WmiLight.Native/WmiLight.Native.rc +++ b/WmiLight.Native/WmiLight.Native.rc @@ -43,8 +43,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 6,7,0,0 - PRODUCTVERSION 6,7,0,0 + FILEVERSION 6,8,0,0 + PRODUCTVERSION 6,8,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -60,22 +60,22 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Martin Kuschnik" -#if _x86 +#if __x86 VALUE "FileDescription", "The native (x86) part of the WmiLight lib." -#elif _x64 +#elif __x64 VALUE "FileDescription", "The native (x64) part of the WmiLight lib." -#elif _arm64 +#elif __arm64 VALUE "FileDescription", "The native (ARM64) part of the WmiLight lib." #else VALUE "FileDescription", "The native part of the WmiLight lib." #endif - VALUE "FileVersion", "6.7.0.0" + VALUE "FileVersion", "6.8.0.0" VALUE "InternalName", "WmiLight.Native" VALUE "LegalCopyright", "Copyright 2024 Martin Kuschnik" VALUE "OriginalFilename", "WmiLight.Native.dll" VALUE "ProductName", "WmiLight" - VALUE "ProductVersion", "6.7.0.0" + VALUE "ProductVersion", "6.8.0.0" END END BLOCK "VarFileInfo" diff --git a/WmiLight.Native/WmiLight.Native.vcxproj b/WmiLight.Native/WmiLight.Native.vcxproj index 84f764e..9286171 100644 --- a/WmiLight.Native/WmiLight.Native.vcxproj +++ b/WmiLight.Native/WmiLight.Native.vcxproj @@ -125,7 +125,7 @@ Level3 true - _x86;WIN32;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + ___x86;WIN32;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h @@ -138,7 +138,7 @@ wbemuuid.lib;%(AdditionalDependencies) - _x86;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __x86;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -147,7 +147,7 @@ true true true - _x86;WIN32;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + __x86;WIN32;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h @@ -162,14 +162,14 @@ wbemuuid.lib;%(AdditionalDependencies) - _x86;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __x86;_UNICODE;UNICODE;%(PreprocessorDefinitions) Level3 true - _x64;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + __x64;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h @@ -182,14 +182,14 @@ wbemuuid.lib;%(AdditionalDependencies) - _x64;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __x64;_UNICODE;UNICODE;%(PreprocessorDefinitions) Level3 true - _arm64;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + __arm64;_DEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h @@ -202,7 +202,7 @@ wbemuuid.lib;%(AdditionalDependencies) - _arm64;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __arm64;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -211,7 +211,7 @@ true true true - _x64;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;x64;%(PreprocessorDefinitions) + __x64;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;x64;%(PreprocessorDefinitions) true Use pch.h @@ -226,7 +226,7 @@ wbemuuid.lib;%(AdditionalDependencies) - _x64;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __x64;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -235,7 +235,7 @@ true true true - _arm64;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;x64;%(PreprocessorDefinitions) + __arm64;NDEBUG;WMILIGHTNATIVE_EXPORTS;_WINDOWS;_USRDLL;x64;%(PreprocessorDefinitions) true Use pch.h @@ -250,7 +250,7 @@ wbemuuid.lib;%(AdditionalDependencies) - _arm64;_UNICODE;UNICODE;%(PreprocessorDefinitions) + __arm64;_UNICODE;UNICODE;%(PreprocessorDefinitions) diff --git a/WmiLight.UnitTests/Win32_PnPEntity.cs b/WmiLight.UnitTests/Win32_PnPEntity.cs new file mode 100644 index 0000000..0fa144c --- /dev/null +++ b/WmiLight.UnitTests/Win32_PnPEntity.cs @@ -0,0 +1,80 @@ +namespace WmiLight.UnitTests +{ + [TestClass] + public class Win32_PnPEntity + { + [TestMethod] + public void Win32_PnPEntity_GetDeviceProperties_Can_Be_Called_With_String_Array_Parameter() + { + using (WmiConnection conncetion = new WmiConnection()) + { + using (WmiMethod method = conncetion.GetMethod("Win32_PnPEntity", "GetDeviceProperties")) + { + foreach (WmiObject pnpEntity in conncetion.CreateQuery("SELECT * FROM Win32_PnPEntity")) + { + uint result = pnpEntity.ExecuteMethod(method, out WmiMethodParameters outParameters); + + Assert.AreEqual(0u, outParameters.GetPropertyValue("ReturnValue")); + Assert.AreEqual(result, outParameters.GetPropertyValue("ReturnValue")); + + WmiObject[] allDeviceProperties = outParameters.GetPropertyValue("deviceProperties"); + + if (allDeviceProperties.Length > 2) + { + WmiMethodParameters inParameters = method.CreateInParameters(); + + inParameters.SetPropertyValue("devicePropertyKeys", allDeviceProperties.Take(2).Select(x => x.GetPropertyValue("KeyName")).ToArray()); + + result = pnpEntity.ExecuteMethod(method, inParameters, out outParameters); + + Assert.AreEqual(0u, outParameters.GetPropertyValue("ReturnValue")); + Assert.AreEqual(result, outParameters.GetPropertyValue("ReturnValue")); + + WmiObject[] requestedDeviceProperties = outParameters.GetPropertyValue("deviceProperties"); + + // 2 properties expected + Assert.AreEqual(2, requestedDeviceProperties.Length); + + return; + } + } + + // if no matching Win32_PnPEntity found + Assert.Inconclusive(); + } + } + } + + [TestMethod] + public void Win32_PnPEntity_GetDeviceProperties_Can_Be_Called_Null_Parameter() + { + using (WmiConnection conncetion = new WmiConnection()) + { + using (WmiMethod method = conncetion.GetMethod("Win32_PnPEntity", "GetDeviceProperties")) + { + foreach (WmiObject pnpEntity in conncetion.CreateQuery("SELECT * FROM Win32_PnPEntity")) + { + WmiMethodParameters inParameters = method.CreateInParameters(); + + inParameters.SetPropertyValue("devicePropertyKeys", null as string[]); + + uint result = pnpEntity.ExecuteMethod(method, inParameters, out WmiMethodParameters outParameters); + + Assert.AreEqual(0u, outParameters.GetPropertyValue("ReturnValue")); + Assert.AreEqual(result, outParameters.GetPropertyValue("ReturnValue")); + + WmiObject[] allDeviceProperties = outParameters.GetPropertyValue("deviceProperties"); + + Assert.IsTrue(allDeviceProperties.Length > 0); + + // Test done! + return; + } + + // if no matching Win32_PnPEntity found + Assert.Inconclusive(); + } + } + } + } +} \ No newline at end of file diff --git a/WmiLight.UnitTestsGenerator/Program.cs b/WmiLight.UnitTestsGenerator/Program.cs index 2c8ac9c..30c4757 100644 --- a/WmiLight.UnitTestsGenerator/Program.cs +++ b/WmiLight.UnitTestsGenerator/Program.cs @@ -32,6 +32,7 @@ static void Main(string[] args) new Tuple( "root\\cimv2","Win32_Printer" , []), new Tuple( "root\\cimv2","Win32_PhysicalMemory" , []), new Tuple( "root\\cimv2","Win32_Process" , ["KernelModeTime"]), + new Tuple( "root\\cimv2","Win32_PnPEntity" , []), new Tuple( "root\\cimv2","Win32_Processor" , ["KernelModeTime", "LoadPercentage", "L3CacheSize"]), new Tuple( "root\\cimv2","Win32_Registry" , ["KernelModeTime", "LoadPercentage"]), new Tuple( "root\\cimv2","Win32_Service" , ["DisconnectedSessions", "TotalSessions"]), diff --git a/WmiLight/Internal/NativeMethods.cs b/WmiLight/Internal/NativeMethods.cs index cdeb94b..0526d51 100644 --- a/WmiLight/Internal/NativeMethods.cs +++ b/WmiLight/Internal/NativeMethods.cs @@ -16,7 +16,7 @@ internal static class NativeMethods /// /// Name of the native WmiLight DLL. /// - private const string NATIVE_DLL_NAME = "WmiLight.Native.dll"; + private const string NATIVE_DLL_NAME = "WmiLight.Native.dll"; #endregion @@ -112,7 +112,7 @@ public static extern HResult ExecNotificationQueryAsync( [DllImport(NATIVE_DLL_NAME, CallingConvention = CallingConvention.StdCall)] public static extern HResult DeleteInstance(IntPtr pWbemServices, [MarshalAs(UnmanagedType.LPWStr)] string strObjectPath, IntPtr ctx); - + [DllImport(NATIVE_DLL_NAME, CallingConvention = CallingConvention.StdCall)] public static extern HResult CreateEventSinkStub(IntPtr pUnsecApp, IntPtr pEventSink, Indicate indicateFunction, SetStatus setStatusFunction, out IntPtr eventSinkStub); @@ -164,6 +164,15 @@ public static extern HResult Get( [DllImport("oleaut32.dll", CallingConvention = CallingConvention.StdCall)] public static extern uint SafeArrayGetDim(IntPtr psa); + [DllImport("oleaut32.dll", CallingConvention = CallingConvention.StdCall)] + public static extern IntPtr SafeArrayCreateVector(VARENUM vt, int lLbound, uint cElements); + + [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)] + public static extern HResult SafeArrayPutElement(IntPtr psa, int[] rgIndices, IntPtr pv); + + [DllImport("oleaut32.dll", CallingConvention = CallingConvention.StdCall)] + public static extern HResult SafeArrayDestroy(IntPtr psa); + #endregion #region Propsys.dll diff --git a/WmiLight/Wbem/WbemClassObject.cs b/WmiLight/Wbem/WbemClassObject.cs index b67c33a..ab32640 100644 --- a/WmiLight/Wbem/WbemClassObject.cs +++ b/WmiLight/Wbem/WbemClassObject.cs @@ -260,6 +260,69 @@ internal void Put(string propertyName, string propertyValue) } } + internal void Put(string propertyName, string[] propertyValue) + { + if (this.Disposed) + throw new ObjectDisposedException(nameof(WbemClassObject)); + + if (propertyValue != null) + { + IntPtr pSafeArray = NativeMethods.SafeArrayCreateVector(VARENUM.VT_BSTR, 0, (uint)propertyValue.Length); + + int index = 0; + + HResult hResult; + + try + { + foreach (string currentPropValue in propertyValue) + { + IntPtr pCurrentPropValue = Marshal.StringToBSTR(currentPropValue); + + try + { + hResult = NativeMethods.SafeArrayPutElement(pSafeArray, new int[] { index++ }, pCurrentPropValue); + + if (hResult.Failed) + throw (Exception)hResult; + } + finally + { + Marshal.FreeBSTR(pCurrentPropValue); + } + } + + VARIANT value = new VARIANT() { vt = VARENUM.VT_BSTR | VARENUM.VT_ARRAY, BStrVal = pSafeArray }; + + hResult = NativeMethods.Put(this, propertyName, ref value, CimType.None); /* Always CimType.None because this parameter is only for new properties. */ + + if (hResult.Failed) + throw (Exception)hResult; + } + finally + { + NativeMethods.SafeArrayDestroy(pSafeArray); + } + } + else + { + VARIANT value = new VARIANT() { vt = VARENUM.VT_NULL }; + + try + { + HResult hResult = NativeMethods.Put(this, propertyName, ref value, CimType.None); /* Always CimType.None because this parameter is only for new properties. */ + + if (hResult.Failed) + throw (Exception)hResult; + } + finally + { + if (value.BStrVal != IntPtr.Zero) + Marshal.FreeBSTR(value.BStrVal); + } + } + } + private static object VariantToObject(ref VARIANT value, CimType type) { if (type.HasFlag(CimType.ArrayFlag)) diff --git a/WmiLight/WmiClass.cs b/WmiLight/WmiClass.cs index cef3744..2a55c02 100644 --- a/WmiLight/WmiClass.cs +++ b/WmiLight/WmiClass.cs @@ -12,17 +12,24 @@ [DebuggerDisplay("{Name,nq}")] public class WmiClass : IDisposable { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly WbemServices wbemServices; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly WbemClassObject wbemClassObject; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool disposed; - internal WmiClass(WbemClassObject wbemClassObject) + internal WmiClass(WbemServices wbemServices, WbemClassObject wbemClassObject) { + if (wbemServices is null) + throw new ArgumentNullException(nameof(wbemServices)); + if (wbemClassObject is null) throw new ArgumentNullException(nameof(wbemClassObject)); + this.wbemServices = wbemServices; this.wbemClassObject = wbemClassObject; } @@ -66,7 +73,7 @@ public WmiMethod GetMethod(string methodName) this.wbemClassObject.GetMethod(methodName, out WbemClassObject inSignatur, out WbemClassObject outSignatur); - return new WmiMethod(this, methodName, inSignatur == null ? null : new WmiMethodParametersDefinition(inSignatur), outSignatur == null ? null : new WmiMethodParametersDefinition(outSignatur)); + return new WmiMethod(this, methodName, inSignatur == null ? null : new WmiMethodParametersDefinition(this.wbemServices, inSignatur), outSignatur == null ? null : new WmiMethodParametersDefinition( this.wbemServices, outSignatur)); } #region Description diff --git a/WmiLight/WmiConnection.cs b/WmiLight/WmiConnection.cs index bcd7ea6..54d6b25 100644 --- a/WmiLight/WmiConnection.cs +++ b/WmiLight/WmiConnection.cs @@ -395,7 +395,7 @@ public WmiClass GetClass(string className) this.Open(); - return new WmiClass(this.wbemServices.GetClass(className)); + return new WmiClass(this.wbemServices, this.wbemServices.GetClass(className)); } #region Description @@ -580,7 +580,7 @@ private TResult ExecuteMethod(string classNameOrPath, string methodName this.wbemServices.ExecuteMethod(classNameOrPath, methodName, inParameters, out WbemClassObject wbemOutParams); - outParameters = new WmiMethodParameters(wbemOutParams); + outParameters = new WmiMethodParameters(this.wbemServices, wbemOutParams); return outParameters.GetPropertyValue("ReturnValue"); } diff --git a/WmiLight/WmiLight.csproj b/WmiLight/WmiLight.csproj index 9350f0b..49e8be5 100644 --- a/WmiLight/WmiLight.csproj +++ b/WmiLight/WmiLight.csproj @@ -23,7 +23,7 @@ - 6.7.0 + 6.8.0 WmiLight Martin Kuschnik Martin Kuschnik diff --git a/WmiLight/WmiMethodParameters.cs b/WmiLight/WmiMethodParameters.cs index f529ad4..582c462 100644 --- a/WmiLight/WmiMethodParameters.cs +++ b/WmiLight/WmiMethodParameters.cs @@ -10,59 +10,17 @@ namespace WmiLight /// Represents the in and out parameters of a WMI defined method. /// #endregion - public class WmiMethodParameters : IDisposable + public class WmiMethodParameters : WmiObject { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private WbemClassObject signatur; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private bool disposed; - - internal WmiMethodParameters(WbemClassObject signatur) + internal WmiMethodParameters(WbemServices wbemServices, WbemClassObject signatur) + :base(wbemServices, signatur) { this.signatur = signatur ?? throw new ArgumentNullException(nameof(signatur)); } - #region Description - /// - /// Gets the faked fields for the debugger view. - /// - #endregion - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - private DebuggerFakeField[] DebuggerFakeFields - { - get - { - return signatur.GetNames().Select(x => new DebuggerFakeField(x, this.signatur.Get(x))).ToArray(); - } - } - - - #region Description - /// - /// Gets the value of a property. - /// - /// The name of the requested property. - /// The value of the requested property. - #endregion - public object GetPropertyValue(string propertyName) - { - return this.signatur.Get(propertyName); - } - - #region Description - /// - /// Gets the value of a property. - /// - /// The type of the requested property. - /// The name of the requested property. - /// The value of the requested property. - #endregion - public TResult GetPropertyValue(string propertyName) - { - return this.signatur.Get(propertyName); - } - #region Description /// /// Sets the value of a property. @@ -183,6 +141,11 @@ public void SetPropertyValue(string propertyName, string propertyValue) this.signatur.Put(propertyName, propertyValue); } + public void SetPropertyValue(string propertyName, string[] propertyValue) + { + this.signatur.Put(propertyName, propertyValue); + } + #region Description /// /// Implicit conversion from to . @@ -191,15 +154,5 @@ public void SetPropertyValue(string propertyName, string propertyValue) /// The value as . #endregion public static implicit operator IntPtr(WmiMethodParameters parameters) => parameters.signatur; - - /// - public void Dispose() - { - if (!this.disposed) - { - this.signatur.Dispose(); - this.disposed = true; - } - } } } diff --git a/WmiLight/WmiMethodParametersDefinition.cs b/WmiLight/WmiMethodParametersDefinition.cs index 3103590..f9e4a3a 100644 --- a/WmiLight/WmiMethodParametersDefinition.cs +++ b/WmiLight/WmiMethodParametersDefinition.cs @@ -6,14 +6,18 @@ namespace WmiLight { internal class WmiMethodParametersDefinition { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly WbemServices wbemServices; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private WbemClassObject signatur; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool disposed; - internal WmiMethodParametersDefinition(WbemClassObject signatur) + internal WmiMethodParametersDefinition(WbemServices wbemServices, WbemClassObject signatur) { + this.wbemServices = wbemServices ?? throw new System.ArgumentNullException(nameof(wbemServices)); this.signatur = signatur ?? throw new System.ArgumentNullException(nameof(signatur)); } @@ -33,7 +37,7 @@ private DebuggerFakeField[] DebuggerFakeFields internal WmiMethodParameters SpawnInstance() { - return new WmiMethodParameters(this.signatur.SpawnInstance()); + return new WmiMethodParameters(this.wbemServices, this.signatur.SpawnInstance()); } /// diff --git a/WmiLight/WmiObject.cs b/WmiLight/WmiObject.cs index 96271ee..23aed11 100644 --- a/WmiLight/WmiObject.cs +++ b/WmiLight/WmiObject.cs @@ -241,7 +241,7 @@ public object this[string propertyName] #endregion public WmiMethod GetMethod(string methodName) { - WmiClass wmiClass = new WmiClass(this.wbemServices.GetClass(this.Class)); + WmiClass wmiClass = new WmiClass( this.wbemServices, this.wbemServices.GetClass(this.Class)); return wmiClass.GetMethod(methodName); } @@ -408,7 +408,7 @@ private TReturnValue ExecuteMethod(string method, IntPtr inParamet { this.wbemServices.ExecuteMethod(this.Path, method, inParameters, out WbemClassObject wbemOutParams); - outParameters = new WmiMethodParameters(wbemOutParams); + outParameters = new WmiMethodParameters(this.wbemServices, wbemOutParams); return outParameters.GetPropertyValue("ReturnValue"); }