I want a sure-shot method to test if the application was run via the UAC box and has full administrative rights. Earlier, I thought of making a folder in C:\Windows\ for testing but running it on other computers proved to be a failure!
The UAC box provides all administrative rights to the computer to do anything(including making folders and creating files in places which needs there rights) and also makes sure that any child program so called or created also does have the same rights as the parent.
Is there a sure-shot way to test if my application has been provided all the administrative rights that I can maximum get by the user while running the application or not? If yes, I would be glad to have to piece of code-work!
C#:
using System.Security.Principal;
...
var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
bool isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator);
VB.Net:
Imports System.Security.Principal
...
Dim identity = WindowsIdentity.GetCurrent()
Dim principal = new WindowsPrincipal(identity)
Dim isElevated as Boolean = principal.IsInRole(WindowsBuiltInRole.Administrator)
After a fair bit of poking around, I found that the most common solutions to this question return false negatives if the user's UAC is set to anything but Off.
My solution these days is to do this:
Imports System.Security.Principal
Imports System.DirectoryServices.AccountManagement
Imports System.DirectoryServices.ActiveDirectory
Imports Microsoft.VisualBasic.ApplicationServices
''' <summary>Checks whether the current user is belongs to any Administrators groups.</summary>
''' <param name="AuthGroups">Optional. A flag indicating whether to use GetAuthorizationGroups instead of the - faster - GetGroups. Default=true.</param>
''' <returns>True if the user belongs to an Administrators group, false otherwise.</returns>
Public Function IsAdministrator(
Optional ByVal AuthGroups As Boolean = True) As Boolean
Static bResult As Boolean? = Nothing
Try
If bResult Is Nothing Then
bResult = New WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)
If Not bResult Then
Dim oContext As PrincipalContext = Nothing
Try 'Domain check first
Domain.GetComputerDomain()
oContext = New PrincipalContext(ContextType.Domain)
Catch
'Fall through to machine check
End Try
If oContext Is Nothing Then oContext = New PrincipalContext(ContextType.Machine)
'Dim oPrincipal As UserPrincipal = UserPrincipal.FindByIdentity(oContext, WindowsIdentity.GetCurrent().Name) ' Don't use - slow
Using oSearchUser As Principal = New UserPrincipal(oContext)
oSearchUser.SamAccountName = WindowsIdentity.GetCurrent().Name
Using oSearcher As PrincipalSearcher = New PrincipalSearcher(oSearchUser)
Using oUser As Principal = oSearcher.FindOne()
If oUser IsNot Nothing Then
If AuthGroups Then
bResult = CType(oUser, UserPrincipal).GetAuthorizationGroups().Any(Function(p) _
p.Sid.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountDomainAdminsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountEnterpriseAdminsSid))
Else
bResult = oUser.GetGroups().Any(Function(p) _
p.Sid.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountDomainAdminsSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountAdministratorSid) OrElse
p.Sid.IsWellKnown(WellKnownSidType.AccountEnterpriseAdminsSid))
End If
End If
End Using
End Using
End Using
End If
End If
Catch
bResult = False
End Try
Return bResult.GetValueOrDefault(False)
End Function
This method is a composite of a few other answers, so I only take credit for packaging it up into a function that will only ever run once and therefore if there is a bit of a delay due to the fall-through, you can probably hide it in start-up.
The AuthGroups argument gives you a choice of the more thorough, recursive AuthorizationGroups check (default) or the faster Groups check.
Related
Thanks for reading.
I have built a VB6 DLL (VB_InterFace just for a name) that talks to a C# DLL (C#_Driver just for a name) that talks to a Bluetooth Device.
The Demo VB6 test app (VB_Demo just for a name) I created as stage one works fine, does what it is supposed to. It calls the VB_Interface and Opens and Closes the BTDevice. Additional functions also work fine.
However on placing the operational code from VB_Interface into another DLL that is the live operations DLL, Open works fine, but Close is throwing an error. "Variable not defined" when returning from the C#_Driver.
I just can't see why, the code is the same, the process is only marginally different. By this I mean ;
In the VB_Demo I have two buttons "Open" "Close" and when I click on these I get the feedback that I expect from the BTDevice.
Private Sub btnOpenPort_Click()
'MsgBox steps(0)
ReDim steps(5)
Dim rc As HF4000_ResultCodes
'rc = driver.OpenSerial(cmbPorts.Text)
If driver.OpenSerial(cmbPorts.Text) = True Then
Private Sub btnClosePort_Click()
Dim rc As HF4000_ResultCodes
If driver.CloseSerial("COM4") = True Then
However in the live DLL it just executes the same functions internally without being initiated by a button click.
' See IScanDevice documentation.
' #see IScanDevice#OpenDevice
Private Function IScanDevice_OpenDevice() As Scanning.Scan_ResultCodes
(truncated slightly)
50 If driver.OpenSerial("COM4") = True Then
rc = READY
MsgBox "Connected to the device successfully."
' See IScanDevice documentation.
' #see IScanDevice#CloseDevice
Private Function IScanDevice_CloseDevice() As Scanning.Scan_ResultCodes
(truncated slightly)
50 If driver.CloseSerial("COM4") = True Then
60 rc = READY
70 IScanDevice_CloseDevice = Scan_Success
clsDriver.cls
Public Event OnStateChanged(newState As String)
Public Event OnDataUpdated()
Dim WithEvents CSharpInteropServiceEvents As CSharpInteropService.LibraryInvoke
Dim load As New LibraryInvoke
Private Sub Class_Initialize()
Set CSharpInteropServiceEvents = load
End Sub
Private Sub CSharpInteropServiceEvents_MessageEvent(ByVal newState As String)
If newState = "OpenForm1" Then
' FormDummy2.Show ' Not required
End If
If State <> newState Then
State = newState
RaiseEvent OnStateChanged(State)
GetDriverData
End If
End Sub
Private Function BluetoothTestInvoke(load, functionName, param)
BluetoothTestInvoke = load.GenericInvoke("BluetoothTest.dll", "BluetoothTest.Class1", functionName, param)
End Function
Function OpenSerial(portNumber) ' "COM4"
Dim param(0) As Variant
Dim retorno As Variant
param(0) = portNumber
retorno = BluetoothTestInvoke(load, "OpenSerial", param)
OpenSerial = retorno(0) <<<<<<< Works fine returns TRUE
End Function
Function CloseSerial(portNumber) ' "COM4"
Dim param(0) As Variant
Dim retorno As Variant
param(0) = portNumber
retorno = BluetoothTestInvoke(load, "CloseSerial", param)
CloseSerial = retorno(0) <<<<<<<<< "Error Subscript Out of Range"
End Function
What I have discovered is this - and I guess this is the reason why the Close is not working. The question is why is this situation occurring ...
When driver.OpenSerial executes, it hits > Function OpenSerial
Within Function OpenSerial it executes BluetoothTestInvoke where "load" is "CSharpInteropService.LibraryInvoke"
From there it moves to - Sub CSharpInteropServiceEvents_MessageEvent
.. and everything is fine.
However when I then execute driver.CloseSerial after that, it hits > Function CloseSerial
Within Function OpenSerial it executes BluetoothTestInvoke where "load" is "CSharpInteropService.LibraryInvoke"
Now here it "should" move to - Sub CSharpInteropServiceEvents_MessageEvent
However No, it just drops to the next line which is CloseSerial = retorno(0)
and this is where I get the "Subscript out of range" error for retorno(0)
For some reason in the CloseSerial it is not invoking "load"
BluetoothTestInvoke(load, "CloseSerial", param)
Thoughts and suggestions much appreciated.
UPDATE
Quite right, one should never assume anything.
On the tips I started digging deeper into the C# Library. It turns out the "param" value that is the Bluetooth port is passed into the CloseSerial call, and from there is is passed around within the external C# library dll. At one stage it is reset so the port number that should be handled is lost, thus it doesn't close but specifically the "expected" data was not returned to the calling app.
param(0) = portNumber
retorno = BluetoothTestInvoke(load, "CloseSerial", param) << param was being reset in the external library.
I have problems getting launching some shortcuts and getting their icon for some strange and unknown reason, using the following methods :
Public Shared Sub Launch(itemToLaunch As String)
Process.Start(itemToLaunch)
End Sub
Public Function GetShellIcon(ByVal path As String) As Icon
Dim info As SHFILEINFO = New SHFILEINFO()
Dim retval As IntPtr = SHGetFileInfo(path, 0, info, Marshal.SizeOf(info), SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_LARGEICON)
If retval = IntPtr.Zero Then
Return New Icon(GetType(Control), "Error.ico")
End If
Dim cargt() As Type = {GetType(IntPtr)}
Dim ci As ConstructorInfo = GetType(Icon).GetConstructor(BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, cargt, Nothing)
Dim cargs() As Object = {info.IconHandle}
Dim icon As Icon = CType(ci.Invoke(cargs), Icon)
Return icon
End Function
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Private Structure SHFILEINFO
Public IconHandle As IntPtr
Public IconIndex As Integer
Public Attributes As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
Public DisplayString As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)>
Public TypeName As String
End Structure
Private Declare Auto Function SHGetFileInfo Lib "Shell32.dll" (path As String, attributes As Integer, ByRef info As SHFILEINFO, infoSize As Integer, flags As Integer) As IntPtr
Public Const SHGFI_ICON = &H100
Public Const SHGFI_SMALLICON = &H1
Public Const SHGFI_LARGEICON = &H0 ' Large icon
These methods works well on almost any item, but sometimes they send me a System.ComponentModel.Win32Exception in System.dll when trying to execute a shortcut file, and on these same files, getting their icon.
It gives me the following message (given by Process.Start is called with a ProcessStartInfo parameter with ErrorDialog=True) :
This error is different than the one that is raised if the path to the .lnk file is not correct pointing to an non existing file :
As an example, you can reproduce this problem this way :
Locate on a Windows 7 install the following files :
C:\Program Files\DVD Maker\DVDMaker.exe (native with Windows 7)
C:\Program Files\WinRAR\WinRAR.exe (v5.0 64 bits, but I guess this will have the same effect with another version)
C:\Program Files\Windows NT\Accessories\wordpad.exe (native with Windows 7)
Copy each of them to the Desktop
With a right-click-drag, create 3 links shortcuts for each of these 3 files from their original location to the desktop. Renames these Shortcuts (for convenience) "[Filename] linkorig"
With a right-click-drag, create 3 links shortcuts for each of the 3 copied files from the Desktop to the desktop. Renames these Shortcuts (for convenience) "[Filename] linkcopy"
Create a Visual basic project, put 4 PictureBoxes onto a Form and name them :
ExeOrigPictureBox
ExeCopyPictureBox
LnkOrigPictureBox
LnkCopyPictureBox
And some Labels to help yourself.
Then copy/paste the following code into the Form code window :
Imports System.Reflection
Imports System.Runtime.InteropServices
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.ExeOrigPictureBox.Tag = "C:\Program Files\WinRAR\WinRAR.exe"
Me.ExeCopyPictureBox.Tag = "C:\Users\Moi\Desktop\WinRAR.exe"
Me.LnkOrigPictureBox.Tag = "C:\Users\Moi\Desktop\WinRAR.exe linkorig.lnk"
Me.LnkCopyPictureBox.Tag = "C:\Users\Moi\Desktop\WinRAR.exe linkcopy.lnk"
Me.ExeOrigPictureBox.Image = GetShellIcon(Me.ExeOrigPictureBox.Tag).ToBitmap
Me.ExeCopyPictureBox.Image = GetShellIcon(Me.ExeCopyPictureBox.Tag).ToBitmap
Me.LnkOrigPictureBox.Image = GetShellIcon(Me.LnkOrigPictureBox.Tag).ToBitmap
Me.LnkCopyPictureBox.Image = GetShellIcon(Me.LnkCopyPictureBox.Tag).ToBitmap
End Sub
Private Sub ExeOrigPictureBox_Click(sender As Object, e As EventArgs) Handles ExeOrigPictureBox.Click, ExeCopyPictureBox.Click, LnkOrigPictureBox.Click, LnkCopyPictureBox.Click
Dim pBox As PictureBox = DirectCast(sender, PictureBox)
Dim pi As ProcessStartInfo = New ProcessStartInfo
pi.FileName = pBox.Tag
pi.ErrorDialog = True
Process.Start(pi)
End Sub
End Class
Module Shell32
Public Function GetShellIcon(ByVal path As String) As Icon
Dim info As SHFILEINFO = New SHFILEINFO()
Dim retval As IntPtr = SHGetFileInfo(path, 0, info, Marshal.SizeOf(info), SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_LARGEICON)
If retval = IntPtr.Zero Then
Return New Icon(GetType(Control), "Error.ico")
End If
Dim cargt() As Type = {GetType(IntPtr)}
Dim ci As ConstructorInfo = GetType(Icon).GetConstructor(BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, cargt, Nothing)
Dim cargs() As Object = {info.IconHandle}
Dim icon As Icon = CType(ci.Invoke(cargs), Icon)
Return icon
End Function
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Private Structure SHFILEINFO
Public IconHandle As IntPtr
Public IconIndex As Integer
Public Attributes As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
Public DisplayString As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)>
Public TypeName As String
End Structure
Private Declare Auto Function SHGetFileInfo Lib "Shell32.dll" (path As String, attributes As Integer, ByRef info As SHFILEINFO, infoSize As Integer, flags As Integer) As IntPtr
Public Const SHGFI_ICON = &H100
Public Const SHGFI_SMALLICON = &H1
Public Const SHGFI_LARGEICON = &H0 ' Large icon
End Module
Then execute.
You will obtain the following :
Clicking on any of the well displayed icons launches the WinRar application.
Clicking on the bad displayed icon displays this error :
Changing the value of Me.LnkOrigPictureBox.Tag with a wrong path like "C:\Users\Moi\Desktop\WinRARdontexistshere.exe linkorig.lnk" and doing the same thing displays another visual and error (as expected) :
This don't work neither with DVDMaker.exe
But everything is fine with wordpad.exe, icon and application launch.
(I've tested the case of the lower/uppercase to see if it interfers, but this is not the problem)
I've noticed the problem on some other apps without understanding the reasons of this, for example :
Paint .net
VirtualBox
CloneSpy
VirtualDub
and other standard Windows apps.
When copy/pasting the problematic file path C:\Users\Moi\Desktop\WinRAR.exe linkorig.lnk onto a Windows explorer title bar, the WinRAR.exe app is launched.
Of course same thing is I double-click the .lnk file.
It is also launched well when copy/pasted into a Windows-R command window.
And also launched if called by typing WinRAR.lnk from a command-line window being placed in the C:\Users\Moi\Desktop\ folder.
I'm running onto a Windows 7 64 bits. The app is compiled using Visual Studio Express 2015. I'm logged as administrator (the one and only default account created on the Windows install). Runing the compiled app "as an administrator" does not change anything.
I tried using some configurations such the following without success :
Dim info As ProcessStartInfo = New ProcessStartInfo(--- here the path ---)
info.CreateNoWindow = False
info.UseShellExecute = False
info.RedirectStandardError = True
info.RedirectStandardOutput = True
info.RedirectStandardInput = True
Dim whatever As Process = Process.Start(info)
How could I solve this launch problem, and the icon retrieval problem of these files ?
Woow... I found the answer by luck when I saw, making some tests with some examples found over the net, that the icon problem and the error message when trying to use the corresponding file was also present when using a standard OpenFileDialog. I suspected a bug in the .Net framework. And the solution was near this, and I still don't really understand its deep reasons.
The problem was the following :
The project was defined by default into the project settings to run with .Net Framework 4.5
I switched it to run with Framework 4
Runned the app : no more problem
I switched it back to run with Framework 4.5
No more problem at all.
I need to start a process and have access to the PID, so I am trying to use ShellExecuteEx. I am attempting to open a batch file. However, no matter how I pass the parameters and no matter where the file is located and what permission's I have on the file, the function is returning with Error Code 5: Access is denied.
The File is located in the same location as the config files that have already been read successfully.
The File is set for full access permissions with any user.
It does this with any file type. I've tried just opening text files with the same outcome (Error 5)
If I use ShellExecute() instead, the batch file is run successfully.
Here is some of the code I've tried:
SHELLEXECUTEINFO exInfo;
exInfo.cbSize = sizeof(SHELLEXECUTEINFO);
exInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
exInfo.lpVerb = "open";
exInfo.lpFile = "C:\\batchtest.bat";
exInfo.nShow = SW_NORMAL;
BOOL hReturnCode = ShellExecute(&exInfo);
DWORD LastError = GetLastError();
I've also tried:
SHELLEXECUTEINFO exInfo;
exInfo.cbSize = sizeof(SHELLEXECUTEINFO);
exInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
exInfo.lpVerb = "open";
exInfo.lpFile = "C:\\Windows\\system32\\cmd.exe";
exInfo.lpParameters = "batchtest.bat";
And many variations of the above.
Also, I've tried something really simple like from here:
Get PID from ShellExecute
to no avail.
However this:
ShellExecute(NULL, "open", "C:\\testbat.bat", NULL, NULL, SW_SHOWNORMAL);
works without an error. Unfortunately, I need the PID, so I can't use ShellExecute.
Any suggestions would be greatly appreciated. I feel like I've exhausted all of my options.
Environment:
VS 2008
Windows 7
EDIT: fixed the code to "C:\batchtest.bat"; as suggested. (Still same result)
Figured it out.
In order to run batch file and I guess some other types of exe's on Windows 7, you have to elevate the call using the lpVerb = _TEXT("runas") -- even if you have UAC turned off. This isn't documented in the SHELLEXECUTEINFO structure documentation on MDSN (it isn't even given as an option), since it says: "The following verbs are commonly used"
The final code was as follows:
SHELLEXECUTEINFO exInfo;
exInfo.cbSize = sizeof(SHELLEXECUTEINFO);
exInfo.fMask = SEE_MASK_NOCLOSEPROCESS; //allows the PID to be returned
exInfo.hwnd = NULL;
exInfo.lpVerb = _TEXT("runas"); //elevates for Windows 7
exInfo.lpFile = "C:\\BatchTest.bat";
exInfo.lpParameters = NULL;
exInfo.nShow = SW_MAXIMIZE;
exInfo.hInstApp = NULL;
exInfo.lpDirectory = NULL;
BOOL hReturnCode = ShellExecuteEx(&exInfo);
I hope that helps others out.
Shouldn't the line
exInfo.lpFile = "C:\\batchtest.exe";
be
exInfo.lpFile = "C:\\batchtest.bat";
Im a complete novice to the "best practices" etc of writing in any code.
I tend to just write it an if it works, why fix it.
Well, this way of working is landing me in some hot water. I am writing a simple windows service to server a single webpage. (This service will be incorperated in to another project which monitors the services and some folders on a group of servers.)
My problem is that whenever a request is recieved, the memory usage jumps up by a few K per request and keeps qoing up on every request.
Now ive found that by putting GC.Collect in the mix it stops at a certain number but im sure its not meant to be used this way. I was wondering if i am missing something or not doing something i should to free up memory.
Here is the code:
Public Class SimpleWebService : Inherits ServiceBase
'Set the values for the different event log types.
Public Const EVENT_ERROR As Integer = 1
Public Const EVENT_WARNING As Integer = 2
Public Const EVENT_INFORMATION As Integer = 4
Public listenerThread As Thread
Dim HTTPListner As HttpListener
Dim blnKeepAlive As Boolean = True
Shared Sub Main()
Dim ServicesToRun As ServiceBase()
ServicesToRun = New ServiceBase() {New SimpleWebService()}
ServiceBase.Run(ServicesToRun)
End Sub
Protected Overrides Sub OnStart(ByVal args As String())
If Not HttpListener.IsSupported Then
CreateEventLogEntry("Windows XP SP2, Server 2003, or higher is required to " & "use the HttpListener class.")
Me.Stop()
End If
Try
listenerThread = New Thread(AddressOf ListenForConnections)
listenerThread.Start()
Catch ex As Exception
CreateEventLogEntry(ex.Message)
End Try
End Sub
Protected Overrides Sub OnStop()
blnKeepAlive = False
End Sub
Private Sub CreateEventLogEntry(ByRef strEventContent As String)
Dim sSource As String
Dim sLog As String
sSource = "Service1"
sLog = "Application"
If Not EventLog.SourceExists(sSource) Then
EventLog.CreateEventSource(sSource, sLog)
End If
Dim ELog As New EventLog(sLog, ".", sSource)
ELog.WriteEntry(strEventContent)
End Sub
Public Sub ListenForConnections()
HTTPListner = New HttpListener
HTTPListner.Prefixes.Add("http://*:1986/")
HTTPListner.Start()
Do While blnKeepAlive
Dim ctx As HttpListenerContext = HTTPListner.GetContext()
Dim HandlerThread As Thread = New Thread(AddressOf ProcessRequest)
HandlerThread.Start(ctx)
HandlerThread = Nothing
Loop
HTTPListner.Stop()
End Sub
Private Sub ProcessRequest(ByVal ctx As HttpListenerContext)
Dim sb As StringBuilder = New StringBuilder
sb.Append("<html><body><h1>Test My Service</h1>")
sb.Append("</body></html>")
Dim buffer() As Byte = Encoding.UTF8.GetBytes(sb.ToString)
ctx.Response.ContentLength64 = buffer.Length
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length)
ctx.Response.OutputStream.Close()
ctx.Response.Close()
sb = Nothing
buffer = Nothing
ctx = Nothing
'This line seems to keep the mem leak down
'System.GC.Collect()
End Sub
End Class
Please feel free to critisise and tear the code apart but please BE KIND. I have admitted I dont tend to follow the best practice when it comes to coding.
You are right, you should not be doing this. Remove the Collect() call and let it run for a week. Any decent .NET book will talk about how the garbage collector works and how it does not immediately release memory when you set an object to Nothing. It doesn't kick in until you've consumed somewhere between 2 and 8 megabytes. This is not a leak, merely effective use of a plentiful resource.
You use a new thread for each individual connection, that's pretty expensive and scales very poorly when you get a lot of connections. Consider using ThreadPool.QueueUserWorkItem instead. Threadpool threads are very cheap and their allocation and execution is well controlled by the threadpool manager.
I know you can do it with an WMI event or overriding WndProc and looking for the right messages, but i was wondering if there wasn't something hidden in the net framework that makes this task easyer.
This free DriveDetector class provides this functionality (as least for USB drives, which is what I used it for). It uses the WndProc approach you describe.
I'm not aware of anything similar that is part of the standard .NET libraries.
In the following code, moDiskDetector will raise an EventArrived event when a new drive is detected.
To detect removal of a drive use "__InstanceDeletionEvent".
Private WithEvents moDiskAddWatcher As ManagementEventWatcher
Private Sub StartWatcher()
If moDiskAddWatcher Is Nothing Then
moDiskAddWatcher = CreateWatcher("__InstanceCreationEvent", "(TargetInstance ISA 'Win32_DiskDrive')")
End If
moDiskAddWatcher.Start()
End Sub
Private Sub StopWatcher()
If moDiskAddWatcher IsNot Nothing Then
moDiskAddWatcher.Stop()
moDiskAddWatcher.Dispose()
End If
End Sub
Private Function CreateWatcher(ByVal sClassName As String, ByVal sCondition As String) As ManagementEventWatcher
Dim oQuery As New WqlEventQuery()
oQuery.EventClassName = sClassName
oQuery.WithinInterval = New TimeSpan(0, 0, 5)
oQuery.Condition = sCondition
Return New ManagementEventWatcher(oQuery)
End Function