From 6c124b1731d3b5f2f078cddca225dda477af3e2e Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sat, 17 Jan 2026 09:40:25 +0100 Subject: [PATCH 1/6] Updated all SetProxy function signatures and related calls to use 'domain' instead of 'authority' across both native (C++) and managed (C#) code. Related to #59 --- WmiLight.Native/WmiLightNative.cpp | 11 +++++------ WmiLight/Exceptions/LocalCredentialsException.cs | 2 +- WmiLight/Exceptions/TransportFailureException.cs | 2 +- WmiLight/Internal/HResult.cs | 2 +- WmiLight/Internal/NativeMethods.cs | 10 +++++----- WmiLight/Internal/NativeMethods.netcore.cs | 2 +- WmiLight/Wbem/Enumerations/WbemStatus.cs | 2 +- WmiLight/Wbem/WbemServices.cs | 8 ++++---- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/WmiLight.Native/WmiLightNative.cpp b/WmiLight.Native/WmiLightNative.cpp index 5d313e7..ecfcec0 100644 --- a/WmiLight.Native/WmiLightNative.cpp +++ b/WmiLight.Native/WmiLightNative.cpp @@ -87,19 +87,18 @@ extern "C" { // only need to export C interface if ); } - __declspec(dllexport) HRESULT _stdcall SetProxy( IUnknown* pIUnknown, wchar_t* username, wchar_t* password, - wchar_t* authority, + wchar_t* domain, ImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel) { if (pIUnknown == nullptr) return E_POINTER; - if (username == nullptr && password == nullptr && authority == nullptr) + if (username == nullptr && password == nullptr && domain == nullptr) { return CoSetProxyBlanket( pIUnknown, @@ -116,9 +115,9 @@ extern "C" { // only need to export C interface if authInfo.User = (unsigned short*)username; authInfo.UserLength = username == nullptr ? 0 : static_cast(wcslen(username)); - - authInfo.Domain = (unsigned short*)authority; - authInfo.DomainLength = authority == nullptr ? 0 : static_cast(wcslen(authority)); + + authInfo.Domain = (unsigned short*)domain; + authInfo.DomainLength = domain == nullptr ? 0 : static_cast(wcslen(domain)); authInfo.Password = (unsigned short*)password; authInfo.PasswordLength = password == nullptr ? 0 : static_cast(wcslen(password)); diff --git a/WmiLight/Exceptions/LocalCredentialsException.cs b/WmiLight/Exceptions/LocalCredentialsException.cs index 8b23c81..db8f1a5 100644 --- a/WmiLight/Exceptions/LocalCredentialsException.cs +++ b/WmiLight/Exceptions/LocalCredentialsException.cs @@ -2,7 +2,7 @@ { #region Description /// - /// The Exception for the case that the Username, password, or authority are specified for a local . + /// The Exception for the case that the Username, password, or domain are specified for a local . /// #endregion public class LocalCredentialsException : WmiException diff --git a/WmiLight/Exceptions/TransportFailureException.cs b/WmiLight/Exceptions/TransportFailureException.cs index 1cfa9b7..9b57d82 100644 --- a/WmiLight/Exceptions/TransportFailureException.cs +++ b/WmiLight/Exceptions/TransportFailureException.cs @@ -2,7 +2,7 @@ { #region Description /// - /// The Exception for the case that the Username, password, or authority are specified for a local . + /// The Exception that is thrown when the WMI service returns WBEM_E_TRANSPORT_FAILURE. /// #endregion public class TransportFailureException : WmiException diff --git a/WmiLight/Internal/HResult.cs b/WmiLight/Internal/HResult.cs index 3e51168..3fd544a 100644 --- a/WmiLight/Internal/HResult.cs +++ b/WmiLight/Internal/HResult.cs @@ -190,7 +190,7 @@ public static implicit operator Exception(HResult hr) return new TransportFailureException(new HResultInfo(hr, "The remote procedure call (RPC) link between the current process and WMI failed.", WbemStatus.WBEM_E_TRANSPORT_FAILURE.ToString())); case (int)WbemStatus.WBEM_E_LOCAL_CREDENTIALS: - return new LocalCredentialsException(new HResultInfo(hr, "Username, password, or authority can only used on a remote connection.", WbemStatus.WBEM_E_LOCAL_CREDENTIALS.ToString())); + return new LocalCredentialsException(new HResultInfo(hr, "Username, password, or domain can only used on a remote connection.", WbemStatus.WBEM_E_LOCAL_CREDENTIALS.ToString())); case (int)WbemStatus.WBEM_E_FAILED: return new WmiException(new HResultInfo(hr, "An unspecified error occurred.", WbemStatus.WBEM_E_FAILED.ToString())); diff --git a/WmiLight/Internal/NativeMethods.cs b/WmiLight/Internal/NativeMethods.cs index aeb51b9..2286d89 100644 --- a/WmiLight/Internal/NativeMethods.cs +++ b/WmiLight/Internal/NativeMethods.cs @@ -86,7 +86,7 @@ public static extern HResult SetProxy( [MarshalAs(UnmanagedType.LPWStr)] string password, [MarshalAs(UnmanagedType.LPWStr)] - string authority, + string domain, ImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel); @@ -197,7 +197,7 @@ public static extern HResult SetProxy( [MarshalAs(UnmanagedType.LPWStr)] string password, [MarshalAs(UnmanagedType.LPWStr)] - string authority, + string domain, ImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel); @@ -314,14 +314,14 @@ public static HResult SetProxy( IntPtr pIUnknown, string username, string password, - string authority, + string domain, ImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel) { if (Environment.Is64BitProcess) - return x64.SetProxy(pIUnknown, username, password, authority, impersonationLevel, authenticationLevel); + return x64.SetProxy(pIUnknown, username, password, domain, impersonationLevel, authenticationLevel); else - return x86.SetProxy(pIUnknown, username, password, authority, impersonationLevel, authenticationLevel); + return x86.SetProxy(pIUnknown, username, password, domain, impersonationLevel, authenticationLevel); } public static HResult ExecQuery( IntPtr pWbemServices, diff --git a/WmiLight/Internal/NativeMethods.netcore.cs b/WmiLight/Internal/NativeMethods.netcore.cs index e4c7fd8..121bbe1 100644 --- a/WmiLight/Internal/NativeMethods.netcore.cs +++ b/WmiLight/Internal/NativeMethods.netcore.cs @@ -88,7 +88,7 @@ public static partial HResult SetProxy( [MarshalAs(UnmanagedType.LPWStr)] string password, [MarshalAs(UnmanagedType.LPWStr)] - string authority, + string domain, ImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel); diff --git a/WmiLight/Wbem/Enumerations/WbemStatus.cs b/WmiLight/Wbem/Enumerations/WbemStatus.cs index 4572275..59a5f6c 100644 --- a/WmiLight/Wbem/Enumerations/WbemStatus.cs +++ b/WmiLight/Wbem/Enumerations/WbemStatus.cs @@ -160,7 +160,7 @@ internal enum WbemStatus : int #region Description /// - /// The user specified a username, password, or authority on a + /// The user specified a username, password, or domain on a /// local connection. The user must use an empty user name and password and rely on /// default security. /// diff --git a/WmiLight/Wbem/WbemServices.cs b/WmiLight/Wbem/WbemServices.cs index 3354d54..fa66abf 100644 --- a/WmiLight/Wbem/WbemServices.cs +++ b/WmiLight/Wbem/WbemServices.cs @@ -26,12 +26,12 @@ internal void SetProxy(ImpersonationLevel impersonate, AuthenticationLevel authL throw (Exception)hResult; } - internal void SetProxy(string userName, string password, string authority, ImpersonationLevel impersonate, AuthenticationLevel authLevel) + internal void SetProxy(string userName, string password, string domain, ImpersonationLevel impersonate, AuthenticationLevel authLevel) { if (this.Disposed) throw new ObjectDisposedException(nameof(WbemServices)); - HResult hResult = NativeMethods.SetProxy(this, userName, password, authority, impersonate, authLevel); + HResult hResult = NativeMethods.SetProxy(this, userName, password, domain, impersonate, authLevel); if (hResult.Failed) throw (Exception)hResult; @@ -61,7 +61,7 @@ internal WbemClassObjectEnumerator ExecQuery(string query, WbemClassObjectEnumer return new WbemClassObjectEnumerator(pEnumerator); } - internal WbemClassObjectEnumerator ExecQuery(string query, WbemClassObjectEnumeratorBehaviorOption behaviorOption, IntPtr ctx, string userName, string password, string authority, AuthenticationLevel authLevel, ImpersonationLevel impersonate) + internal WbemClassObjectEnumerator ExecQuery(string query, WbemClassObjectEnumeratorBehaviorOption behaviorOption, IntPtr ctx, string userName, string password, string domain, AuthenticationLevel authLevel, ImpersonationLevel impersonate) { if (this.Disposed) throw new ObjectDisposedException(nameof(WbemServices)); @@ -82,7 +82,7 @@ internal WbemClassObjectEnumerator ExecQuery(string query, WbemClassObjectEnumer } } - hResult = NativeMethods.SetProxy(pEnumerator, userName, password, authority, impersonate, authLevel); + hResult = NativeMethods.SetProxy(pEnumerator, userName, password, domain, impersonate, authLevel); if (hResult.Failed) throw (Exception)hResult; From ae18a7758b18be85b995031e5357d692ebc0d184 Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 18 Jan 2026 10:54:30 +0100 Subject: [PATCH 2/6] Removed manual NTLM/Kerberos authority selection; authentication protocol is now negotiated automatically by Windows. --- README.md | 4 +- WmiLight.Native/WmiLightNative.cpp | 2 +- WmiLight/Internal/NormalizedCredential.cs | 67 +++++++++++++++++++++++ WmiLight/WmiConnection.cs | 67 +++++++++++------------ WmiLight/WmiConnectionOptions.cs | 11 ++-- 5 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 WmiLight/Internal/NormalizedCredential.cs diff --git a/README.md b/README.md index 5c24935..598273a 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,11 @@ using (WmiConnection con = new WmiConnection()) } ``` -Query all partitions for a remote machine with credentials: +Query all partitions for a remote machine with credentials (UPN format recommended): ```C# var opt = new WmiConnectionOptions() { EnablePackageEncryption = true }; -var cred = new NetworkCredential("USERNAME", "PASSWORD", "DOMAIN"); +var cred = new NetworkCredential("wmi_user@WMI-Test", "PASSWORD"); using (WmiConnection con = new WmiConnection(@"\\MACHINENAME\root\cimv2", cred, opt)) { diff --git a/WmiLight.Native/WmiLightNative.cpp b/WmiLight.Native/WmiLightNative.cpp index ecfcec0..5a025a7 100644 --- a/WmiLight.Native/WmiLightNative.cpp +++ b/WmiLight.Native/WmiLightNative.cpp @@ -128,7 +128,7 @@ extern "C" { // only need to export C interface if pIUnknown, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, - nullptr, + COLE_DEFAULT_PRINCIPAL, authenticationLevel, impersonationLevel, &authInfo, diff --git a/WmiLight/Internal/NormalizedCredential.cs b/WmiLight/Internal/NormalizedCredential.cs new file mode 100644 index 0000000..4be0cac --- /dev/null +++ b/WmiLight/Internal/NormalizedCredential.cs @@ -0,0 +1,67 @@ +namespace WmiLight +{ + using System; + using System.Diagnostics; + using System.Net; + + internal class NormalizedCredential + { + internal NormalizedCredential(NetworkCredential networkCredential) + { + if (networkCredential.UserName != null) + { + string[] usernameParts = networkCredential.UserName.Split('\\'); + + if (usernameParts.Length == 2) + { + this.UserNameWithoutDomain = usernameParts[1]; + this.UserNameWithDomain = networkCredential.UserName; + + if (string.IsNullOrEmpty(networkCredential.Domain)) + this.Domain = usernameParts[0]; + else + this.Domain = networkCredential.Domain; + } + else + { + usernameParts = networkCredential.UserName.Split('@'); + + if (usernameParts.Length == 2) + { + this.UserNameWithoutDomain = usernameParts[0]; + this.UserNameWithDomain = networkCredential.UserName; + + if (string.IsNullOrEmpty(networkCredential.Domain) || string.Equals(networkCredential.Domain, usernameParts[1], StringComparison.OrdinalIgnoreCase)) + this.Domain = usernameParts[1]; + else + this.Domain = networkCredential.Domain; + + } + else + { + this.UserNameWithoutDomain = networkCredential.UserName; + this.UserNameWithDomain = string.IsNullOrEmpty(networkCredential.Domain) ? networkCredential.UserName : $"{networkCredential.Domain}\\{networkCredential.UserName}"; + this.Domain = networkCredential.Domain; + } + } + } + else + { + this.UserNameWithoutDomain = null; + this.UserNameWithDomain = null; + this.Domain = networkCredential.Domain; + } + + this.Password = networkCredential.Password; + } + + internal string UserNameWithoutDomain { get; } + + internal string UserNameWithDomain { get; } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal string Password { get; } + + internal string Domain { get; } + } +} diff --git a/WmiLight/WmiConnection.cs b/WmiLight/WmiConnection.cs index 8ec3655..8441f2b 100644 --- a/WmiLight/WmiConnection.cs +++ b/WmiLight/WmiConnection.cs @@ -41,7 +41,7 @@ public partial class WmiConnection : IDisposable /// #endregion [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly NetworkCredential credential; + private readonly NormalizedCredential credential; #region Description /// @@ -163,7 +163,9 @@ public WmiConnection(string path) public WmiConnection(string path, WmiConnectionOptions options) { this.path = path; - this.options = options; + + if (options != null) + this.options = options; } #region Description @@ -182,7 +184,9 @@ public WmiConnection(string path, WmiConnectionOptions options) public WmiConnection(string path, NetworkCredential credential) { this.path = path; - this.credential = credential; + + if (credential != null) + this.credential = new NormalizedCredential(credential); } #region Description @@ -202,8 +206,12 @@ public WmiConnection(string path, NetworkCredential credential) public WmiConnection(string path, NetworkCredential credential, WmiConnectionOptions options) { this.path = path; - this.credential = credential; - this.options = options; + + if (credential != null) + this.credential = new NormalizedCredential(credential); + + if (options != null) + this.options = options; } #endregion @@ -229,34 +237,6 @@ public bool IsConnected } } - #region Description - /// - /// Gets the Authority for a remote connection. - /// - #endregion - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private string Authority - { - get - { - if (this.credential != null && !string.IsNullOrWhiteSpace(this.credential.Domain)) - { - if (this.options.AuthenticationProtocol == AuthenticationProtocol.Kerberos) - { - return string.Format("Kerberos: {0}", this.credential.Domain); - } - else - { - return string.Format("NTLMDOMAIN: {0}", this.credential.Domain); - } - } - else - { - return null; - } - } - } - #region Description /// /// Gets a value indicating whether the configuration is remote. @@ -301,8 +281,20 @@ public void Open() { try { - this.wbemServices = locator.ConnectServer(this.path, this.credential.UserName, this.credential.Password, null, WbemConnectOption.None, this.Authority, this.context); - this.wbemServices.SetProxy(this.credential.UserName, this.credential.Password, this.Authority, ImpersonationLevel.Impersonate, authLevel); + // Authority is always null. Therefore, the operating system negotiates with COM to determine whether NTLM or Kerberos authentication is used. + // + // Until version 8.0.0, I attempted to let the user decide whether NTLM or Kerberos should be used. + // When NTLM was chosen, the Authority was set to "NTLMDomain:" to enforce NTLM usage. This worked well for NTLM. + // Unfortunately, with "Kerberos:", it always resulted in the following DCOM error, regardless of how the domain was configured. + // + // DCOM was unable to communicate with the computer COMPUTER.DOMAIN using any of the configured protocols; + // requested by PID 1dd8 (EXE PATH), while activating CLSID {8BC3F05E-D86B-11D0-A075-00C04FB68820}. + // + // To minimize support requests and frustration for the end user, I now rely on Windows to negotiate the correct protocol automatically. + + this.wbemServices = locator.ConnectServer(this.path, this.credential.UserNameWithDomain, this.credential.Password, null, WbemConnectOption.None, null, this.context); + + this.wbemServices.SetProxy(this.credential.UserNameWithoutDomain, this.credential.Password, this.credential.Domain, ImpersonationLevel.Impersonate, authLevel); } catch (LocalCredentialsException) { @@ -612,6 +604,9 @@ internal WmiEventSubscription ExecNotificationQueryAsync(string query, Action @@ -8,20 +10,21 @@ public class WmiConnectionOptions { #region Properties - + #region Description /// /// Gets or sets the mode of authentication. /// #endregion + [Obsolete("Manual selection of AuthenticationProtocol is no longer supported. The operating system now automatically determines whether NTLM or Kerberos authentication is used.")] public AuthenticationProtocol AuthenticationProtocol { get; set; } - + #region Description /// /// Gets or sets a value indicating whether the packages will be encrypted. /// #endregion - public bool EnablePackageEncryption { get; set; } + public bool EnablePackageEncryption { get; set; } #endregion } From 58dc26e957014da064f7618c6ef77b093718a54f Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 18 Jan 2026 10:56:36 +0100 Subject: [PATCH 3/6] Added missing doc --- WmiLight/Exceptions/InvalidMethodException.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WmiLight/Exceptions/InvalidMethodException.cs b/WmiLight/Exceptions/InvalidMethodException.cs index 837e062..bc73e9a 100644 --- a/WmiLight/Exceptions/InvalidMethodException.cs +++ b/WmiLight/Exceptions/InvalidMethodException.cs @@ -27,6 +27,11 @@ internal InvalidMethodException(string method, WbemStatus wbemStatus) #endregion + #region Description + /// + /// Gets the name of the invalid Method. + /// + #endregion public string Method { get; } } } From 00582c193ad7900440abb1056785a6b0b07894b0 Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 18 Jan 2026 10:58:11 +0100 Subject: [PATCH 4/6] fixed comment --- WmiLight/WmiConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WmiLight/WmiConnection.cs b/WmiLight/WmiConnection.cs index 8441f2b..5a49bd5 100644 --- a/WmiLight/WmiConnection.cs +++ b/WmiLight/WmiConnection.cs @@ -283,7 +283,7 @@ public void Open() { // Authority is always null. Therefore, the operating system negotiates with COM to determine whether NTLM or Kerberos authentication is used. // - // Until version 8.0.0, I attempted to let the user decide whether NTLM or Kerberos should be used. + // Until version 7.0.0, I attempted to let the user decide whether NTLM or Kerberos should be used. // When NTLM was chosen, the Authority was set to "NTLMDomain:" to enforce NTLM usage. This worked well for NTLM. // Unfortunately, with "Kerberos:", it always resulted in the following DCOM error, regardless of how the domain was configured. // From 93e3e47187d374fa427e188088411ad45cd6ef03 Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 18 Jan 2026 11:00:30 +0100 Subject: [PATCH 5/6] increased version to 7.1.0 --- WmiLight.Native/WmiLight.Native.rc | 8 ++++---- WmiLight/WmiLight.csproj | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WmiLight.Native/WmiLight.Native.rc b/WmiLight.Native/WmiLight.Native.rc index b38f96e..c18d168 100644 --- a/WmiLight.Native/WmiLight.Native.rc +++ b/WmiLight.Native/WmiLight.Native.rc @@ -43,8 +43,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 7,0,0,0 - PRODUCTVERSION 7,0,0,0 + FILEVERSION 7,1,0,0 + PRODUCTVERSION 7,1,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -70,12 +70,12 @@ BEGIN VALUE "FileDescription", "The native part of the WmiLight lib." #endif - VALUE "FileVersion", "7.0.0.0" + VALUE "FileVersion", "7.1.0.0" VALUE "InternalName", "WmiLight.Native" VALUE "LegalCopyright", "Copyright 2026 Martin Kuschnik" VALUE "OriginalFilename", "WmiLight.Native.dll" VALUE "ProductName", "WmiLight" - VALUE "ProductVersion", "7.0.0.0" + VALUE "ProductVersion", "7.1.0.0" END END BLOCK "VarFileInfo" diff --git a/WmiLight/WmiLight.csproj b/WmiLight/WmiLight.csproj index 9e012b5..c99c40c 100644 --- a/WmiLight/WmiLight.csproj +++ b/WmiLight/WmiLight.csproj @@ -23,7 +23,7 @@ - 7.0.0 + 7.1.0 WmiLight Martin Kuschnik Martin Kuschnik @@ -42,7 +42,7 @@ <_NeedToDownloadMicrosoftNetSdkCompilersToolsetPackage>true - 7.0.0 + 7.1.0 True From 370984676ff84b430a76026de23272bcf18d5868 Mon Sep 17 00:00:00 2001 From: Martin Kuschnik Date: Sun, 18 Jan 2026 11:02:09 +0100 Subject: [PATCH 6/6] Update README example to use generic UPN credential format Replaced specific username in NetworkCredential with a generic "USER@DOMAIN" format in the README example to better demonstrate recommended UPN usage for remote WMI connections. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 598273a..112df40 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Query all partitions for a remote machine with credentials (UPN format recommend ```C# var opt = new WmiConnectionOptions() { EnablePackageEncryption = true }; -var cred = new NetworkCredential("wmi_user@WMI-Test", "PASSWORD"); +var cred = new NetworkCredential("USER@DOMAIN", "PASSWORD"); using (WmiConnection con = new WmiConnection(@"\\MACHINENAME\root\cimv2", cred, opt)) {