Nikita Martyanov
Nikita Martyanov

Reputation: 321

How to add command line interface for windows service

I have a Windows service application. Currently all admin tasks are done through config editing.

I want to add some sort of command line interfaces - and i want it to do through powershell.

And i don't know where should i start - how can i create application interface in this case. How powershell should communicate with service? remote feature also required in this case.

(In the feature there are maybe other admin tools - with GUI or through browser.)

Upvotes: 1

Views: 871

Answers (2)

Matt Davis
Matt Davis

Reputation: 46044

A Windows service initially looks something like this:

using System.ServiceProcess;
internal partial class MyService : ServiceBase
{
    static void Main()
    {
        ServiceBase[] ServicesToRun = new ServiceBase[] { new MyService() };
        ServiceBase.Run( ServicesToRun );
    }
}

What I have done is modify Main() so that I can use it both to launch the service and to process command-line stuff, like this:

using System;
using System.Runtime.InteropServices;
using System.ServiceProcess;
internal partial class MyService : ServiceBase
{
    const int ATTACH_PARENT_PROCESS = -1;

    [DllImport( "kernel32.dll" )]
    static extern bool AttachConsole( int dwProcessId );
    [DllImport( "kernel32.dll" )]
    static extern bool FreeConsole();

    static void Main()
    {
        if ( Environment.UserInteractive ) {
            try {
                // Redirect console output to the parent process.
                AttachConsole( ATTACH_PARENT_PROCESS );

                // Process command line arguments here...
            } catch {
                // Handle exceptions here...
            } finally {
                // Detach from the console.
                FreeConsole();
            }
        } else {
            ServiceBase[] ServicesToRun = new ServiceBase[] { new MyService() };
            ServiceBase.Run( ServicesToRun );
        }
    }
}

When the executable is built, I register it with the operating system as normal (actually, I use a -install command-line option to do that). When the service is started, the UserInteractive flag is false, so the service starts as usual. From a command prompt, though, the UserInteractive flag is true, so the command-line processing takes over.

All you need at this point is have the command-line instance of your executable communicate with the service instance of your executable via some sort of IPC - socket, pipe, shared memory, WCF, etc.

Upvotes: 2

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200303

Expanding a little on L.B's short-ish remark: having a privileged service interact with the user's desktop is not the best of ideas, because doing so may open a route for privilege elevation. Shatter attacks for instance worked that way.

A better way to handle interaction with the user would be to have an unprivileged listener on localhost (e.g. 127.0.0.1:5555) that will display messages submitted via that port, and have the privileged service connect to the listener to send messages to the user.

The code snippet below - while leaving a lot room for improvement - should give you a general idea of how such a listener could look like:

$addr = "127.0.0.1"
$port = 5555

[byte[]]$byte = @(0)
$enc = [System.Text.Encoding]::ASCII
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$socket = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Parse($addr), $port)
$socket.Start()
while ( $true ) {
  $client = $socket.AcceptTcpClient()
  $stream = $client.GetStream()
  [byte[]]$input = @()
  while ( ($i = $stream.Read($byte, 0, 1)) -ne 0 ) { $input += $byte }
  $client.Close()
  [System.Windows.Forms.MessageBox]::Show($enc.GetString($input), "Title")
}

Upvotes: 2

Related Questions