due to our applications being moved onto a Citrix VM platform I need to move our application settings config files from C:\ProgramData to an alternative location. With this particular application each user has their own config file, which is effectively a clone of the app.config file but with just user specific content, for example encrypted authentication credentials.
With this in mind, I modified the code to save the config files to a mapped network drive. Using system.configuration I am able to read content without any issues from the networked drive but when I attempt to write changes I get the following error message:
*"Attempted to perform an unauthorized operation."
The following is the function which generates the error:
Public Shared Function WriteSettingUser(ByVal configPath As String, ByVal userName As String, ByVal sectionName As String, ByVal setting As String, ByVal value As String) As String
Dim result As Boolean = False
Dim filemap As New ExeConfigurationFileMap
Dim config As Configuration
Dim section As AppSettingsSection
Try
filemap.ExeConfigFilename = configPath & userName & ".config"
config = ConfigurationManager.OpenMappedExeConfiguration(filemap, ConfigurationUserLevel.None)
section = config.GetSection(sectionName)
section.Settings(setting).Value = value
config.Save()
result = True
Catch ex As Exception
Throw
result = False
Finally
End Try
Return result
The error is generated at the 'config.save' line.
Would appreciate any insight into this issue.
Kind Regards
Paul J.
I'm working on adding a document upload function to an application I've written. I want the user to be able to upload, open, and delete a document on a network drive that they cannot access normally. With this in mind, I stumbled upon Impersonation, where the user can impersonate a user account that has full rights to the drive, then dispose of that after the code has been executed.
I've never used impersonation before, so during my research I found this thread:
Impersonate a Windows or Active Directory user from a different, untrusted domain
I created and copied the class that user Max Vernon had posted as follows:
Option Explicit On
Option Infer Off
Imports System
Imports System.Runtime.InteropServices ' DLL Import
Imports System.Security.Principal ' WindowsImpersonationContext
Imports System.ComponentModel
Public Class Impersonation
'Group Type Enum
Enum SECURITY_IMPERSONATION_LEVEL As Int32
SecurityAnonymous = 0
SecurityIdentification = 1
SecurityImpersonation = 2
SecurityDelegation = 3
End Enum
Public Enum LogonType As Integer
'This logon type is intended for users who will be interactively using the computer, such as a user being logged on
'by a terminal server, remote shell, or similar process.
'This logon type has the additional expense of caching logon information for disconnected operations,
'therefore, it is inappropriate for some client/server applications, such as a mail server.
LOGON32_LOGON_INTERACTIVE = 2
'This logon type is intended for high performance servers to authenticate plaintext passwords.
'The LogonUser function does not cache credentials for this logon type.
LOGON32_LOGON_NETWORK = 3
'This logon type is intended for batch servers, where processes may be executing on behalf of a user without
'their direct intervention. This type is also for higher performance servers that process many plaintext
'authentication attempts at a time, such as mail or Web servers.
'The LogonUser function does not cache credentials for this logon type.
LOGON32_LOGON_BATCH = 4
'Indicates a service-type logon. The account provided must have the service privilege enabled.
LOGON32_LOGON_SERVICE = 5
'This logon type is for GINA DLLs that log on users who will be interactively using the computer.
'This logon type can generate a unique audit record that shows when the workstation was unlocked.
LOGON32_LOGON_UNLOCK = 7
'This logon type preserves the name and password in the authentication package, which allows the server to make
'connections to other network servers while impersonating the client. A server can accept plaintext credentials
'from a client, call LogonUser, verify that the user can access the system across the network, and still
'communicate with other servers.
'NOTE: Windows NT: This value is not supported.
LOGON32_LOGON_NETWORK_CLEARTEXT = 8
'This logon type allows the caller to clone its current token and specify new credentials for outbound connections.
'The new logon session has the same local identifier but uses different credentials for other network connections.
'NOTE: This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
'NOTE: Windows NT: This value is not supported.
LOGON32_LOGON_NEW_CREDENTIALS = 9
End Enum
Public Enum LogonProvider As Integer
'Use the standard logon provider for the system.
'The default security provider is negotiate, unless you pass NULL for the domain name and the user name
'is not in UPN format. In this case, the default provider is NTLM.
'NOTE: Windows 2000/NT: The default security provider is NTLM.
LOGON32_PROVIDER_DEFAULT = 0
LOGON32_PROVIDER_WINNT35 = 1
LOGON32_PROVIDER_WINNT40 = 2
LOGON32_PROVIDER_WINNT50 = 3
End Enum
'Obtains user token.
Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, ByVal dwLogonType As LogonType, ByVal dwLogonProvider As LogonProvider, ByRef phToken As IntPtr) As Integer
'Closes open handles returned by LogonUser.
Declare Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean
'Creates duplicate token handle.
Declare Auto Function DuplicateToken Lib "advapi32.dll" (ExistingTokenHandle As IntPtr, SECURITY_IMPERSONATION_LEVEL As Int16, ByRef DuplicateTokenHandle As IntPtr) As Boolean
'WindowsImpersonationContext newUser.
Private newUser As WindowsImpersonationContext
'Attempts to impersonate a user. If successful, returns
'a WindowsImpersonationContext of the new user's identity.
'
'Username that you want to impersonate.
'Logon domain.
'User's password to logon with.
Public Sub Impersonator(ByVal sDomain As String, ByVal sUsername As String, ByVal sPassword As String)
'Initialize tokens
Dim pExistingTokenHandle As New IntPtr(0)
Dim pDuplicateTokenHandle As New IntPtr(0)
If sDomain = "" Then
sDomain = System.Environment.MachineName
End If
Try
Const LOGON32_PROVIDER_DEFAULT As Int32 = 0
Const LOGON32_LOGON_NEW_CREDENTIALS = 9
Dim bImpersonated As Boolean = LogonUser(sUsername, sDomain, sPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, pExistingTokenHandle)
If bImpersonated = False Then
Dim nErrorCode As Int32 = Marshal.GetLastWin32Error()
Throw New ApplicationException("LogonUser() failed with error code: " & nErrorCode.ToString)
End If
Dim bRetVal As Boolean = DuplicateToken(pExistingTokenHandle, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, pDuplicateTokenHandle)
If bRetVal = False Then
Dim nErrorCode As Int32 = Marshal.GetLastWin32Error
CloseHandle(pExistingTokenHandle)
Throw New ApplicationException("DuplicateToken() failed with error code: " & nErrorCode)
Else
Dim newId As New WindowsIdentity(pDuplicateTokenHandle)
Dim impersonatedUser As WindowsImpersonationContext = newId.Impersonate
newUser = impersonatedUser
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
If pExistingTokenHandle <> IntPtr.Zero Then
CloseHandle(pExistingTokenHandle)
End If
If pDuplicateTokenHandle <> IntPtr.Zero Then
CloseHandle(pDuplicateTokenHandle)
End If
End Try
End Sub
Public Sub Undo()
newUser.Undo()
End Sub
End Class
The impersonation works great for "uploading" (actually just copying a file over from the users local files to the network drive, creating a specific file path if it doesn't exist) but doesn't seem to work when trying to open the file back up, or delete said file.
I get an access denied error like this:
Error Message When Trying to Open File
The Open File Click Event and Class Call Looks Like This:
Private Sub btnOpenDoc_Click(sender As Object, e As EventArgs) Handles btnOpenDoc.Click
Dim Impersonator As New Impersonation
Dim sUser As String = "UserNameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
Try
If sActionID <> "" And iDocument = 1 Then
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
Process.Start(RetrieveFilePath())
'Ends Impersonation
Impersonator.Undo()
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->btnOpenDoc_Click", currentUser.getEmployeeName())
End Try
End Sub
Here's Document Delete Function:
Private Function DeleteFile() As Boolean
Dim Impersonator As New Impersonation
Dim sUser As String = "UsernameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
Try
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
File.Delete(RetrieveFilePath())
Return True
'Ends Impersonation
Impersonator.Undo()
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->DeleteFile", currentUser.getEmployeeName())
Return False
End Try
End Function
It's used in basically the same way in the FileSave function. Like I said I'm new to impersonation, and feel like I've hit a wall, having researched and tried various things all morning. Any advice is much appreciated!
-Levi
So after much research and trial and error I have an answer to this.
The short answer:
There is not a clean, elegant way to use impersonation to open a file on a network drive because you either butt heads with Windows Security or run into problems with Windows Shell. I decided to go another route.
The long answer:
I believe I was correct in that the Access was Denied error was due to trying to open a file as the impersonated user on the local user's computer. To get around this I decided to try and use ProcessStartInfo() to pass in the correct credentials (while also using impersonation to access the drive) like this:
'Opens the document associated with this action
Private Sub btnOpenDoc_Click(sender As Object, e As EventArgs) Handles btnOpenDoc.Click
'Initializes an impersonation object
Dim Impersonator As New Impersonation
'Strings with login credentials
Dim sUser As String = "UsernameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
'Used to load file path in from RetrieveFilePath()
Dim sPath As String = ""
Try
If sActionID <> "" And iDocument = 1 Then
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
'Initializes a ProcessStartInfo Object to use with impersonation
'as Process.Start class always inherits the security context of
'the parent process i.e. the local user
Dim startInfo As New ProcessStartInfo()
'Creates a secure string as the startInfo.Password parameter only accepts SecureStrings
Dim securePass As New Security.SecureString()
'You can't put a full string into a SecureString, so appending char by char
For Each c As Char In sPass
securePass.AppendChar(c)
Next
'Grab the file path
sPath = RetrieveFilePath()
'Load in the parameters for startInfo
startInfo.FileName = sPath
startInfo.UserName = sUser
startInfo.Password = securePass
startInfo.Domain = sDomain
startInfo.UseShellExecute = False
startInfo.WorkingDirectory = "\\Directory\Goes Here"
If File.Exists(sPath) Then
'Execute the process using startInfo
Process.Start(startInfo)
Else
MsgBox("File Not Found!")
End If
'Dispose of securePass
securePass.Dispose()
'Ends Impersonation
Impersonator.Undo()
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->btnOpenDoc_Click", currentUser.getEmployeeName())
End Try
End Sub
There are some interesting aspects to note here. You have to use a SecureString for the password when using ProcessStartInfo, and can only be assigned per character, and more importantly, I had to set UseShellExecute property to False.
I was hopeful that this would work, but after some iterations I got stuck on this error message:
Error Message Example
I figured out that this was due to being unable to access Windows Shell to find the default program to open the corresponding file type with, so it just expected an executable. After more research I was unable to find a clean way to get around this so I've decided to go about addressing this file upload a different way.
I know this is an old question, but maybe it can help someone else, I had the same problem and finally, realized that it's not enough to grant access to the impersonate user to read and write on the folder but also modify, if you don't, the user can not delete the file.
So this is what I've got -
Public Shared Function GetDirectoryEntry() As DirectoryEntry
Try
Dim entryRoot As New DirectoryEntry("LDAP://RootDSE")
Dim Domain As String = DirectCast(entryRoot.Properties("defaultNamingContext")(0), String)
Dim de As New DirectoryEntry()
de.Path = "LDAP://" & Domain
de.AuthenticationType = AuthenticationTypes.Secure
Return de
Catch
Return Nothing
End Try
End Function
Protected Sub rbAddUser_Click(sender As Object, e As EventArgs) Handles rbAddUser.Click
AddMemberToGroup("LDAP://DOMAIN.local/CN=" & !DISTRIBUTIONNAME! & ",CN=Users,DC=DOMAIN,DC=local", "/CN=" & !SELECTEDUSER! & ",CN=Users,DC=DOMAIN,DC=local")
End Sub
Private Sub AddMemberToGroup(ByVal bindString As String, ByVal newMember As String)
Dim ent As DirectoryEntry = GetDirectoryEntry()
ent.Properties("member").Add(newMember)
ent.CommitChanges()
End Sub
I hope this is easy enough for people to read, anyway the group and user are selected by the users in a table and when they click the add button I want the selected users to be adding to the selected distribution list.
when it gets to the CommitChanges() I get this error
An exception of type 'System.DirectoryServices.DirectoryServicesCOMException' occurred in System.DirectoryServices.dll but was not handled in user code Additional information: An operations error occurred.Error -2147016672
This is a common issue with the Process Model application pool configuration, from the official documentation:
By using the <processModel> element, you can configure many of the security, performance, health, and reliability features of application pools on IIS 7 and later.
This issue exists as CommitChanges() requires elevated privileges, and can be fixed by setting your web-application to run under NetworkManager; this can be done in two ways:
Directly in your code, place the problem code inside this Using statement:
Using HostingEnvironment.Impersonate()
'Problem code goes here.
End Using
Via IIS Manager:
Navigate to your website's application pool;
Navigate to Advanced Settings;
Scroll down to the Process Model group;
Change Identity to NetworkService
I solved the error by passing through my user credentials
Private Sub AddMemberToGroup(ByVal bindString As String, ByVal newMember As String)
Dim ent As New GetDirectoryEntry(bindString)
ent.Properties("member").Add(newMember)
ent.Username = "DOMAIN\USERNAME"
ent.Password = "PASSWORD"
ent.CommitChanges()
End Sub
However my code still doesn't work, I just get no errors.
I have wrote a web service, in a nutshell it uses openpop to get email messages does stuff with the content to insert into databases and saves attachments which are images. That works fine when i save images locally, it does exactley what it is suppose to. Now an added requirment was to save images to an FTP directory, so i can create my folders dynamically (they are created based upon timestamp) and that works well. My problem comes from when i try to save them to the ftp. Yes my user name and password are correct, otherwise i wouldn't be creating the directory.
Private Sub UploadFile(ByVal fileToSave As FileInfo, ByVal path As String)
Dim UploadRequest As FtpWebRequest = DirectCast(WebRequest.Create("ftp://UserName:Passowrd#999.99.999.9" & path), FtpWebRequest)
UploadRequest.Credentials = New NetworkCredential("PicService", "grean.matching18")
UploadRequest.Method = System.Net.WebRequestMethods.Ftp.UploadFile
UploadRequest.UseBinary = True
UploadRequest.UsePassive = True
' Const BufferSize As Integer = 2048
' Dim content(BufferSize - 1) As Byte, dataRead As Integer
Dim bFile() As Byte = System.IO.File.ReadAllBytes(fileToSave.ToString)
'UploadRequest.ContentLength = content.Length
Using FileStream1 As FileStream = fileToSave.OpenRead()
Try
'open request to send
Using RequestStream As Stream = UploadRequest.GetRequestStream
End Using
Catch ex As Exception
Finally
'ensure file closed
FileStream1.Close()
End Try
End Using
End Sub
I have tried using Passive False and Binary False as well, i did more research on my stack trace.
And found this article but no solution as of yet. Any input would be appreciated, i am also posting another question on windows services for different issue. If you would like to take a shot at it, the other question isnt about ftp but permissions for a service on windows server 2003
This may not be the solution but I've found that the URI string has to be 'just right' and that what is 'just right' varies by the ftp server.
So ftp://server/directory/file works on some servers but needs to be ftp://server//directory/file to work on others (note the double slash after the server name)
Aso, your URI has 'password' spelled incorrectly: ftp://UserName:Passowrd#999.99.999.9 and you are supplying the credentials in a separate code line as well.
I created a simple windows service on my local PC and added the following code to it
Protected Overrides Sub OnStart(ByVal args() As String)
Const iTIME_INTERVAL As Integer = 60000 ' 60 seconds.
Dim oTimer As System.Threading.Timer
System.IO.File.AppendAllText("C:\AuthorLog.txt", _
"AuthorLogService has been started at " & Now.ToString())
Dim tDelegate As Threading.TimerCallback = AddressOf EventAction
oTimer = New System.Threading.Timer(tDelegate, Me, 0, iTIME_INTERVAL)
End Sub
Protected Overrides Sub OnStop()
End Sub
Public Sub EventAction(ByVal sender As Object)
System.IO.File.AppendAllText("C:\AuthorLog.txt", _
"AuthorLogService fires EventAction at " & Now.ToString())
End Sub
Next I added a Setup project to this solution and added a custom action (By double clicking application folder then clicking add output folder then selecting primary output from the dialog). The solution builds fine but I have 2 problems.
1) Everytime I install the service, it asks me for the username, password and confirm password; I was wondering if there was anyway to get rid of it atleast while running locally. I tried setting the account type to user, local service, local system etc but it keeps popping up.
2) Once I enter the credentials (random ones), I get an error "No mapping between account names and security ids was done".
Kindly help me out
1: You could make your service be selfinstalling as in this codeproject article and then just send in the username/password you want to use to the ServiceProcessInstaller.
2: Try entering the credentials in a different format. If you're currently using ".\user" try writing "computer\user" or vice versa.