Link to home
Start Free TrialLog in
Avatar of Zorac_da_mantis
Zorac_da_mantis

asked on

VB.NET Service unable to Write file to UNC location

Hello all at EE

I have a Class within a VB.NET service that needs to create a WAV file and write some header info into it onto a network share. I also need to have a form to interact with the service. I get the error, "Access denied" when writing the WAV file. This is because the service is running as local system and does not have access to the network share.

I havea dedicated network user that I can use to access the share but how do I do that in code, The class function is below.
A quick response to this would be fantastic I have a very tight timscale (Who doesnt) to finish this project.

Many thanks

  Function processWavFile(ByRef strWavFile As Scripting.File, ByRef strSource As String, ByRef strDestination As String) As Boolean
        On Error GoTo err_Renamed
        Dim sTmp As Object
        Dim tmpRecord As clsRecord
        Dim ts As Scripting.TextStream
        'read text file to get info and read it into a new record
        tmpRecord = New clsRecord

strDestination = "\\server\networkshare"

        tmpRecord.AUDIODESTINATION = strDestination & "\" & UserID & "_" & strWavFile.Name

    ts = strWavFile.OpenAsTextStream(Scripting.IOMode.ForReading, Scripting.Tristate.TristateFalse)
    Dim sWhole As String
    sWhole = ts.Read(strWavFile.Size)
    tmpRecord.BODY = Mid(sWhole, 9)
    ts.Close()
    ts = Nothing
        ''''write the new wav file based on the record
'ERROR OCCURS HERE
        writeNewWav(tmpRecord)

        processWavFile = True
        Exit Function
err_Renamed:
Err.Raise(8002, , "error in WAV creation." & Err.Description)
        processWavFile = False
    End Function
Avatar of broadbent
broadbent
Flag of United Kingdom of Great Britain and Northern Ireland image

I've also been stuck on a similar question, but have had no answer - so I will be very interested in your question
Avatar of Zorac_da_mantis
Zorac_da_mantis

ASKER

Hi I have been looking around frantically to find a solution but nothing as yet, all I can find are referances to the Win32 api WNetAddConnection2 but no reliable VB.NET code to actually make the connection. In the link http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wnet/wnet/wnetaddconnection2.asp I found on a similar question in EE it states that you dont need to actually map a drive letter by leaving the lpLocalName attribute as NULL.

Does anyone have any knowledge or know how to implement this in VB.NET??

Many thanks
i think you shold try to give permissions to asp.net user.
Hi Thanks for the comment, I am not using ASP.NET and the process is being run as a local system account, this will be a development for a firm wide orgainisation so unfortunately granting each local system on their network rights to the resource is not the answer.
Impersonation has come up as a possibility
What do you mean by Impersonation, broadbent??
I've only just started looking at this, but I was about to start looking at
http://www.pscode.com/vb/scripts/ShowCode.asp?txtCodeId=963&lngWId=10
this is basically permissions probelem, can you create file manually? ..i mean not from within the service if so then try to give proper permissions to this user i mean write permission to that folder.
Hi

You are right, the user in this case in the Computers LocalSystem account, which is why I need to log on to the share within my application as a different user,

All I want to know is how to make a connection to the share as a user in code, I would imagine something like thi:

Sub connectToShare( ByVal UserID as string, ByVal Password As String, ByVal Domain As String)
'<<< What code goes Here>>>
End Sub
The problem I had was finding the user's password, assuming the app is going to be used by others.
I think you better need to provide proper logon information to your service. did you get my point?  service should run under some admin user account that has proper priviliages to the shared folder, if you didn't give proper permissions to shared folder try to give those permissions. hope this will work...
I have both User name and password but I dont know how to push it through in code, thanks for that imersonation code I tried it but it didnt seem to work, good idea though I have also come actoss the CreateProcessAsUser
Hi

I cannot provide logon credentials to the service because then I lose the ability to pop up a WIndows Form or have a systray icon. The service then does not interact with the users desktop effecetively.
i'd tested this scenerio from widin my existing service,
i had add code to create text file to a shared folder initially i didnt have any permissions. It gave me exception "Access to the path "path...."is denied..."
then I gave the domain user full permissions over this folder and (currently I'm running my computer wid domain user account)  and it gave me no error and file is created successfully.
Is your Service Logging on as a Domain User?? Right click properties, Log On as??
nope, my service is running under local system account.
That i show I got the error in the first place :(
My Windows Logon account is a domain admin, so I have rights to everywhere on the network.
My Service runs as a Local System account and when it needs to access an Active Directory share (\\server\sharename) I get Access denied. While I can browse the path with no problems
hmmm im not using active directory here.
Do you have any knwledge of how to use the CreateProcessAsUser call?? If I can implement this I can get the service to launch an exe as the top level user that will move the files instead.
I've found this code from msdn hope it will help

Const LOGON32_LOGON_INTERACTIVE = 2
Const LOGON32_PROVIDER_DEFAULT = 0
Const CREATE_DEFAULT_ERROR_MODE = &H4000000

Private Type STARTUPINFO

   cb As Long
   lpReserved As Long ' !!! must be Long for Unicode string
   lpDesktop As Long  ' !!! must be Long for Unicode string
   lpTitle As Long    ' !!! must be Long for Unicode string
   dwX As Long
   dwY As Long
   dwXSize As Long
   dwYSize As Long
   dwXCountChars As Long
   dwYCountChars As Long
   dwFillAttribute As Long
   dwFlags As Long
   wShowWindow As Integer
   cbReserved2 As Integer
   lpReserved2 As Long
   hStdInput As Long
   hStdOutput As Long
   hStdError As Long

End Type

Private Type PROCESS_INFORMATION

   hProcess As Long
   hThread As Long
   dwProcessId As Long
   dwThreadId As Long

End Type

Private Declare Function CreateProcessAsUser Lib "advapi32.dll" _
      Alias "CreateProcessAsUserA" _
      (ByVal hToken As Long, _
      ByVal lpApplicationName As Long, _
      ByVal lpCommandLine As String, _
      ByVal lpProcessAttributes As Long, _
      ByVal lpThreadAttributes As Long, _
      ByVal bInheritHandles As Long, _
      ByVal dwCreationFlags As Long, _
      ByVal lpEnvironment As Long, _
      ByVal lpCurrentDirectory As String, _
      lpStartupInfo As STARTUPINFO, _
      lpProcessInformation As PROCESS_INFORMATION) As Long

Private Declare Function LogonUser Lib "advapi32.dll" Alias "LogonUserA" ( _
      ByVal lpszUsername As String, _
      ByVal lpszDomain As String, _
      ByVal lpszPassword As String, _
      ByVal dwLogonType As Long, _
      ByVal dwLogonProvider As Long, _
      phToken As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" ( _
      ByVal hObject As Long) As Long

Private Sub Command1_Click()
      Dim hToken As Long
      Dim ulResult As Long
      Dim startup As STARTUPINFO
      Dim process As PROCESS_INFORMATION
   
      ulResult = LogonUser("ImpersonatedUsed", "ImpersonatedDomain", _
         "ImpersonatedUserPassword", LOGON32_LOGON_INTERACTIVE, _
            LOGON32_PROVIDER_DEFAULT, hToken)

      If ulResult = 0 Then
         MsgBox "Error in LogonUser:  " & Err.LastDllError
         Exit Sub
      End If

      startup.cb = Len(startup)
      'TODO: Replace 'mail.exe' with the name of the program you wish to start
      ulResult = CreateProcessAsUser(hToken, 0&, "mail.exe", 0&, 0&, _
         False, CREATE_DEFAULT_ERROR_MODE, 0&, "path", startup, process)

      If ulResult = 0 Then
         MsgBox "Error in CreateProcessAsUser:  " & Err.LastDllError
         Exit Sub
      End If
   
      CloseHandle hToken
   
End Sub

Cant use this code in .NET :( its VB6 code im using Visual Studio 2003
Avatar of Bob Learned
Here is a Windows impersonator class:

' Source:
'  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsIdentityClassImpersonateTopic1.asp

Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Security.Permissions

Public Class WindowsImpersonator

  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

  <DllImport("kernel32.dll")> _
  Public 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

  Public Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean

  Public Declare Auto Function DuplicateToken Lib "advapi32.dll" (ByVal ExistingTokenHandle As IntPtr, _
    ByVal SECURITY_IMPERSONATION_LEVEL As Integer, _
    ByRef DuplicateTokenHandle As IntPtr) As Boolean

  Private m_impersonatedUser As WindowsImpersonationContext

  'GetErrorMessage formats and returns an error message
  'corresponding to the input errorCode.
  Private Shared 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 Exception("Failed to format message for error code " + errorCode.ToString() + ". ")
    End If

    Return lpMsgBuf
  End Function     'GetErrorMessage

  Public Sub Impersonate(ByVal domainName As String, ByVal userName As String, ByVal password As String)

    Const LOGON32_PROVIDER_DEFAULT As Integer = 0
    'This parameter causes LogonUser to create a primary token.
    Const LOGON32_LOGON_INTERACTIVE As Integer = 2
    Const SecurityImpersonation As Integer = 2

    Dim tokenHandle As New IntPtr(0)
    Dim dupeTokenHandle As New IntPtr(0)
    Try


      tokenHandle = IntPtr.Zero
      dupeTokenHandle = IntPtr.Zero

      ' Call LogonUser to obtain a handle to an access token.
      Dim returnValue As Boolean = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, tokenHandle)

      If Not returnValue Then
        Dim ret As Integer = Marshal.GetLastWin32Error()
        Throw New Exception(String.Format("Error: [{0}] {1}", ret, GetErrorMessage(ret)))
      End If

      Dim retVal As Boolean = DuplicateToken(tokenHandle, SecurityImpersonation, dupeTokenHandle)
      If Not retVal Then
        CloseHandle(tokenHandle)
        Throw New Exception("Exception thrown in trying to duplicate token.")
      End If

      ' TThe token that is passed to the following constructor must
      ' be a primary token in order to use it for impersonation.
      Dim newId As New WindowsIdentity(dupeTokenHandle)
      m_impersonatedUser = newId.Impersonate()

      ' Free the tokens.
      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
    Catch ex As Exception
      Console.WriteLine(("Exception occurred. " + ex.Message))
    End Try

  End Sub

  Public Sub Undo()
    m_impersonatedUser.Undo()
  End Sub


  Public ReadOnly Property CurrentName() As String
    Get
      Return WindowsIdentity.GetCurrent.Name
    End Get
  End Property

End Class

Bob
Thanks a mil for this Bob, Im not sure if im implementing it correctly though, Heres what I have done:


 Function processWavFile(ByRef strWavFile As Scripting.File, ByRef strSource As String, ByRef strDestination As String, ByRef Field1 As String, ByRef Field2 As String, ByRef Field3 As String, ByRef Field4 As String, ByRef Field5 As String, ByRef Field6 As String, ByRef IMPORTACCOUNT As String) As Boolean
        On Error GoTo err_Renamed
        Dim tmpRecord As clsRecord

'============NEW ADDITION==================================
    '<Declared the class>
        Dim ImpersonateUser As WindowsImpersonator
'========================================================

        Dim ts As Scripting.TextStream
        'read text file to get info and read it into a new record
        tmpRecord = New clsRecord

        tmpRecord.AUDIODESTINATION = strDestination & "\" & UserID & "_" & strWavFile.Name
        tmpRecord.IMPORTACCOUNT = UserID
        tmpRecord.FIELD1 = WorkType


    ts = strWavFile.OpenAsTextStream(Scripting.IOMode.ForReading, Scripting.Tristate.TristateFalse)
    Dim sWhole As String
    sWhole = ts.Read(strWavFile.Size)
    tmpRecord.BODY = Mid(sWhole, 9)
    ts.Close()
    ts = Nothing

        ''''write the new wav file based on the record
'==========NEW ADDITION==================================
      '<Called the impersonate Function>
        ImpersonateUser.Impersonate("domainname", "MyUserID", "MyPasword")
'======================================================

        writeNewWav(tmpRecord)
        processWavFile = True
        Exit Function
err_Renamed:
Err.Raise(8002, , "error in WAV creation." & Err.Description)
        processWavFile = False
    End Function


I placed the code you sent me in its own class file and all seems to work but when I call the Impersonate call I get the following error in my Log

Object reference not set to an instance of an object.

Please excuse my inexperiance but where am I going wrong??

ASKER CERTIFIED SOLUTION
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
THANK YOU THANK YOU THANK YOU

That works like a charm now.