Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

VB.NET Client-Service Skeleton VISTA Ready

3.75/5 (7 votes)
26 Mar 2009CPOL5 min read 30K   378  
A client-server GUI-Service application skeleton. Ready for Vista.

Introduction

During my career in banking companies, I spent a lot of time developing software components that had to start running at machine startup without any user logon. This meant writing a Windows service. Moreover I had to also provide a GUI that should allow users to change some basic configuration settings. 

With the introduction of .NET Framework, to write a system service has become much simpler than before. This becomes possible with the use of classes in the System.ServiceProcess namespace. Until Windows XP, there were no strict security rules to conform to and above all service and user process were freely able to share resources with not many problems. But with the release of the next generation of Windows, some of these tasks become more complex to realize and developers have to spend a lot of effort to make it work again. In this article, I want to share with you the skeleton that I've used to upgrade some services and make them Vista ready like my GekoMirror application.

The Architecture

The architecture is strictly client-server, where the server part runs in a Windows service and the client is a window form that allow the users to consume the service and to configure its behavior. First of all, it is important to remember that in the new releases of Windows, the client and the service often run in different security contexts: Windows service runs with “Local System” or “Network Service” permissions, and the GUI runs using the logged users security context. I suggest you to read about the new features introduced with the release of Windows VISTA like the User Access Control (UAC). You can find a useful document at this link. You will also be interested in how Windows restricts access to core system resources (folders, registry, system objects). Our service must be patched in order to satisfy these new requirements.

Implementation

The communication between GUI and the service is built using a named-pipe, the information exchange is text based with a “command line” syntax: The client sends string messages into the pipe with the format “command param1 param2 …” and waits for a response from the server. Data and configuration persistence are implemented making use of files and the system registry.

Guidelines Vista Ready

Let’s assume that our service is running as local system and the GUI as logged user. Then the service can access with read and write permission the %programfiles% folder and the LocalMachine registry hive, differently the GUI, that runs with a low level security token, cannot. So, every time the GUI needs to make some permanent changes to the configuration, it has to request the service to do the work for it. Moreover when service creates the named pipe where the client sends commands into, the default operating system behavior is to grant only the read access to non administrator users. Then it is necessary to explicitly grant “standard users” to write into the pipe.

In general the guidelines are:

On Server Side

  • Do all the work needed to create, modify and delete files or folders in the core system folders (%systemroot%, %programfiles%).
  • Do all the work needed to create, modify and delete registry hive or values.
  • Grant explicitly to the standard users the right permissions to the system objects. (pipe, timers, mutex, …).

On Client Side

  • Ask the service to do all those tasks that it cannot do.
  • Ask the service to return all the configuration settings that the client needs.

Applying these “little” corrections, you can patch your service/client application to work with the next generation of Windows.

Source Code

The source code is broken into three parts:

  1. A common part that is used to work with pipe: “Interop” class
  2. A server part that manages commands execution, the client requests and service commands: “CommandEngine” and “Server” classes
  3. A client part that is a window form: “frmClient” class

Points of Interest

Service Threading

One interesting point is the use of threading to answer system commands. When system calls the “OnStart” subroutine, a new thread is created and the thread function will stay “alive” with an infinite loop until it will be killed. Otherwise when a stop request is made, all active client connections are terminated and both the working thread and the thread that listens for new client connection stop.

VB.NET
Public Class MyService

    Private ps As Server 'Server Class
    Private st As Thread 'Server thread

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Add code here to start your service. This method should set things
        ' in motion so your service can do its work.

        Try
            'Create a new server class instance
            ps = New Server()

            'Add an handler for message received by server
            AddHandler ps.MessageReceived, AddressOf ReceiveMessage

            'Start the server working function in a separate thread
            st = New Thread(AddressOf ps.Start)
            st.Start()

        Catch ex As Exception
            EventLog.WriteEntry("MyService", _
"An error occurred creating server class." & vbCrLf & ex.Message, _
EventLogEntryType.Error)
            Throw ex

        End Try

    End Sub
    Protected Overrides Sub OnStop()
        ' Add code here to perform any tear-down necessary to stop your service.
        If st.ThreadState <> Threading.ThreadState.Unstarted Then
            'Request the server to shutdown
            ps.Stop()
            Try
                'Wait 10 seconds for server shutting down
                st.Join(10000)
                EventLog.WriteEntry("MyService", _
"Server thread is terminated successfully.", _
EventLogEntryType.Information)

            Catch ex As Exception
                'if the server has trouble shutting down, abort the thread
                st.Abort()
                EventLog.WriteEntry("MyService", _
"An error occurred terminating server thread." & vbCrLf & ex.Message, _
EventLogEntryType.Error)

            End Try
        End If

    End Sub
