diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs
new file mode 100644
index 0000000..dfee9ee
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs
@@ -0,0 +1,11 @@
+namespace Lithnet.CredentialProvider
+{
+ internal class ConsentUICommandLineArgs
+ {
+ public uint AppInfoProcessId { get; set; }
+
+ public int Size { get; set; }
+
+ public long Address { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs
new file mode 100644
index 0000000..6c56ae8
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs
@@ -0,0 +1,337 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using Lithnet.CredentialProvider.Interop;
+using Microsoft.Win32.SafeHandles;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Threading;
+using NativeMethods = Windows.Win32.PInvoke;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// ConsentUIData is an abstract base class that represents all the different types of data structures that can be passed to the ConsentUI process for a UAC elevation prompt.
+ /// The static members of the class can be used to retrieve the data structure passed to the ConsentUI process, or to determine if the current process is the ConsentUI process.
+ /// The caller will be provided with one of the concrete implementations of this class, depending on the type of data structure that was passed to the ConsentUI process.
+ /// Use the property to determine the type of data structure and cast it to one of the concrete implementations.
+ ///
+ public abstract class ConsentUIData
+ {
+ private static bool? isConsentUI;
+ private static ConsentUICommandLineArgs commandLineArgs;
+ private protected ConsentUIStructureHeader header;
+ private readonly byte[] rawData;
+
+ ///
+ /// Gets a value indicating the type of ConsentUI data structure
+ ///
+ public ConsentUIType Type => this.header.Type;
+
+ ///
+ /// Gets a value indicating the consent prompt type
+ ///
+ public int PromptType => this.header.PromptType;
+
+ ///
+ /// Gets a handle to the Window that was responsible for invoking the ConsentUI prompt
+ ///
+ public IntPtr HWnd => this.header.hWnd;
+
+ public ElevationType ElevationType => this.header.elevationType;
+
+ ///
+ /// Gets the ID of the session where the ConsentUI prompt was originally invoked
+ ///
+ public int SessionId => this.header.sessionId;
+
+ private protected ConsentUIData(IntPtr pData, int expectedSize)
+ {
+ this.rawData = GetRawBytes(pData, expectedSize);
+ this.header = Marshal.PtrToStructure(pData);
+
+ if (this.header.Size != expectedSize)
+ {
+ throw new InvalidDataException($"The size of the data structure {this.header.Size} does not match the expected size {expectedSize}");
+ }
+ }
+
+ ///
+ /// Gets the Windows Identity from the original caller requesting elevation
+ ///
+ /// A WindowsIdentity object that represents the user requesting elevation
+ /// Thrown when the user's token could not be obtained from the session information
+ public WindowsIdentity GetWindowsIdentity()
+ {
+ var duplicatedToken = DuplicateHandleInternal(this.header.hToken);
+ return new WindowsIdentity(duplicatedToken.DangerousGetHandle());
+ }
+
+ ///
+ /// Gets a raw byte array representing the ConsentUI data structure
+ ///
+ /// A byte array
+ public byte[] GetRawData()
+ {
+ return this.rawData;
+ }
+
+ ///
+ /// Gets a string value that is packed at the end of the data structure if the offset is valid
+ ///
+ /// A pointer to the start of the data structure
+ /// The position from the start of the data structure where the string starts
+ /// A string containing all characters from the given offset up to the first null character found
+ private protected string GetStringValueIfValid(IntPtr pData, int offset)
+ {
+ if (offset > 0)
+ {
+ this.ThrowOnInvalidOffset(offset);
+ return Marshal.PtrToStringUni(IntPtr.Add(pData, offset));
+ }
+
+ return null;
+ }
+
+ ///
+ /// Throws an exception if the given offset is greater than the size of the data structure
+ ///
+ /// The value of the offset
+ /// Thrown when the value of the pointer is greater than the expected data size
+ private protected void ThrowOnInvalidOffset(int value)
+ {
+ if (value >= this.header.Size)
+ {
+ throw new InvalidDataException($"Offset value {value} is greater than the expected data size {this.header.Size}");
+ }
+ }
+
+ ///
+ /// Creates a ConsentUIData object from a previously-obtained raw byte representation
+ ///
+ /// The raw bytes of a supported ConsentUI data structure
+ /// A ConsentUIData object
+ public static ConsentUIData GetConsentUIData(byte[] consentUIDataStructure)
+ {
+ SafeHGlobalHandle pData = SafeHGlobalHandle.AllocHGlobal(consentUIDataStructure.Length);
+ Marshal.Copy(consentUIDataStructure, 0, pData.ToIntPtr(), consentUIDataStructure.Length);
+ return CreateInstance(pData.ToIntPtr(), consentUIDataStructure.Length);
+ }
+
+ ///
+ /// Gets the data structure passed to the Consent UI process
+ ///
+ /// A ConsentUIData object
+ public static ConsentUIData GetConsentUIData()
+ {
+ var pData = GetConsentUIData(out int structSize);
+ return ConsentUIData.CreateInstance(pData.ToIntPtr(), structSize);
+ }
+
+ ///
+ /// Gets a value indicating whether the current process is consent.exe, indicating that the provider is running inside an elevated UAC prompt
+ ///
+ ///
+ public static bool IsConsentUIParent()
+ {
+ if (isConsentUI == null)
+ {
+ var consentPath = Environment.ExpandEnvironmentVariables("%systemroot%\\system32\\consent.exe");
+ var process = Process.GetCurrentProcess();
+ var callingProcess = process.MainModule?.FileName;
+ isConsentUI = string.Equals(callingProcess, consentPath, StringComparison.OrdinalIgnoreCase);
+ }
+
+ return isConsentUI.Value;
+ }
+
+ ///
+ /// Gets the raw bytes of the ConsentUI data structure
+ ///
+ /// A byte array
+ public static byte[] GetConsentUIDataRawBytes()
+ {
+ var pData = GetConsentUIData(out int structSize);
+ return GetRawBytes(pData.ToIntPtr(), structSize);
+ }
+
+ ///
+ /// Parses the command line of the consent.exe process to retrieve the data structure passed to it
+ ///
+ /// Returns the size of the data structure as reported in the command line arguments
+ /// A pointer to the newly created copy of the data structure
+ /// Thrown when either consent.exe is not the parent process
+ /// Throw when the arguments passed to consent.exe are invalid
+ private static SafeHGlobalHandle GetConsentUIData(out int size)
+ {
+ if (!IsConsentUIParent())
+ {
+ throw new InvalidOperationException("The consent UI data can only be retrieved when consent.exe is the parent process");
+ }
+
+ commandLineArgs ??= GetConsentUICommandLineArgs();
+
+ size = commandLineArgs.Size;
+ return ReadMemoryFromProcess(commandLineArgs.AppInfoProcessId, commandLineArgs.Address, commandLineArgs.Size);
+ }
+
+
+ ///
+ /// Extracts the command line arguments passed to the consent.exe process
+ ///
+ /// A ConsentUICommandLineArgs object containing the arguments parsed from the command line
+ /// Thrown when the arguments passed to consent.exe cannot be parsed or are of the incorrect number
+ private static ConsentUICommandLineArgs GetConsentUICommandLineArgs()
+ {
+ var args = Environment.GetCommandLineArgs();
+
+ if (args.Length != 4)
+ {
+ throw new ArgumentException($"Unable to parse command line of consent.exe. The number of elements was incorrect\r\n{string.Join("\r\n", args)}");
+ }
+
+ if (!uint.TryParse(args[1], out var appInfoPid))
+ {
+ throw new ArgumentException($"Unable to parse command line of consent.exe. The expected first element was not an integer\r\n{string.Join("\r\n", args)}");
+ }
+
+ if (!int.TryParse(args[2], out var size))
+ {
+ throw new ArgumentException($"Unable to parse command line of consent.exe. The expected second element was not an integer\r\n{string.Join("\r\n", args)}");
+ }
+
+ if (!long.TryParse(args[3], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
+ {
+ throw new ArgumentException($"Unable to parse command line of consent.exe. The expected third element was not an integer\r\n{string.Join("\r\n", args)}");
+ }
+
+ return new ConsentUICommandLineArgs
+ {
+ Address = address,
+ AppInfoProcessId = appInfoPid,
+ Size = size,
+ };
+ }
+
+ ///
+ /// Copies the memory from a raw pointer into a managed byte array
+ ///
+ /// The pointer where the data copy must start
+ /// The number of bytes to copy
+ /// A copy of the raw memory returned as a managed byte array
+ private static byte[] GetRawBytes(IntPtr pData, int size)
+ {
+ byte[] dataForExport = new byte[size];
+ Marshal.Copy(pData, dataForExport, 0, size);
+ return dataForExport;
+ }
+
+ ///
+ /// Reads the memory from a specified process
+ ///
+ /// The ID of the process
+ /// The memory address to read
+ /// The size of the data at the specified memory address
+ /// A handle to a copy of the process memory
+ /// Thrown when the process could not be opened or the memory address could not be read
+ /// Thrown when the size of the copied structure did not equal the expected size as passed to the method
+ private static SafeHGlobalHandle ReadMemoryFromProcess(uint processId, long address, int size)
+ {
+ SafeHGlobalHandle pData = SafeHGlobalHandle.AllocHGlobal(size);
+ var pAddress = new IntPtr(address);
+
+ SafeFileHandle hProcess = OpenProcessHandle(processId, PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ);
+
+ unsafe
+ {
+ nuint numberOfBytesRead = 0;
+
+ if (!NativeMethods.ReadProcessMemory(hProcess, pAddress.ToPointer(), pData.ToIntPtr().ToPointer(), (nuint)size, &numberOfBytesRead))
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new Win32Exception(error, $"Unable to read memory from process {processId}");
+ }
+
+ if (numberOfBytesRead != (nuint)size)
+ {
+ throw new InvalidDataException($"Bytes read from memory {numberOfBytesRead} was not the expected structure size {size}");
+ }
+ }
+
+ return pData;
+ }
+
+ ///
+ /// Opens a native handle to a process
+ ///
+ /// The ID of the process
+ /// The requested access rights
+ /// A safe handle to the process
+ /// Thrown when the process handle could not be obtained
+ private static SafeFileHandle OpenProcessHandle(uint processId, PROCESS_ACCESS_RIGHTS rights)
+ {
+ var hProcess = NativeMethods.OpenProcess_SafeHandle(rights, false, processId);
+ if (hProcess.IsInvalid)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new Win32Exception(error, $"Unable to open process {processId}");
+ }
+
+ return hProcess;
+ }
+
+ ///
+ /// Creates an instance of the appropriate subclass of ConsentUIData by reading the type from the data structure
+ ///
+ /// A pointer to the data structure
+ /// The expected size of the data structure
+ /// A ConsentUIData object
+ /// Thrown when the size of the expected data structure does not match the size reported in the structure itself
+ private static ConsentUIData CreateInstance(IntPtr pData, int expectedSize)
+ {
+ var sizeReportedInStructure = Marshal.ReadInt32(pData, 0);
+
+ if (sizeReportedInStructure != expectedSize)
+ {
+ throw new InvalidDataException($"The expected size {expectedSize} did not match the size reported by the structure {sizeReportedInStructure}");
+ }
+
+ var type = (ConsentUIType)Marshal.ReadInt32(pData, 4);
+
+ return type switch
+ {
+ ConsentUIType.Exe => new ConsentUIDataExe(pData, sizeReportedInStructure),
+ ConsentUIType.Msi => new ConsentUIDataMsi(pData, sizeReportedInStructure),
+ ConsentUIType.Com => new ConsentUIDataCom(pData, sizeReportedInStructure),
+ ConsentUIType.Msix => new ConsentUIDataMsix(pData, sizeReportedInStructure),
+ ConsentUIType.ActiveX => new ConsentUIDataActiveX(pData, sizeReportedInStructure),
+ ConsentUIType.CredCollect => new ConsentUIDataCredCollect(pData, sizeReportedInStructure),
+ _ => throw new InvalidDataException("The ConsentUI data structure was for an unknown type"),
+ };
+ }
+
+ ///
+ /// Duplicates a handle passed in from the AppInfo service
+ ///
+ /// The handle to duplicate
+ /// A duplicated reference to the handle
+ /// Thrown when the handle could not be duplicated
+ protected private static SafeHandle DuplicateHandleInternal(IntPtr handle)
+ {
+ commandLineArgs ??= GetConsentUICommandLineArgs();
+
+ var processHandle = OpenProcessHandle(commandLineArgs.AppInfoProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE);
+ SafeFileHandle t = new(handle, false);
+
+ if (!NativeMethods.DuplicateHandle(processHandle, t, Process.GetCurrentProcess().SafeHandle, out var duplicatedToken, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to duplicate the handle");
+ }
+
+ return duplicatedToken;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs
new file mode 100644
index 0000000..c19dc3a
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// Represents the data structure passed to consent UI when a user is trying to install an ActiveX control
+ ///
+ /// This data structure is currently unknown and only the common header values are present
+ public class ConsentUIDataActiveX : ConsentUIData
+ {
+ internal ConsentUIDataActiveX(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.ActiveX)
+ {
+ throw new InvalidOperationException("The data structure is not of type ActiveX");
+ }
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs
new file mode 100644
index 0000000..c596f0e
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Runtime.InteropServices;
+using Lithnet.CredentialProvider.Interop;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// Represents the data structure passed to consent UI when a user is trying to elevate a COM component. This is typically seen when a user presses a 'shield' icon in something like the file security dialog to elevate permissions.
+ ///
+ public class ConsentUIDataCom : ConsentUIData
+ {
+ ///
+ /// The path to the COM component that is requesting elevation
+ ///
+ public string ComComponentPath { get; }
+
+ ///
+ /// The resource path to the image to display in consent UI
+ ///
+ public string ImageResourcePath { get; }
+
+ ///
+ /// The path to the process that is hosting the COM component
+ ///
+ public string ProcessPath { get; }
+
+ ///
+ /// A user friendly description of the type of operation that will be performed by the elevation
+ ///
+ public string OperationType { get; }
+
+ ///
+ /// The CLSID of the COM component
+ ///
+ public Guid ClsId { get; }
+
+ internal ConsentUIDataCom(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.Com)
+ {
+ throw new InvalidOperationException("The data structure is not of type COM");
+ }
+
+ var s = Marshal.PtrToStructure(pData);
+
+ this.ComComponentPath = this.GetStringValueIfValid(pData, (int)s.oComComponentPath);
+ this.ImageResourcePath = this.GetStringValueIfValid(pData, (int)s.oImageResourcePath);
+ this.ProcessPath = this.GetStringValueIfValid(pData, (int)s.oProcessPath);
+ this.OperationType = this.GetStringValueIfValid(pData, (int)s.oOperationType);
+ this.ClsId = s.Clsid;
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs
new file mode 100644
index 0000000..a8554b6
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// Represents the data structure passed to consent UI in a yet unknown scenario
+ ///
+ /// This data structure is currently unknown and only the common header values are present
+ public class ConsentUIDataCredCollect : ConsentUIData
+ {
+ internal ConsentUIDataCredCollect(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.CredCollect)
+ {
+ throw new InvalidOperationException("The data structure is not of type CredCollect");
+ }
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs
new file mode 100644
index 0000000..4d74baa
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Runtime.InteropServices;
+using Lithnet.CredentialProvider.Interop;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// Represents the data structure passed to consent UI when a user is trying to elevate an executable
+ ///
+ public class ConsentUIDataExe : ConsentUIData
+ {
+ private IntPtr hFile;
+
+ ///
+ /// The path to the process that the user has requested to be launched as an administrator
+ ///
+ public string ExecutablePath { get; }
+
+ ///
+ /// A currently unknown value. In most cases it seems to be the same as
+ ///
+ public string Unknown1 { get; }
+
+ ///
+ /// The full command line, including arguments that will be used to launch the executable
+ ///
+ public string CommandLine { get; }
+
+ ///
+ /// A currently unknown parameter
+ ///
+ public string Unknown2 { get; }
+
+ ///
+ /// Gets a handle to the executable that the user has requested to be launched as an administrator
+ ///
+ /// A handle to the executable
+ public SafeHandle GetExecutableHandle()
+ {
+ return DuplicateHandleInternal(this.hFile);
+ }
+
+ internal ConsentUIDataExe(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.Exe)
+ {
+ throw new InvalidOperationException("The data structure is not of type EXE");
+ }
+
+ var s = Marshal.PtrToStructure(pData);
+
+ this.hFile = s.hFile;
+ this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath1);
+ this.Unknown1 = this.GetStringValueIfValid(pData, (int)s.oExecutablePath2);
+ this.CommandLine = this.GetStringValueIfValid(pData, (int)s.oCommandLine);
+ this.Unknown2 = this.GetStringValueIfValid(pData, (int)s.oUnknown0);
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs
new file mode 100644
index 0000000..930412b
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Runtime.InteropServices;
+using Lithnet.CredentialProvider.Interop;
+
+namespace Lithnet.CredentialProvider
+{
+ ///
+ /// Represents the data structure passed to consent UI when a user is trying to elevate an MSI installer
+ ///
+ public class ConsentUIDataMsi : ConsentUIData
+ {
+ ///
+ /// The name of the product being installed
+ ///
+ public string ProductName { get; }
+
+ ///
+ /// The version of the product being installed
+ ///
+ public string Version { get; }
+
+ ///
+ /// The locale of the product being installed
+ ///
+ public string Locale { get; }
+
+ ///
+ /// The publisher of the product being installed
+ ///
+ public string Publisher { get; }
+
+ ///
+ /// The path to the MSI installer
+ ///
+ public string ExecutionPath { get; }
+
+ ///
+ /// The path to the original MSI file launched by the user
+ ///
+ public string OriginalMsi { get; }
+
+ ///
+ /// A currently unknown parameter
+ ///
+ public string Unknown1 { get; }
+
+ ///
+ /// A currently unknown parameter
+ ///
+ public string Unknown2 { get; }
+
+ internal ConsentUIDataMsi(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.Msi)
+ {
+ throw new InvalidOperationException("The data structure is not of type MSI");
+ }
+
+ var s = Marshal.PtrToStructure(pData);
+
+ this.ProductName = this.GetStringValueIfValid(pData, (int)s.oProductName);
+ this.Version = this.GetStringValueIfValid(pData, (int)s.oVersion);
+ this.Locale = this.GetStringValueIfValid(pData, (int)s.oLocale);
+ this.Publisher = this.GetStringValueIfValid(pData, (int)s.oPublisher);
+ this.ExecutionPath = this.GetStringValueIfValid(pData, (int)s.oExecutionPath);
+ this.OriginalMsi = this.GetStringValueIfValid(pData, (int)s.oOriginalMsi);
+ this.Unknown1 = this.GetStringValueIfValid(pData, (int)s.oUnknown1);
+ this.Unknown2 = this.GetStringValueIfValid(pData, (int)s.oUnknown2);
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs
new file mode 100644
index 0000000..cbced84
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ ///
+ /// Represents the data structure passed to consent UI when a user is trying to elevate an MSIX package
+ ///
+ public class ConsentUIDataMsix : ConsentUIData
+ {
+ ///
+ /// The path to the package being installed
+ ///
+ public string ExecutablePath { get; }
+
+ ///
+ /// The name of the package being installed
+ ///
+ public string PackageName { get; }
+
+ ///
+ /// The full command line, including any arguments used to launch the installer
+ ///
+ public string CommandLine { get; }
+
+ ///
+ /// A currently unknown parameter
+ ///
+ public string OtherName { get; }
+
+ internal ConsentUIDataMsix(IntPtr pData, int expectedSize) : base(pData, expectedSize)
+ {
+ if (this.header.Type != ConsentUIType.Msix)
+ {
+ throw new InvalidOperationException("The data structure is not of type MSIX");
+ }
+
+ var s = Marshal.PtrToStructure(pData);
+
+ this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath);
+ this.PackageName = this.GetStringValueIfValid(pData, (int)s.oPackageName);
+ this.CommandLine = this.GetStringValueIfValid(pData, (int)s.oCommandLine);
+ this.OtherName = this.GetStringValueIfValid(pData, (int)s.oOtherName);
+ }
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs b/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs
new file mode 100644
index 0000000..39770de
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs
@@ -0,0 +1,10 @@
+namespace Lithnet.CredentialProvider
+{
+ public enum ElevationType
+ {
+ Unknown1 = 0,
+ Unknown2 = 1,
+ Consent = 2,
+ Credentials = 3
+ }
+}
\ No newline at end of file
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat
new file mode 100644
index 0000000..289236b
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x86.dat
new file mode 100644
index 0000000..5097964
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x86.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat
new file mode 100644
index 0000000..17c483f
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat
new file mode 100644
index 0000000..0ef9b15
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat
new file mode 100644
index 0000000..4087acf
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat
new file mode 100644
index 0000000..9c63366
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x64.dat
new file mode 100644
index 0000000..2a4c6c0
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x64.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat
new file mode 100644
index 0000000..b1e69c2
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat differ
diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat
new file mode 100644
index 0000000..311afd6
Binary files /dev/null and b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat differ
diff --git a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs
index 86cda32..605c31b 100644
--- a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs
+++ b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs
@@ -51,6 +51,16 @@ namespace Lithnet.CredentialProvider
///
public IReadOnlyList Tiles { get; private set; }
+ ///
+ /// Gets a value that indicates if this credential provider is loaded by Logon UI
+ ///
+ public bool IsLogonUI => this.UsageScenario == UsageScenario.Logon;
+
+ ///
+ /// Gets a value that indicates if the credential provider is loaded by Consent UI (eg UAC prompt)
+ ///
+ public bool IsConsentUI => this.UsageScenario == UsageScenario.CredUI && ConsentUIData.IsConsentUIParent();
+
///
/// Provides access to the serialized input data provided by CredUI
///
diff --git a/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs
new file mode 100644
index 0000000..91446e7
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs
@@ -0,0 +1,12 @@
+namespace Lithnet.CredentialProvider
+{
+ public enum ConsentUIType
+ {
+ Exe = 0,
+ Com = 1,
+ Msi = 2,
+ ActiveX = 3,
+ CredCollect = 4,
+ Msix = 5
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs b/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs
new file mode 100644
index 0000000..7650635
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ internal sealed class SafeHGlobalHandle : IDisposable
+ {
+ ///
+ /// Unmanaged pointer wrapped by this object
+ ///
+ IntPtr pointer;
+
+ SafeHGlobalHandle()
+ {
+ this.pointer = IntPtr.Zero;
+ }
+
+ SafeHGlobalHandle(IntPtr handle)
+ {
+ this.pointer = handle;
+ }
+
+ ~SafeHGlobalHandle()
+ {
+ this.Dispose();
+ }
+
+ public static SafeHGlobalHandle InvalidHandle => new SafeHGlobalHandle(IntPtr.Zero);
+
+ ///
+ /// Operator to obtain the unmanaged pointer wrapped by the object. Note
+ /// that the returned pointer is only valid for the lifetime of this
+ /// object.
+ ///
+ /// Unmanaged pointer wrapped by the object
+ public IntPtr ToIntPtr()
+ {
+ return this.pointer;
+ }
+
+ public void Dispose()
+ {
+ if (this.pointer != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(this.pointer);
+ this.pointer = IntPtr.Zero;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ public static SafeHGlobalHandle AllocHGlobal(int cb)
+ {
+ if (cb < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(cb), "The value of this argument must be non-negative");
+ }
+
+ SafeHGlobalHandle result = new SafeHGlobalHandle();
+ result.pointer = Marshal.AllocHGlobal(cb);
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs
new file mode 100644
index 0000000..24a35a3
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct ConsentUIStructureCom
+ {
+ public ConsentUIStructureHeader Header;
+
+ // 64
+
+ public IntPtr oOperationType; // 8
+ public IntPtr oComComponentPath; // 8
+
+ // 64 + 16 == 80
+
+ public IntPtr oImageResourcePath; // 8
+ public IntPtr oProcessPath; // 8
+
+ // 80 + 16 == 96
+
+ public Guid Clsid; // 16
+
+ // 96 + 16 == 112
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs
new file mode 100644
index 0000000..ed69888
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct ConsentUIStructureExe
+ {
+ public ConsentUIStructureHeader Header;
+
+ // 64
+
+ public IntPtr hFile; // 8
+ public IntPtr oExecutablePath1; // 8
+
+ // 64 + 16 = 80
+
+ public IntPtr oExecutablePath2; // 8
+ public IntPtr oCommandLine; // 8
+
+ // 80 + 16 == 96
+
+ public IntPtr oUnknown0; // 8
+ public int ProcessId; // 4
+ // Padding (x64) // 4
+
+ // 96 + 12 (+ 4) = 112
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs
new file mode 100644
index 0000000..e84af60
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct ConsentUIStructureHeader
+ {
+ public int Size; // 4
+ public ConsentUIType Type; // 4
+ public int PromptType; // 4
+ // padding on x64 - 4
+
+ // 16
+
+ public IntPtr hWnd; // 8
+ public IntPtr hToken; // 8
+
+ // 32
+
+ public ElevationType elevationType; // 4
+ public int sessionId; // 4
+ public IntPtr hMutex; // 8
+
+ // 48+
+
+ public int unknownFlags1; // 4
+ public int unknownFlags2; // 4
+ public IntPtr pReturnAddress; // 8
+
+ // 64
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs
new file mode 100644
index 0000000..85b7ab3
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct ConsentUIStructureMsi
+ {
+ public ConsentUIStructureHeader Header;
+
+ // 64
+
+ public IntPtr hUnknown1; // 8
+ public IntPtr oProductName; // 8
+
+ // 64 + 16 == 80
+
+ public IntPtr oVersion; // 8
+ public IntPtr oLocale; // 8
+
+ // 80 + 16 == 96
+
+ public IntPtr oPublisher; // 8
+ public IntPtr oExecutionPath; // 8
+
+ // 96 + 16 == 112
+
+ public IntPtr oOriginalMsi; // 8
+ public IntPtr hUnknown2; // 8
+
+ // 96 + 16 == 128
+
+ public IntPtr oUnknown1; // 8
+ public IntPtr oUnknown2; // 8
+
+ // 128 + 16 == 144
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs
new file mode 100644
index 0000000..456624a
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.CredentialProvider.Interop
+{
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct ConsentUIStructureMsix
+ {
+ public ConsentUIStructureHeader Header;
+
+ // 64
+
+ public IntPtr oExecutablePath; // 8
+ public IntPtr oCommandLine; // 8
+
+ // 64 + 16 == 80
+
+ public IntPtr oPackageName; // 8
+ public IntPtr oOtherName; // 8
+
+ // 80 + 16 == 96
+
+ public int ProcessId; // 4
+ // Padding (x64) // 4
+
+ // 96 + 4 + 4 = 104
+ }
+}
diff --git a/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj b/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj
index b6161f4..34d41dc 100644
--- a/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj
+++ b/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj
@@ -17,8 +17,8 @@
Lithnet
Copyright 2023 Lithnet Pty Ltd
Lithnet Windows Credential Provider
- 1.0.0
- alpha1
+ 1.1.0
+ beta1
Lithnet
true
true
@@ -43,6 +43,9 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+
diff --git a/src/Lithnet.CredentialProvider/NativeMethods.json b/src/Lithnet.CredentialProvider/NativeMethods.json
new file mode 100644
index 0000000..6d04a57
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/NativeMethods.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "https://aka.ms/CsWin32.schema.json"
+}
\ No newline at end of file
diff --git a/src/Lithnet.CredentialProvider/NativeMethods.txt b/src/Lithnet.CredentialProvider/NativeMethods.txt
new file mode 100644
index 0000000..9b8730e
--- /dev/null
+++ b/src/Lithnet.CredentialProvider/NativeMethods.txt
@@ -0,0 +1,7 @@
+ReadProcessMemory
+OpenProcess
+WTSQueryUserToken
+GetTokenInformation
+CloseHandle
+DuplicateHandle
+GetFileSizeEx
\ No newline at end of file
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs
index b54b624..df61b4d 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs
@@ -1,14 +1,22 @@
-using Microsoft.Extensions.Logging;
+using System;
+using Microsoft.Extensions.Logging;
using NLog;
using NLog.Extensions.Logging;
namespace Lithnet.CredentialProvider.Samples
{
- internal static class InternalLogger
+ internal class InternalLoggerFactory : ICredentialProviderLoggerFactory
{
- internal static ILoggerFactory LoggerFactory { get; }
+ internal static ICredentialProviderLoggerFactory Instance { get; }
- static InternalLogger()
+ private ILoggerFactory loggerFactory;
+
+ public InternalLoggerFactory(ILoggerFactory factory)
+ {
+ this.loggerFactory = factory;
+ }
+
+ static InternalLoggerFactory()
{
/*
This sample uses NLog to capture trace events from the provider, but you can use any
@@ -30,7 +38,53 @@ namespace Lithnet.CredentialProvider.Samples
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logconsole);
LogManager.Configuration = config;
- InternalLogger.LoggerFactory = new NLogLoggerFactory(new NLogLoggerProvider(new NLogProviderOptions() { ReplaceLoggerFactory = true }, LogManager.LogFactory));
+ var loggerFactory = new NLogLoggerFactory(new NLogLoggerProvider(new NLogProviderOptions() { ReplaceLoggerFactory = true }, LogManager.LogFactory));
+ InternalLoggerFactory.Instance = new InternalLoggerFactory(loggerFactory);
+ }
+
+ public ICredentialProviderLogger CreateLogger(Type type)
+ {
+ return new CredentialProviderLogger(loggerFactory.CreateLogger(type));
+ }
+
+ public ICredentialProviderLogger CreateLogger()
+ {
+ return new CredentialProviderLogger(loggerFactory.CreateLogger());
+ }
+ }
+
+ public class CredentialProviderLogger : ICredentialProviderLogger
+ {
+ private readonly Microsoft.Extensions.Logging.ILogger logger;
+
+ public CredentialProviderLogger(Microsoft.Extensions.Logging.ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ public void LogError(Exception ex, string message)
+ {
+ this.logger.LogError(ex, message);
+ }
+
+ public void LogError(string message)
+ {
+ this.logger.LogError(message);
+ }
+
+ public void LogTrace(string message)
+ {
+ this.logger.LogTrace(message);
+ }
+
+ public void LogInformation(string message)
+ {
+ this.logger.LogInformation(message);
+ }
+
+ public void LogWarning(string message)
+ {
+ this.logger.LogWarning(message);
}
}
}
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs
index 8486d7f..8346cfb 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs
@@ -14,9 +14,9 @@ namespace Lithnet.CredentialProvider.Samples
[Guid("4eb911fa-ca18-40ea-86df-19aff5d1da58")]
public class TestCredentialProviderNet472x64 : CredentialProviderBase
{
- protected override ILoggerFactory GetLoggerFactory()
+ protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
- return InternalLogger.LoggerFactory;
+ return InternalLoggerFactory.Instance;
}
public override IEnumerable GetControls(UsageScenario cpus)
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs
index 0720de8..adb26e8 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs
@@ -15,7 +15,7 @@ namespace Lithnet.CredentialProvider.Samples
private SmallLabelControl CheckboxStateControl;
private SmallLabelControl ComboboxStateControl;
- private ILogger logger = InternalLogger.LoggerFactory.CreateLogger();
+ private ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger();
public TestCredentialProviderTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs
index c4ce35c..9bf6ad8 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs
@@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples
[Guid("c9055c88-03f9-4a12-8e33-1ee75826a4a6")]
public class TestCredentialProviderNet472x86 : CredentialProviderBase
{
- private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger();
+ private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger();
- protected override ILoggerFactory GetLoggerFactory()
+ protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
- return InternalLogger.LoggerFactory;
+ return InternalLoggerFactory.Instance;
}
public override IEnumerable GetControls(UsageScenario cpus)
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs
index 774b7b5..1ef2d92 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs
@@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples
[Guid("4cd12d80-9259-4f38-94dc-1828080ad9ff")]
public class TestCredentialProviderNet60x64 : CredentialProviderBase
{
- private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger();
+ private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger();
- protected override ILoggerFactory GetLoggerFactory()
+ protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
- return InternalLogger.LoggerFactory;
+ return InternalLoggerFactory.Instance;
}
public override IEnumerable GetControls(UsageScenario cpus)
diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs
index 035cec6..8b4d847 100644
--- a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs
+++ b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs
@@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples
[Guid("90592593-f4d3-4f62-aa83-9cf1f7b590e0")]
public class TestCredentialProviderNet60x86 : CredentialProviderBase
{
- private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger();
+ private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger();
- protected override ILoggerFactory GetLoggerFactory()
+ protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
- return InternalLogger.LoggerFactory;
+ return InternalLoggerFactory.Instance;
}
public override IEnumerable GetControls(UsageScenario cpus)