We help IT Professionals succeed at work.

Starting a program from a service in VB.NET

Sheldon Livingston
on
I created a service from vb.net 2005...

I would like to start an application... such as calc.exe.

calc.exe opens (I can see it task manager) but it isn't visible unless I change the service to interact with the desktop.

Anyone know any work arounds for this?
Comment
Watch Question

Commented:
This happens in wisndows 2003

NT Services do not have access to the interactive desktop by default.
Windows NT and above have the concept of window stations. Your logon session
has a window station and your service has its own. So they both have a
windows desktop of their own and you can only see yours. If your service
runs as local system you can use service control manager to give that
service access to the interactive desktop. So that might help but only for a
little while I guess because if you connect to that server with remote
desktop your remote session will live in just another window station.

http://bytes.com/topic/c-sharp/answers/543869-windows-service-process-start

http://support.microsoft.com/kb/115825
Top Expert 2010

Commented:
Hi,
I'll see what I can do to help you understand why you don't see calculator on the desktop. The service your running is most likely running under "SYSTEM" (LocalSystem) which means there is no desktop associated so calculator is launched in another session other than the session the user is running. If you look at task manager once more you will notice the calculator is running under the SYSTEM account.
There is some concern with using "interactive" option because this has all changed on Vista and later where you can't run interactive services so this option only works for XP and earlier and should be avoided.
If your service needs to run under SYSTEM then you have to use PInvoke and CreateProcessAsUser or CreateProcessWithToken. You must also be careful because if you don't specify an environment block for these calls there will be problems when the application use windows dialogs. If you know the application doesn't use dialogs like FileOpen, Print, FileSave then you can ignore the environment block. If the application your launching does use them or has GUI than it's best that you create the enviornment block.
 
Sheldon LivingstonConsultant

Author

Commented:
I understand why... just didn't know if there was a work around.
Top Expert 2010
Commented:
<< I understand why... just didn't know if there was a work around. >>
What type of workaround do you mean? What you'll need in order to launch the application onto the user desktop is to get the current users session token. Then you can use CreateProcessAsUser.
I created a class that does this, I'll share it with you (ProcessSession.vb) it will let you launch an application from a service running under (LocalSystem).
Basic example of using (ProcessSession.vb)

Dim ps As New ProcessSession
Try
ps.CreateSessionIdentity(ps.GetActiveConsoleSessionId)
Catch ex As Win32Exception
'// output to event log.
Finally
If Not ps.WindowsIdentitySession.IsSystem Then
ps.Start("notepad.exe")
End If
End Try
 
You may also use the services session notifications to obtain SessionId.

'
' ProcessSession.vb
'



Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.ComponentModel