[…]
End Class

Class Server

    Public Sub Start()
        'start the listening thread
        Me._listenThread = New Thread(New ThreadStart(AddressOf ListenForClients))
        Me._listenThread.Start()
    End Sub

    Public Sub [Stop]()
	  'Terminate all active client connections
        For Each c As ClientInfo In Me._clients
            Interop.DisconnectNamedPipe(c.handle)
            c.handle.Close()
        Next
	  'close the last handle
        Interop.DisconnectNamedPipe(Me._new_clientHandle)
        Me._new_clientHandle.Close()
	  'terminate the listening thread
        Me._listenThread.Abort()
    End Sub
[…]
End Class 

Pipe Security

Another point of interest is creating the pipe and how write permissions are granted to standard users. To create a named pipe with a modified security attribute, I used the “Security Descriptor String” notation and created a security descriptor with the function ConvertStringSecurityDescriptorToSecurityDescriptor().

VB.NET
Dim sd As IntPtr
Dim sds As String
Dim sdl As Integer
 sds = "D:(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;BA)(A;;FA;;;BU)"
rc = Interop.ConvertStringSecurityDescriptorToSecurityDescriptor(sds, 1, sd, sdl)
If Not rc Then
    EventLog.WriteEntry("MyService", _ 
    "ConvertStringSecurityDescriptorToSecurityDescriptor:" & _
	Marshal.GetHRForLastWin32Error, _
    EventLogEntryType.Error)
     Exit Sub
End If
 Dim sa As Interop.SECURITY_ATTRIBUTES
sa = New Interop.SECURITY_ATTRIBUTES
sa.nLength = Marshal.SizeOf(GetType(Interop.SECURITY_ATTRIBUTES))
sa.bInheritHandle = 1
sa.lpSecurityDescriptor = sd
 '
'Creates an instance of a named pipe with the security 
'attribute and returns a handle for subsequent pipe operations
'
Me._new_clientHandle = Interop.CreateNamedPipe( _
    Interop.PIPE_NAME, _
    Interop.DUPLEX Or Interop.FILE_FLAG_OVERLAPPED, _
    0, _
    255, _
    Interop.BUFFER_SIZE, _
    Interop.BUFFER_SIZE, _
    0, _
    sa)
If (Me._new_clientHandle.IsInvalid) Then
    Exit Sub
End If

Command Parsing & Executions

As mentioned before, client/sever message exchange has a “command line” syntax. The client sends a message to the server in the format “command argument1 argument2 …” and waits for a not empty response. Pay attention to return something also after an exception, otherwise client process may hang.

VB.NET
Public Class CommandEngine

    Public Class Commands
        Public Const CMD_ADD = "add"
        Public Const CMD_MUL = "mul"
        Public Const CMD_DIV = "div"
        Public Const CMD_SUB = "sub"
    End Class

    Public Const CMD_ERROR = "ERROR."
    Public Const CMD_NOTFOUND = "NOT FOUND."

    Public Shared Function ExecuteCommand(ByVal cmd As String) As String

        Dim r As String
        Dim x As Double
        Dim y As Double

        r = String.Empty
        x = 0
        y = 0

        Dim args As String()
        args = cmd.ToLower.Split(" ")
        If args.Length <> 3 Then
            Return CMD_ERROR
        End If
        x = args(1)
        y = args(2)

        If args(0) = Commands.CMD_ADD Then
            Try
                r = (x + y).ToString
            Catch ex As Exception
                Return CMD_ERROR
            End Try

        ElseIf args(0) = Commands.CMD_SUB Then
            Try
                r = (x - y).ToString
            Catch ex As Exception
                Return CMD_ERROR
            End Try

        ElseIf args(0) = Commands.CMD_MUL Then
            Try
                r = (x * y).ToString
            Catch ex As Exception
                Return CMD_ERROR
            End Try

        ElseIf args(0) = Commands.CMD_DIV Then
            Try
                r = (x / y).ToString
            Catch ex As Exception
                Return CMD_ERROR
            End Try

        Else
            Return CMD_NOTFOUND

        End If

        Return r

    End Function

End Class

Running the Sample

The sample consists of a service executable Myservice.exe and a GUI client application Client.exe. To run the sample, you need to install the service using the “installutil” command. You must run the “Visual Studio command prompt” with administrative privileges and execute the utility in the executables folder of the project:

C:\Temp\VBClient-Service\MyService\bin\Debug> Installutil MyService.exe

Conclusion

Windows VISTA represents a big step forward in security management, but many efforts are required to patch some backward compatibility issues. Have a good upgrade.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)