I'm using .NET 3.5.
I'm experiencing a memory leak when creating a reference to a X509Certificate. I'm using ANTS profiler to analyse the results and the private bytes are increasing while the bytes in the heap remain static (indicating it's a memory leak due to unmanaged code).
I'm using the CRYPT32 dll to manage the certificates. I open a reference to cert store to get a store handle which is an internal pointer (intptr). I then use this store handler to find the cert in store (saved locally). Once I have the cert I close the cert store and return the cert to the calling program. I'm using the flag 0 in the certclosestore which keeps open resources open after the close store is called. I believe this is what is causing the memory leak as it's mentioned here:
http://msdn.microsoft.com/en-us/library/ms937035.aspx
However, when I change the close flag to:
CertCloseStore(storeHandle, 2)
This is supposed to free up the allocated resources. However it just causes the service to bomb out.
The application works on the sense that it is verify the certs etc. The only problem is that the memory use is slowly creeping up and the service needs to be restarted every week or so. Any ideas or thoughts would be greatly appreciated.
public static X509Certificate CreateFromRegistry(string certificateIdent)
{
X509Certificate certificate = null;
IntPtr storeHandle = CertificateStore.CertOpenStore(CERT_STORE_PROV_SYSTEM,
0, 0,CERT_SYSTEM_STORE_LOCAL_MACHINE, "MY");;
certificate = new X509Certificate(CertificateStore.
FindCertInStore(certificateIdent, storeHandle));
CertificateStore.CertCloseStore(storeHandle, 0);
return certificate;
}
public class CertificateStore
{
const int CERT_STORE_PROV_SYSTEM = 10;
private static int CERT_SYSTEM_STORE_LOCAL_MACHINE = (2 << 16);
const uint PKCS_7_ASN_ENCODING = 0x00010000;
const uint X509_ASN_ENCODING = 0x00000001;
const uint CERT_FIND_SUBJECT_STR = 0x00080007;
const uint CERT_FIND_ISSUER_STR = 0x00080004;
static uint MY_ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
[DllImport("CRYPT32", EntryPoint = "CertOpenStore",
CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertOpenStore(
int storeProvider, int encodingType,
int hcryptProv, int flags, string pvPara);
[DllImport("CRYPT32", EntryPoint = "CertCloseStore",
CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CertCloseStore(
IntPtr storeProvider,
int flags);
}
public static X509Certificate FindCertInStore
(string trustedRootIssuerName, IntPtr storeHandle)
{
IntPtr hCertCntxt;
X509Certificate theActualCertificate = null;
if (storeHandle != IntPtr.Zero)
{
hCertCntxt = CertFindCertificateInStore(
storeHandle,
MY_ENCODING_TYPE,
0,
CERT_FIND_ISSUER_STR,
trustedRootIssuerName,
IntPtr.Zero);
if (hCertCntxt != IntPtr.Zero)
{
theActualCertificate = new X509Certificate(hCertCntxt);
}
}
return theActualCertificate;
}
Well, you are leaking CRYPT32 resources of course. One immediate candidate I see in your snippet is the return value of CertFindCertificateInStore(). It must be released by an explicit call to CertFreeCertificateContext(), I don't see one.
The X509Certification(IntPtr) constructor is not documented well, it doesn't describe how long the context needs to be valid. I see it calling an internal method named X509Utils._DuplicateCertContext() so very high odds that you immediately can call the release function after creating the object.
Do check the rest of your code with a fine-toothed comb and triple check that all handles and pointers you get from CRYPT32 are released.
Related
I would like to allow the user to connect to paired audio devices directly from the app instead of navigating to the bluetooth settings manually.
I am successfully listing all bluetooth devices using WinRT Apis:
var result = await DeviceInformation.FindAllAsync(BluetoothDevice.GetDeviceSelector());
// allow user to select a device
DeviceInfo d = await SelectDevice(result);
BluetoothDevice bluetoothDevice = await BluetoothDevice.FromIdAsync(d.Id);
As the WinRT-Apis do not expose any "Connect" interface (Remember, I want to connect the device as Windows would not communicate with it myself), I'm exploring using P/Invoke, so I use the following after reading this answer on superuser.com which suggests using BluetoothSetServiceState:
// Definitions
private const string bluetoothDll = "bthprops.cpl";
[DllImport(bluetoothDll, ExactSpelling = true, SetLastError = true)]
private static extern uint BluetoothSetServiceState(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi, ref Guid pGuidService, uint dwServiceFlags);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern IntPtr BluetoothFindFirstRadio(ref Bluetooth_Find_Radio_Params pbtfrp, out IntPtr phRadio);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern bool BluetoothFindRadioClose(IntPtr findHandle);
[DllImport(bluetoothDll, SetLastError = true)]
private static extern uint BluetoothGetDeviceInfo(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi);
private const uint BLUETOOTH_SERVICE_DISABLE = 0;
private const uint BLUETOOTH_SERVICE_ENABLE = 0x00000001;
// Code (using the bluetoothDevice obtained from the WinRT Api)
using(var pointer = GetRadioPointer())
{
BLUETOOTH_DEVICE_INFO deviceInfo = new BLUETOOTH_DEVICE_INFO
{
Address = bluetoothDevice.BluetoothAddress,
dwSize = (uint)Marshal.SizeOf<BLUETOOTH_DEVICE_INFO>()
};
uint result = BluetoothGetDeviceInfo(pointer.Handle, ref deviceInfo);
Guid serviceRef = InTheHand.Net.Bluetooth.BluetoothService.Handsfree;
result = BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
}
// I get the radio like this:
private RadioHandle GetRadioPointer()
{
Bluetooth_Find_Radio_Params pbtfrp = new Bluetooth_Find_Radio_Params();
pbtfrp.Initialize();
IntPtr findHandle = IntPtr.Zero;
try
{
findHandle = BluetoothFindFirstRadio(ref pbtfrp, out IntPtr phRadio);
return new RadioHandle(phRadio);
}
finally
{
if (findHandle != IntPtr.Zero)
{
BluetoothFindRadioClose(findHandle);
}
}
}
However, I cannot get it to work. BluetoothSetServiceState always returns 87, which is ERROR_INVALID_PARAMETER and nothing happens. Any idea on how to solve this? Using the command line tools referenced in the superuser-post, it works...
Thanks for your help.
I found it out by chance myself and it works now. Depending whether the service is already in the state (even if the device is disconnected), you will need to turn it off before. So turning it off and on again works:
BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 0);
BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
So as it turns out, you can connect a device if you enumerate the services, turn all off and on again. Disconnecting works by turning all off. When the last one is off, Windows disconnects the device.
Why would you ever expect that the following line would work:
private const string bluetoothDll = "bthprops.cpl";
when MSDN's page states:
DLL: Bthprops.dll
?
We are writing a code to do on-demand scan of a file from C# using Windows Defender APIs.
[DllImport(#"C:\Program Files\Windows Defender\MpClient.dll")]
public static extern int WDStatus(out bool pfEnabled);
[DllImport(#"C:\Program Files\Windows Defender\MpClient.dll")]
public static extern int MpManagerOpen(uint dwReserved, out IntPtr phMpHandle);
[DllImport(#"C:\Program Files\Windows Defender\MpClient.dll")]
public static extern int MpScanStart(IntPtr hMpHandle, uint ScanType, uint dwScanOptions, IntPtr pScanResources, IntPtr pCallbackInfo, out IntPtr phScanHandle);
[DllImport(#"C:\Program Files\Windows Defender\MpClient.dll")]
public static extern int MpHandleClose(IntPtr hMpHandle);
private void DoDefenderScan_Click(object sender, EventArgs e)
{
try
{
bool pfEnabled;
int result = WDStatus(out pfEnabled); //Returns the defender status - It's working properly.
ErrorHandler.ThrowOnFailure(result, VSConstants.S_OK);
IntPtr phMpHandle;
uint dwReserved = 0;
IntPtr phScanHandle;
MpManagerOpen(dwReserved, out phMpHandle); //Opens Defender and returns the handle in phMpHandle.
tagMPRESOURCE_INFO mpResourceInfo = new tagMPRESOURCE_INFO();
mpResourceInfo.Path = "eicar.com";
mpResourceInfo.Scheme = "file";
mpResourceInfo.Class = IntPtr.Zero;
tagMPRESOURCE_INFO[] pResourceList = new tagMPRESOURCE_INFO[1];
pResourceList.SetValue(mpResourceInfo, 0);
tagMPSCAN_RESOURCES scanResource = new tagMPSCAN_RESOURCES();
scanResource.dwResourceCount = 1;
scanResource.pResourceList = pResourceList;
IntPtr resourcePointer = StructToPtr(scanResource);
result = MpScanStart(phMpHandle, 3, 0, resourcePointer, IntPtr.Zero, out phScanHandle); **//Getting Access violation exception here**.
MpHandleClose(phMpHandle);
MpHandleClose(phScanHandle);
Marshal.FreeHGlobal(resourcePointer);
}
catch (Exception)
{ }
}
And the structure is defined here.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct tagMPSCAN_RESOURCES
{
public uint dwResourceCount;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
public tagMPRESOURCE_INFO[] pResourceList;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct tagMPRESOURCE_INFO
{
[MarshalAs(UnmanagedType.LPWStr)]
public String Scheme;
[MarshalAs(UnmanagedType.LPWStr)]
public String Path;
public IntPtr Class;
}
public class MPRESOURCE_CLASS
{
public uint Value;
}
private static IntPtr StructToPtr(object obj)
{
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
Marshal.StructureToPtr(obj, ptr, false);
return ptr;
}
The code is written based on the documentation available at
https://msdn.microsoft.com/en-us/library/vs/alm/dn920144(v=vs.85).aspx
We are getting this exception
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at
result = MpScanStart(phMpHandle, 3, 0, resourcePointer, IntPtr.Zero, out phScanHandle); **//Getting Access violation exception here**.
What could be the problem? Is the format of struct is correct?
P.S - No information about MPRESOURCE_CLASS is available in msdn.
I'm not sure, whether this line of code is correct.
mpResourceInfo.Class = IntPtr.Zero;
Update:
Quick scan is working fine with this code:
result = MpScanStart(phMpHandle, 1, 0, IntPtr.Zero, IntPtr.Zero, out phScanHandle);
Defender logs in the event viewer [ Applications and Services Logs-Microsoft-Windows-Windows Defender/Operational ] as
Windows Defender scan has started.
Scan ID:{CDC2AC0D-7648-4313-851C-4D8B7B5EB5CD}
Scan Type:AntiSpyware
Scan Parameters:Quick Scan
I couldn't identify the problem here. So I ended up with Antimalware Scan Interface (AMSI) available starting from Windows 10.
I have written a sample C# code here.
One thing I found is AMSI requires Windows defender/any antivirus to be turned on to verify the file passed to API. But triggering a scan through MpClient.dllwill trigger a defender scan even if defender is turned off.
Also ensure your project targets x64 platform.
public enum AMSI_RESULT
{
AMSI_RESULT_CLEAN = 0,
AMSI_RESULT_NOT_DETECTED = 1,
AMSI_RESULT_DETECTED = 32768
}
[DllImport("Amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)]string appName, out IntPtr amsiContext);
[DllImport("Amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiUninitialize(IntPtr amsiContext);
[DllImport("Amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiOpenSession(IntPtr amsiContext, out IntPtr session);
[DllImport("Amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiCloseSession(IntPtr amsiContext, IntPtr session);
[DllImport("Amsi.dll", EntryPoint = "AmsiScanString", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanString(IntPtr amsiContext, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)]string #string, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)]string contentName, IntPtr session, out AMSI_RESULT result);
[DllImport("Amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanBuffer(IntPtr amsiContext, [In] [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, uint length, [In()] [MarshalAs(UnmanagedType.LPWStr)] string contentName, IntPtr session, out AMSI_RESULT result);
//This method apparently exists on MSDN but not in AMSI.dll (version 4.9.10586.0)
[DllImport("Amsi.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern bool AmsiResultIsMalware(AMSI_RESULT result);
private void CallAntimalwareScanInterface()
{
IntPtr amsiContext;
IntPtr session;
AMSI_RESULT result = 0;
int returnValue;
returnValue = AmsiInitialize("VirusScanAPI", out amsiContext); //appName is the name of the application consuming the Amsi.dll. Here my project name is VirusScanAPI.
returnValue = AmsiOpenSession(amsiContext, out session);
returnValue = AmsiScanString(amsiContext, #"X5O!P%#AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*", "EICAR", session, out result); //I've used EICAR test string.
AmsiCloseSession(amsiContext, session);
AmsiUninitialize(amsiContext);
}
I've been searching about problem and I've read this as one of the possible causes:
"You often see differences between debug and release builds because
debug builds contain extra metadata to assist in debugging."
here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/4f48c152-68cd-45ec-a11e-baa7de7f79c3/attempted-to-read-or-write-protected-memory?forum=csharpgeneral
Also you should check this answer to "Is it possible to catch an access violation exception in .NET?" and the further details that are explained in the article Handling Corrupted State Exceptions in MSDN magazine
...
So, according to that answers and articles I'd try:
1st Double check signatures and COM interop thunks for all unmanaged code to verify that they're correct.
2nd Set Visual Studio Debugger to bypass this exception:
Tools menu ->Options -> Debugging -> General -> Uncheck this option "Suppress JIT optimization on module load"
3rd Try-Catch the exception
(note: if you are using .Net 4 then in App.config, within the tag modify runtime to include legacyCorruptedStateExceptionsPolicy enabled="true"like:
<runtime>
<legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
)
In addition, here, I've found that some .net framework versions (latest comment point to 4.6.1 in one of the answer's comments) has a bug related with this exception and the solution, in the past, has been upgrading the framework.
Also, in the one of that answers I've read:
Hi There are two possible reasons.
1.We have un-managed code and we are calling it from managed code. that is preventing to run this code. try running these commands and
restart your pc
cmd: netsh winsock reset
open cmd.exe and run command "netsh winsock reset catalog"
2.Anti-virus is considering un-managed code as harmful and restricting to run this code disable anti-virus and then check
I'd like to know if some of these approaches helps you to solve your issue.
I really hope this helps.
KR,
Juan
You may use Antimalware Scan Interface to check file for malware.
The Antimalware Scan Interface (AMSI) is a generic interface standard that allows applications and services to integrate with any antimalware product present on a machine. It provides enhanced malware protection for users and their data, applications, and workloads.
It's available starting from Windows 10.
Windows Defender comes with CLI tool 'MpCmdRun' - it's not a full-sized antivirus app, but an API interface to the actual Windows Defender that's always (?) running in background.
Saving to a temporary file via Path.GetTempFileName() and then running a scan like this
MpCmdRun.exe -Scan -ScanType 3 -File "c:\path\to\temp\file" -DisableRemediation
works fine even in an ASP.NET (Core) app, that runs under app-pool identity
I've actually written a small (40 lines of code) C# helper that does everything for you (saves temp file, runs a scan, cleans up)
https://github.com/jitbit/WinDefender/blob/main/WinDefender.cs
this error is appearing while I am attempting to do two things.
while attempting this (Code block 1):
_class = new Proc(Process.GetProcessesByName("procname")[0]);
then in the class Proc whats happening is
public Proc(Process _SelectedProcess)
{
Process = _SelectedProcess;
}
public Process Process
{
get
{
return SelectedProcess;
}
set
{
SelectedProcess = value;
if (SelectedProcess != null)
{
Process.EnterDebugMode();
_Reader = new Win32_Memory(value.Handle, value.MainModule.BaseAddress.ToInt32(), value.Id);
}
}
}
That's some of the ways I get the exception, sometimes this passes without any exception for no apparent reason as far as I see.
Note: it never passes in windows 7, I'm using windows 10 and sometimes it happens that the function works
but if it does pass, the next time I need to use OpenProcess() outside of the Process class, I almost always get the exception, and if i do, then afterwards it fails executing code block 1 if I try to do so again.
this (code block 2) also gets the same access denied error, and sometimes doesnt...
if (_Reader.ReadInt(_addr) == 1) _Reader.Write(_addr, 0);
public bool Write(int address, long value)
{
hProc = OpenProcess(ProcessAccessFlags.VMWrite, false, ID);
byte[] val = BitConverter.GetBytes(value);
bool worked = WriteProcessMemory(hProc, new IntPtr(address), val, (uint)val.LongLength, 0);
CloseHandle(hProc);
return worked;
}
the access flags:
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
the imports:
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, int unused);
also worth noting that sometimes all of this code gets executed without ANY error and will work for as long as I do not reopen this application or if i do not restart the targeted application.
please help me out on this one, if I wasn't clear on some things - this is my first question and I haven't really ever needed to ask one before this one... so I will explain anything necessary afterwards
If, as your last comment indicates, the processes truly have nothing to do with each other, then that exactly explains the AccessDeniedException. You aren't allowed to just modify memory of any random process. That would be a security hole.
Both processes have to be setup and agree to share memory with each other. There are many ways to do inter-process communication between cooperating processes: here's a start: Shared memory between 2 processes (applications)
If possible, I want to be able to check whether or not the recycle bin is empty, with minimal hassle (importing dlls, importing anything, creating entire new class to hold recycle bin functionality etc...)
I already have the code below that I found online to empty the recycle bin so it seems natural to suspect that I should be able to extend this to check if it needs emptying first, perhaps.. another function within Shell32.dll.
enum BinFlags : uint
{
SHERB_NOCONFIRMATION = 0x00000001,
SHERB_NOPROGRESSUI = 0x00000002,
SHERB_NOSOUND = 0x00000004
}
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
static extern uint SHEmptyRecycleBin(IntPtr hwnd, string rootPath,
BinFlags flags);
/* snip, bunch of code... */
SHEmptyRecycleBin(IntPtr.Zero, null, 0);
You can add reference to the C:\Windows\System32\Shell32.dll and use the following snippet:
Shell shell = new Shell();
Folder recycleBin = shell.NameSpace(10);
int itemsCount = recycleBin.Items().Count;
Taken from here.
It's poor documentation, but you might want SHQueryRecycleBin EDIT: Slightly better documentation over at MSDN.
[DllImport("shell32.dll")]
static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO
pSHQueryRBInfo);
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct SHQUERYRBINFO
{
public int cbSize;
public long i64Size;
public long i64NumItems;
}
It looks like you make the call and it fills the object and if you look at i64NumItems and it is 0 then the recycle bin is empty.
public static int GetCount()
{
SHQUERYRBINFO sqrbi = new SHQUERYRBINFO();
sqrbi.cbSize = Marshal.SizeOf(typeof(SHQUERYRBINFO));
int hresult = SHQueryRecycleBin(string.Empty, ref sqrbi);
return (int)sqrbi.i64NumItems;
}
At work we make our own tablets. Some of the tablets have fingerprint biometrics, some don't. Sometimes a tech forgets to plug it in. I have yet to find a way to check if that device (or any for that matter) is present.
My first approach was to use the GUID for a biometric which is {53D29EF7-377C-4D14-864B-EB3A85769359}. I would search in the registry at hklm\system\currontcontrolset\control\class and check to see if that key is present.
That doesn't work because it seems that Windows 7 has that key present even if you've never had a biometric installed. It worked in XP, but I just tried again on a unit that used to have a biometric but I took it out and that key is still present.
The hardest part about this problem is that I have to work with Windows 7, 7 embedded, xp, and xp embedded.
Next idea was to use WMI, but I couldn't find the correct class to call to check if it is present.
I then found a biometric.dll but that only works in Windows 7.
Sometimes finding a common solution to a problem is not always easy. I'm doing this project in C# but iI'm willing to convert it to any language.
Any ideas on were I should start looking?
With the help of Joshua Drake who gave me an awesome link on how to solve my problem, those are my results:
The code that I am fixing to post is kind of specialized in that it looks for a specific GUID and only looks for the first one. I adapted it from the article about how to disable a device, although this code does not disable anything it merely checks for presence.
public static bool IsDevicePresent(string guid)
{
var info = IntPtr.Zero;
var NullGuid = new Guid(guid);
try
{
info = SetupDiGetClassDevsW(ref NullGuid,null,IntPtr.Zero,DIGCF_PRESENT);
CheckError("SetupDiGetClassDevs");
var devdata = new SP_DEVINFO_DATA();
devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
// Get first device matching device criterion.
SetupDiEnumDeviceInfo(info,0,out devdata);
// if no items match filter, throw
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
CheckError("No device found matching filter.", 0xcffff);
CheckError("SetupDiEnumDeviceInfo");
}
catch
{
return false;
}
finally
{
if (info != IntPtr.Zero)
SetupDiDestroyDeviceInfoList(info);
}
return true;
}
private static void CheckError(string message, int lasterror = -1)
{
int code = lasterror == -1 ? Marshal.GetLastWin32Error() : lasterror;
if (code != 0)
throw new ApplicationException(String.Format("Error disabling hardware device (Code {0}): {1}",code, message));
}
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid,[MarshalAs(UnmanagedType.LPWStr)]string Enumerator,IntPtr parent,UInt32 flags);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,UInt32 memberIndex,[Out] out SP_DEVINFO_DATA deviceInfoData);
//used to find device info from device manager
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid classGuid;
public UInt32 devInst;
public IntPtr reserved;
}
private const uint DIGCF_PRESENT = 2;
private const uint ERROR_INVALID_DATA = 13;
private const uint ERROR_NO_MORE_ITEMS = 259;
private const uint ERROR_ELEMENT_NOT_FOUND = 1168;
And here is a simple unit test to prove it works for first device
[Test]
public void TestDevicePresent()
{
var bluetoothClassGuid = "e0cbf06c-cd8b-4647-bb8a-263b43f0f974";
var biometricClassGuid = "53D29EF7-377C-4D14-864B-EB3A85769359";
var cdromdrivClassGiud = "4d36e965-e325-11ce-bfc1-08002be10318";
Assert.False(Native.IsDevicePresent(bluetoothClassGuid));
Assert.False(Native.IsDevicePresent(biometricClassGuid));
Assert.True(Native.IsDevicePresent(cdromdrivClassGiud));
}