Public Class ProcessSession
    ' egl1044
    ' XP or later
    ' Class must be used under LocalSystem account only.
    Private Const CREATE_UNICODE_ENVIRONMENT As Integer = &H400

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Private Structure STARTUPINFO
        Dim cb As Integer
        Dim lpReserved As String
        Dim lpDesktop As String
        Dim lpTitle As String
        Dim dwX As Integer
        Dim dwY As Integer
        Dim dwXSize As Integer
        Dim dwXCountChars As Integer
        Dim dwYCountChars As Integer
        Dim dwFillAttribute As Integer
        Dim dwFlags As Integer
        Dim wShowWindow As Short
        Dim cbReserved2 As Short
        Dim lpReserved2 As IntPtr
        Dim hStdInput As IntPtr
        Dim hStdOutput As IntPtr
        Dim hStdError As IntPtr
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Private Structure PROCESS_INFORMATION
        Dim hProcess As IntPtr
        Dim hThread As IntPtr
        Dim dwProcessID As Integer
        Dim dwThreadID As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Private Structure SECURITY_ATTRIBUTES
        Dim Length As Integer
        Dim lpSecurityDescriptor As IntPtr
        Dim bInheritHandle As Boolean
    End Structure

    <DllImport("Advapi32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)> _
    Private Shared Function CreateProcessAsUser(ByVal hToken As IntPtr, ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal bInheritHandle As Integer, ByVal dwCreationFlags As Integer, ByVal lpEnvrionment As IntPtr, ByVal lpCurrentDirectory As String, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Integer
    End Function
    <DllImport("Userenv.dll", CharSet:=CharSet.Auto, ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function CreateEnvironmentBlock(ByRef lpEnvironment As IntPtr, ByVal hToken As IntPtr, ByVal bInherit As Integer) As Integer
    End Function
    <DllImport("Userenv.dll", CharSet:=CharSet.Auto, ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function DestroyEnvironmentBlock(ByVal lpEnvironment As IntPtr) As Integer
    End Function
    <DllImport("Wtsapi32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function WTSQueryUserToken(ByVal SessionId As Integer, ByRef phToken As IntPtr) As Integer
    End Function
    <DllImport("Kernel32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function WTSGetActiveConsoleSessionId() As Integer
    End Function
    <DllImport("Kernel32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function CloseHandle(ByVal hObject As IntPtr) As Integer
    End Function

    Private hSessionToken As IntPtr = IntPtr.Zero           '//     The session token handle.
    Private pEnvBlock As IntPtr = IntPtr.Zero               '//     The session env block pointer
    Private winId As WindowsIdentity = Nothing              '//     The new WindowsIdentity from the session token.

    Private _pid As Integer = (-1)                          '//     The process identifier
    Private _tid As Integer = (-1)                          '//     The thread identifier 
    Private _hProcess As IntPtr = IntPtr.Zero               '//     The process handle.
    Private _hThread As IntPtr = IntPtr.Zero                '//     The thread handle.

    Public Sub CreateSessionIdentity(ByVal sessionId As Integer)
        '   You may specify a session id from a services session change 
        '   notifications or by using GetActiveConsoleSessionId.

        '   Obtain the primary access token of the logged-on user specified by the session ID
        If WTSQueryUserToken(sessionId, hSessionToken) = 0 Then
            Throw New Win32Exception(Marshal.GetLastWin32Error)
        End If

        '   Create WindowsIdentity from the session token.
        winId = New WindowsIdentity(hSessionToken)

        '   Get the enviorment block pointer for the user session.
        If CreateEnvironmentBlock(pEnvBlock, hSessionToken, 0) = 0 Then
            Throw New Win32Exception(Marshal.GetLastWin32Error)
        End If

    End Sub

    Public Function Start(ByVal fileName As String) As Boolean

        Dim sa As SECURITY_ATTRIBUTES = Nothing
        Dim pi As PROCESS_INFORMATION = Nothing
        Dim si As STARTUPINFO = Nothing
        Dim fOk As Boolean = False

        If CreateProcessAsUser(hSessionToken, fileName, Nothing, sa, sa, 0, CREATE_UNICODE_ENVIRONMENT, pEnvBlock, Nothing, si, pi) Then
            '   Assign property information variables.
            _pid = pi.dwProcessID
            _tid = pi.dwThreadID
            _hProcess = pi.hProcess
            _hThread = pi.hThread
            fOk = True
        Else
            fOk = False
        End If
        Return fOk
    End Function

    Public Function GetActiveConsoleSessionId() As Integer
        Return WTSGetActiveConsoleSessionId()
    End Function

    Public ReadOnly Property WindowsIdentitySession() As WindowsIdentity
        Get
            Return winId '   <<   WindowsIdentity
        End Get
    End Property

    Public ReadOnly Property ProcessId() As Integer
        Get
            Return _pid
        End Get
    End Property

    Public ReadOnly Property ThreadId() As Integer
        Get
            Return _tid
        End Get
    End Property

    Public ReadOnly Property ProcessHandle() As IntPtr
        Get
            Return _hProcess
        End Get
    End Property

    Public ReadOnly Property ThreadHandle() As IntPtr
        Get
            Return _hThread
        End Get
    End Property

    Protected Overrides Sub Finalize()
        DestroyEnvironmentBlock(pEnvBlock)
        CloseHandle(hSessionToken)
        CloseHandle(_hProcess)
        CloseHandle(_hThread)
        winId.Dispose()
        winId = Nothing
        MyBase.Finalize()
    End Sub

End Class

Open in new window

Sheldon LivingstonConsultant

Author

Commented:
This is the type of thing that I am looking for...

Thank you