PaulDalyTT
Programmer
Hi folks,
Has anyone any experience in using impersonation in VB.NET?
I have developed my own task scheuler which runs as a service under 'Local System' account on my laptop. The scheduler works fine and I can get the tasks to start as and when I need them.
However, I would like to be able to run tasks under a user other than the Local System account. Specifically, I would like to use my own domain user so that I can copy a file from the laptop to the LAN. (one of the tasks is a weekly backup of my laptop and want to copy this bkf file to the LAN for safe storage.)
I have created a commandline program 'VBRunAs' which my scheduler calls if the task specifies a username / password. This uses impersonation to run the task under the specified username and password. (Otherwise my scheduler runs the task directly under the System Account)
When I call the VBRunAs program, either from command line or via the scheduler, the process starts but does not appear to be impersonating the user. ie the process is still run under the calling user, not the impersonated user (according to task manager)
I have confirmed that the correct username and password is being supplied to the runas command using a messagebox in the command line app.
I have also tried using the processinfo arguments to set username and password (you'll see them commented out in the code). This works when I run at command line, but I get access denied when I run via scheduler.
Code below...
any ideas???
Thanks all!
Paul.
Main VBRunAs code
AliasAccount class (took this 'as is' from a VB site
Has anyone any experience in using impersonation in VB.NET?
I have developed my own task scheuler which runs as a service under 'Local System' account on my laptop. The scheduler works fine and I can get the tasks to start as and when I need them.
However, I would like to be able to run tasks under a user other than the Local System account. Specifically, I would like to use my own domain user so that I can copy a file from the laptop to the LAN. (one of the tasks is a weekly backup of my laptop and want to copy this bkf file to the LAN for safe storage.)
I have created a commandline program 'VBRunAs' which my scheduler calls if the task specifies a username / password. This uses impersonation to run the task under the specified username and password. (Otherwise my scheduler runs the task directly under the System Account)
When I call the VBRunAs program, either from command line or via the scheduler, the process starts but does not appear to be impersonating the user. ie the process is still run under the calling user, not the impersonated user (according to task manager)
I have confirmed that the correct username and password is being supplied to the runas command using a messagebox in the command line app.
I have also tried using the processinfo arguments to set username and password (you'll see them commented out in the code). This works when I run at command line, but I get access denied when I run via scheduler.
Code below...
any ideas???
Thanks all!
Paul.
Main VBRunAs code
Code:
Module VBRunAs
Private Function GetSecurePassword(ByVal pwd As String) As System.Security.SecureString
GetSecurePassword = Nothing
If Not String.IsNullOrEmpty(pwd) Then
GetSecurePassword = New System.Security.SecureString
For Each character As Char In pwd.ToCharArray
GetSecurePassword.AppendChar(character)
Next
GetSecurePassword.MakeReadOnly()
End If
End Function
Private exe As String
Private args As String
Private DomainName As String
Private UserName As String
Private Password As String
'Sub main()
' MsgBox("insufficieng args: " & _
' "ByVal DomainName As String" & vbCr & _
' "ByVal UserName As String" & vbCr & _
' "ByVal Password As String" & vbCr & _
' "ByVal Exe As String" & vbCr & _
' "ByVal Args As String")
'End Sub
Sub Main(ByVal cmdArgs() As String)
If cmdArgs.Length = 5 Then
DomainName = cmdArgs(0)
UserName = cmdArgs(1)
Password = cmdArgs(2)
exe = cmdArgs(3)
args = cmdArgs(4)
' Dim imp As New RunAs_Impersonator
Dim imp As New AliasAccount(DomainName & "\" & UserName, Password)
Try
'imp.ImpersonateStart(DomainName, UserName, Password) 'creates new context using token for user
imp.BeginImpersonation()
'everything between BeginImpersonation and EndImpersonation will be run as the impersonated user
Dim pInfo As New ProcessStartInfo()
Dim p As New Process
pInfo.FileName = exe
If Not (args = "" Or _
args Is Nothing Or _
args = "UnDef") Then
pInfo.Arguments = args
End If
'pInfo.Domain = DomainName
'pInfo.UserName = UserName
'pInfo.Password = GetSecurePassword(Password)
pInfo.WindowStyle = ProcessWindowStyle.Normal
pInfo.UseShellExecute = False
pInfo.CreateNoWindow = False
MsgBox("About to start " & pInfo.FileName & vbCr & _
"As " & DomainName & "\" & UserName & vbCr & _
"using " & Password _
)
p.StartInfo = pInfo
p.Start()
imp.EndImpersonation()
Catch ex As Exception 'make sure impersonation is stopped whether code succeeds or not
MsgBox("Exception trapped in VBRunAs: " & ex.Message)
imp.EndImpersonation()
End Try
Else
MsgBox("Incorrect Number of args." & vbCr & vbCr & _
"You supplied " & cmdArgs.Length & " args. " & vbCr & _
"You SHOULD have supplied 5 args. " & vbCr & _
"DomainName As String" & vbCr & _
"UserName As String" & vbCr & _
"Password As String" & vbCr & _
"Exe As String" & vbCr & _
"""Args"" As String")
End If
End Sub
End Module
AliasAccount class (took this 'as is' from a VB site
Code:
Public Class AliasAccount
Private _username, _password, _domainname As String
Private _tokenHandle As New IntPtr(0)
Private _dupeTokenHandle As New IntPtr(0)
Private _impersonatedUser As System.Security.Principal.WindowsImpersonationContext
Public Sub New(ByVal username As String, ByVal password As String)
Dim nameparts() As String = username.Split("\")
If nameparts.Length > 1 Then
_domainname = nameparts(0)
_username = nameparts(1)
Else
_username = username
End If
_password = password
End Sub
Public Sub New(ByVal username As String, ByVal password As String, ByVal domainname As String)
_username = username
_password = password
_domainname = domainname
End Sub
Public Sub BeginImpersonation()
Const LOGON32_PROVIDER_DEFAULT As Integer = 0
Const LOGON32_LOGON_INTERACTIVE As Integer = 2
Const SecurityImpersonation As Integer = 2
Dim win32ErrorNumber As Integer
_tokenHandle = IntPtr.Zero
_dupeTokenHandle = IntPtr.Zero
If Not LogonUser(_username, _domainname, _password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, _tokenHandle) Then
win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()
Throw New ImpersonationException(win32ErrorNumber, GetErrorMessage(win32ErrorNumber), _username, _domainname)
End If
If Not DuplicateToken(_tokenHandle, SecurityImpersonation, _dupeTokenHandle) Then
win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error()
CloseHandle(_tokenHandle)
Throw New ImpersonationException(win32ErrorNumber, "Unable to duplicate token!", _username, _domainname)
End If
Dim newId As New System.Security.Principal.WindowsIdentity(_dupeTokenHandle)
_impersonatedUser = newId.Impersonate()
End Sub
Public Sub EndImpersonation()
If Not _impersonatedUser Is Nothing Then
_impersonatedUser.Undo()
_impersonatedUser = Nothing
If Not System.IntPtr.op_Equality(_tokenHandle, IntPtr.Zero) Then
CloseHandle(_tokenHandle)
End If
If Not System.IntPtr.op_Equality(_dupeTokenHandle, IntPtr.Zero) Then
CloseHandle(_dupeTokenHandle)
End If
End If
End Sub
Public ReadOnly Property username() As String
Get
Return _username
End Get
End Property
Public ReadOnly Property domainname() As String
Get
Return _domainname
End Get
End Property
Public ReadOnly Property currentWindowsUsername() As String
Get
Return System.Security.Principal.WindowsIdentity.GetCurrent().Name
End Get
End Property
#Region "Exception Class"
Public Class ImpersonationException
Inherits System.Exception
Public ReadOnly win32ErrorNumber As Integer
Public Sub New(ByVal win32ErrorNumber As Integer, ByVal msg As String, ByVal username As String, ByVal domainname As String)
MyBase.New(String.Format("Impersonation of {1}\{0} failed! [{2}] {3}", username, domainname, win32ErrorNumber, msg))
Me.win32ErrorNumber = win32ErrorNumber
End Sub
End Class
#End Region
#Region "External Declarations and Helpers"
Private Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As [String], _
ByVal lpszDomain As [String], ByVal lpszPassword As [String], _
ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, _
ByRef phToken As IntPtr) As Boolean
Private Declare Auto Function DuplicateToken Lib "advapi32.dll" (ByVal ExistingTokenHandle As IntPtr, _
ByVal SECURITY_IMPERSONATION_LEVEL As Integer, _
ByRef DuplicateTokenHandle As IntPtr) As Boolean
Private Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean
<System.Runtime.InteropServices.DllImport("kernel32.dll")> _
Private Shared Function FormatMessage(ByVal dwFlags As Integer, ByRef lpSource As IntPtr, _
ByVal dwMessageId As Integer, ByVal dwLanguageId As Integer, ByRef lpBuffer As [String], _
ByVal nSize As Integer, ByRef Arguments As IntPtr) As Integer
End Function
Private Function GetErrorMessage(ByVal errorCode As Integer) As String
Dim FORMAT_MESSAGE_ALLOCATE_BUFFER As Integer = &H100
Dim FORMAT_MESSAGE_IGNORE_INSERTS As Integer = &H200
Dim FORMAT_MESSAGE_FROM_SYSTEM As Integer = &H1000
Dim messageSize As Integer = 255
Dim lpMsgBuf As String
Dim dwFlags As Integer = FORMAT_MESSAGE_ALLOCATE_BUFFER Or FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS
Dim ptrlpSource As IntPtr = IntPtr.Zero
Dim prtArguments As IntPtr = IntPtr.Zero
Dim retVal As Integer = FormatMessage(dwFlags, ptrlpSource, errorCode, 0, lpMsgBuf, messageSize, prtArguments)
If 0 = retVal Then
Throw New System.Exception("Failed to format message for error code " + errorCode.ToString() + ". ")
End If
Return lpMsgBuf
End Function
#End Region
End Class