Performance monitoring: strange error in .Net 2.0



There seems to be a strange interaction/conflict between
System.Diagnostics.PerformanceCounter objects and
System.Security.Principal.WindowsIdentity.Impersonate() in CLR 2.0, that
didn't exist in 1.1.

I arrived at this by letting VS.Net 2005 convert a VB.Net 2003 project and
just starting it in the debugger after making a few minor modifications
(all related to variable initialization): it compiled without errors, but
started throwing exceptions that I believe it shouldn't, and that didn't
occur in 1.1.


My actual project (or even the relevant part of it) is too large and too
proprietary to post it in a newsgroup, but I created a sample program that
reproduces the problem and I'll paste it at the bottom of this message
(VB.Net 2003 .vb file, not converted to VS.2005 yet).


It uses LogonUser to log on to an account name/password as specified in two
CONST's near the top of the file; DuplicateHandleEx to duplicate the
handle, and the passes the duplicate to WindowsIdentity.Impersonate.

The account I tested it with has administator rights.


So first create a temporary local user account to test it with, and put
that account's name and password in the two constants.

Start a debug build in VS.Net. When you click the START button it should
start printing CPU use and system uptime in the debug output window every
500 msec until you click STOP.


After verifying that this works, create a copy of the project directory,
and open the solution in VS.2005 and run it there.


VS.Net 2003:
It just works as expected.


VS.Net 2005:
One of the performance counters (CPU use) only works the first time its
NextValue method is called; from the second call on it throws a
Win32Exception "Access denied".

Now comment out the "Logon" line in Button1_Click, and the error doesn't
occur anymore.


The code (the Win32 API declarations are copied & pasted from my real
application, some parts may not be used here).
I tried to avoid broken lines (word-wrap by newsreaders), but please don't
shoot if...

'=================== Form1.vb ============================================

Public Class Form1
Inherits System.Windows.Forms.Form

Private Const ACCOUNT_NAME As String = "UserName"
Private Const ACCOUNT_PASS As String = "PassWord"

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
Friend WithEvents Timer1 As System.Windows.Forms.Timer

<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.components = New System.ComponentModel.Container
Me.Button1 = New System.Windows.Forms.Button
Me.Button2 = New System.Windows.Forms.Button
Me.Timer1 = New System.Windows.Forms.Timer(Me.components)
Me.SuspendLayout()
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(16, 24)
Me.Button1.Name = "Button1"
Me.Button1.TabIndex = 0
Me.Button1.Text = "Start"
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(104, 24)
Me.Button2.Name = "Button2"
Me.Button2.TabIndex = 1
Me.Button2.Text = "Stop"
'
'Timer1
'
Me.Timer1.Interval = 500
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 273)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)

End Sub

#End Region

Private Enum LOGONTYPE As Integer
LOGON32_LOGON_INTERACTIVE = 2
LOGON32_LOGON_NETWORK = 3
LOGON32_LOGON_BATCH = 4
LOGON32_LOGON_SERVICE = 5
LOGON32_LOGON_NEW_CREDENTIALS = 9
End Enum

Private Enum LOGONPROVIDER As Integer
LOGON32_PROVIDER_DEFAULT = 0
LOGON32_PROVIDER_WINNT35 = 1
LOGON32_PROVIDER_WINNT40 = 2
LOGON32_PROVIDER_WINNT50 = 3
End Enum

Private Enum SECURITY_IMPERSONATION_LEVEL As Integer
SecurityAnonymous = 0
SecurityIdentification = 1
SecurityImpersonation = 2
SecurityDelegation = 3
End Enum

Private Enum TOKEN_TYPE
tokenprimary = 1
tokenimpersonation
End Enum

Private Declare Auto Function LogonUser Lib "advapi32" ( _
ByVal Username As String, _
ByVal Domain As String, _
ByVal Password As String, _
ByVal dwLogonType As LOGONTYPE, _
ByVal dwLogonProvider As LOGONPROVIDER, _
ByRef hToken As IntPtr _
) As Boolean

Private Declare Auto Function DuplicateToken Lib "advapi32" ( _
ByVal ExistingTokenHandle As IntPtr, _
ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, _
ByRef DuplicateTokenHandle As IntPtr _
) As Boolean

Private Declare Auto Function DuplicateTokenEx Lib "advapi32" ( _
ByVal hExistingToken As IntPtr, _
ByVal dwDesiredAccess As Int32, _
ByVal lpTokenAttributes As IntPtr, _
ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, _
ByVal TokenType As TOKEN_TYPE, _
ByRef hNewToken As IntPtr _
) As Boolean

Private Declare Auto Function CloseHandle Lib "kernel32" ( _
ByVal hObject As IntPtr) As Boolean

Private hUserToken As IntPtr = IntPtr.Zero
Private hFullToken As IntPtr = IntPtr.Zero

Private Impersonation _
As System.Security.Principal.WindowsImpersonationContext = Nothing

Public Enum LogonMode
LogonImpersonate
LogonDelegate
End Enum

Private pdCPU, pdUPT As System.Diagnostics.PerformanceCounter

'----------------------------------------------------------------------

Private Sub LogOn()
Try
Dim LMode As LOGONTYPE
LMode = LOGONTYPE.LOGON32_LOGON_INTERACTIVE

If LogonUser(ACCOUNT_NAME, System.Environment.MachineName, _
ACCOUNT_PASS, LMode, _
LOGONPROVIDER.LOGON32_PROVIDER_WINNT50, hUserToken) Then

If DuplicateTokenEx(hUserToken, 0, IntPtr.Zero, _
SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, _
TOKEN_TYPE.tokenprimary, hFullToken) Then

Impersonation = _
System.Security.Principal.WindowsIdentity.Impersonate(hFullToken)

Debug.WriteLine("Logon OK")
End If
End If
Catch ex As Exception
Debug.WriteLine(ex.ToString())
End Try
End Sub

Private Sub LogOff()
If Not Impersonation Is Nothing Then
Impersonation.Undo()
Impersonation = Nothing
End If
If hFullToken.ToInt32 <> 0 Then
CloseHandle(hFullToken)
hFullToken = IntPtr.Zero
End If
If hUserToken.ToInt32 <> 0 Then
CloseHandle(hUserToken)
hUserToken = IntPtr.Zero
End If
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
LogOn()
pdCPU = New System.Diagnostics.PerformanceCounter("Processor", _
"% Processor Time", "_Total")
pdUPT = New System.Diagnostics.PerformanceCounter("System", _
"System Up Time")
Timer1.Enabled = True
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
Timer1.Enabled = False
pdCPU = Nothing
pdUPT = Nothing
LogOff()
End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Timer1.Tick
Dim s1 As Single, s2 As Single
Try
s1 = pdCPU.NextValue
Catch ex As Exception
s1 = -1.0
End Try
Try
s2 = pdUPT.NextValue
Catch ex As Exception
s2 = -1.0
End Try
Debug.WriteLine(s1 & ", " & s2)
End Sub

End Class
.


Loading