Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ RunAsService is a command line tool that allows you to setup a regular console
This tool requires that .NET Framework 4.5 be already installed on your computer.
If you do not have .NET Framework 4.5 this tool will display a message and not run.

IMPORTANT: Any services you install using this tool will
**IMPORTANT**: Any services you install using this tool will
require that this tool remain on that computer in the same
location in order for those services to continue functioning. Therefore
before installing any services you should make sure this tool is
somewhere where it can remain permanently. If you do end up moving
this tool use the 'fixservices' action to fix the existing services.
(details on how to use 'fixservices' can be found below)

## Usage / Syntax
```
RunAsService
Typing just the name of the tool without specifying any parameters.
Or specifying incorrect paramters will bring you to this help
screen you are currently reading.

RunAsService install [Name] [Display Name] PathToExecutable [Args]
RunAsService install [Name] [Display Name] [Work Dir] PathToExecutable [Args]
Name
The name of the service, if none is specified the name
will default to the name of the executable.

You might choose to give it a different name than
the executable to keep some kind of existing convention,
make it friendlier or make it easier to use with commands like
Expand All @@ -36,7 +37,10 @@ RunAsService install [Name] [Display Name] PathToExecutable [Args]
Generally the display name is longer and more descriptive
than the name and gives the user a better idea of what
the service is and/or does.

Work Dir
This is the working directory, where the executable reads and
writes data. If you omit it, the default working directory is
where the executable is.
PathToExecutable
The location of the application you want to run as a service.

Expand All @@ -63,33 +67,37 @@ RunAsService fixservices
on that computer and at the same location. If you do not call
'fixservices' after moving this tool the existing services
installed using this tool will stop functioning.
```
## EXAMPLES

# EXAMPLES

RunAsService install "c:\my apps\Myapp.exe"

Installs Myapp as a service called 'Myapp'

RunAsService install "c:\my apps\Myapp.exe" arg0 arg1
* `RunAsService install "c:\my apps\Myapp.exe"` <br>
Installs Myapp as a service called 'Myapp'<br>

* `RunAsService install "c:\my apps\Myapp.exe" arg0 arg1` <br>
Installs Myapp as a service called 'Myapp' passes
the arg0 and arg1 to Myapp.exe when it's started.

RunAsService install "My Service" "c:\my apps\Myapp.exe"
* `RunAsService install "c:\my data\path" "c:\my apps\Myapp.exe" arg0 arg1` <br>
Installs Myapp as a service called 'Myapp' passes
the arg0 and arg1 to Myapp.exe when it's started.<br>
Use "c:\my data\path" as the working directory.

* `RunAsService install "My Service" "c:\my apps\Myapp.exe"` <br>
Installs Myapp as a service called "My Service"

RunAsService install "My Service" "My Super Cool Service" "c:\my apps\Myapp.exe"
* `RunAsService install "My Service" "My Super Cool Service" "c:\my apps\Myapp.exe"` <br>
Installs Myapp as a service internally called "My Service"
when using commands like 'net start' and 'net stop' and shows
up as "My Super Cool Service" in Window's services list.

RunAsService uninstall "My Service"
* `RunAsService uninstall "My Service"` <br>
Uninstalls the service.

RunAsService fixservices
* `RunAsService fixservices` <br>
Updates services installed using RunAsService to point to the
new location.

# NOTES
## NOTES

You can use Windows built in commands to start and stop your services. For example you can use:

Expand Down
6 changes: 3 additions & 3 deletions RunAsService.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
# Visual Studio Version 16
VisualStudioVersion = 16.0.32106.194
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunAsService", "RunAsService\RunAsService.csproj", "{0F1E2B46-34C0-460C-A836-EB77F6237B47}"
EndProject
Expand All @@ -16,13 +16,13 @@ Global
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Debug|Any CPU.ActiveCfg = Debug|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Debug|Any CPU.Build.0 = Debug|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Debug|x86.ActiveCfg = Debug|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Debug|x86.Build.0 = Debug|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Release|Any CPU.ActiveCfg = Release|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Release|x86.ActiveCfg = Release|x86
{0F1E2B46-34C0-460C-A836-EB77F6237B47}.Release|x86.Build.0 = Release|x86
{D92B2933-89E5-4090-A1B4-A44D2A83D7B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D92B2933-89E5-4090-A1B4-A44D2A83D7B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D92B2933-89E5-4090-A1B4-A44D2A83D7B2}.Debug|x86.ActiveCfg = Debug|Any CPU
{D92B2933-89E5-4090-A1B4-A44D2A83D7B2}.Debug|x86.Build.0 = Debug|Any CPU
{D92B2933-89E5-4090-A1B4-A44D2A83D7B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
159 changes: 129 additions & 30 deletions RunAsService/Program.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;

using System.Runtime.InteropServices;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.ServiceProcess;


namespace RunAsService {
class RunAsService {
// TODO: Consolidate this and ConsoleAppServiceProxy into one
// TODO: Detect if this executable has moved and update the paths
// TODO: Add command line options: install uninstall fixservices run help
// TODO: Confirm that it's a console application?
// TODO: Working directory as a parameter

#region DLLImport

Expand Down Expand Up @@ -86,6 +86,10 @@ private static int GetExecutablePathIndex(string[] elements) {
return -1;
}

private static bool StringIsPath(string path){
return Path.GetDirectoryName(path) == path; // return it if it's a valid path string.
}

[STAThread]
static void Main(string[] args) {
const string CONST_RunAsServiceAction = "runasservice";
Expand All @@ -101,6 +105,7 @@ static void Main(string[] args) {
#region
{
var executablePathIndex = GetExecutablePathIndex(actionParameters);
var workDir = "";

if(executablePathIndex == -1) {
Console.WriteLine("You must specify the path to a console application executable. Nothing in the parameters you specified pointed to an executable.");
Expand All @@ -109,35 +114,75 @@ static void Main(string[] args) {
return;
}

if(executablePathIndex > 2) { // Valid ExecutablePathIndex's: 0, 1, 2 (leave room for Name or display name)
Console.WriteLine("You included too many parameters before specifying the executable path, perhaps your service name or display name contains spaces and you didn't put quotes around them. Instead of the executable path I found this: " + actionParameters[2]);
if (executablePathIndex > 3) {
// 0 is Service Name, 1 is the Display Name, working dir can be either 1 or 2, ExecutablePathIndex can be 0, 1, 2, 3
Console.WriteLine("You included too many parameters before specifying the executable path, perhaps your service name or display name contains spaces and you didn't put quotes around them. ");
Console.WriteLine();
WriteHelpMessage();
return;
}

var executablePath = actionParameters[executablePathIndex];
var executableArguments = string.Join(" ", ToArray(Skip(actionParameters, executablePathIndex + 1)));
var executablePathAndArguments = executablePath + " " + executableArguments;

string serviceName;
string serviceDisplayName;

if(executablePathIndex == 0) {
serviceName = serviceDisplayName = Path.GetFileNameWithoutExtension(executablePath);
} else if(executablePathIndex == 1) {
serviceName = serviceDisplayName = actionParameters[0];
} else if(executablePathIndex == 2) {
serviceName = actionParameters[0];
serviceDisplayName = actionParameters[1];
} else {
WriteHelpMessage();
return;
var executablePath = actionParameters[executablePathIndex];
// var executableArguments = string.Join(" ", ToArray(Skip(actionParameters, executablePathIndex + 1)));
// Use the raw commandline instead of processed one like above. This will preserve the quotes: ""
string cmdLine = @Environment.CommandLine;
string executableFullPath = Path.GetFullPath(executablePath);
var startArg = cmdLine.IndexOf(executablePath) + executablePath.Length; // Start of raw arguments, leading space included.
var executablePathAndArguments = '"' + executableFullPath + '"' + cmdLine.Substring(startArg);



string serviceName = "";
string serviceDisplayName = "";
switch (executablePathIndex) {
case 0:
// no argument before the executable.
serviceName = serviceDisplayName = Path.GetFileNameWithoutExtension(executablePath);
break;

case 1:
// 1 arg before executable
if (StringIsPath(actionParameters[0])) {
// WorkDir Executable
serviceName = serviceDisplayName = Path.GetFileNameWithoutExtension(executablePath);
workDir = actionParameters[0];
}
else {
// serviceName Executable
serviceName = serviceDisplayName = actionParameters[0];
}
break;

case 2:
// 2 args before the executable
// ServiceName DisplayName/WorkDir Executable
serviceName = actionParameters[0];
if (StringIsPath(actionParameters[1])) {
workDir = actionParameters[1];
serviceDisplayName = serviceName;
}
else {
serviceDisplayName = actionParameters[1];
}
break;

case 3:
// ServiceName DisplayName WorkDir Executable
serviceName = actionParameters[0];
serviceDisplayName = actionParameters[1];
workDir = actionParameters[2];
break;
}

var thisAppPath = GetThisExecutableFullPath();
var executableProxyPath = thisAppPath + " " + CONST_RunAsServiceAction + " ";

if (workDir != ""){
workDir = (workDir[0] == '"') ? workDir : '"' + workDir + '"'; // Add the quotes if it's not there yet.
// Add the workDir as the 1st parameter and the executable and args follows
executablePathAndArguments = workDir + " " + executablePathAndArguments;
}

if(Any(ServiceController.GetServices(), o => o.ServiceName.Trim().ToLower() == serviceName.Trim().ToLower())) {
Console.WriteLine("A service with that name already exists, you must first uninstall the existing service.");
Console.WriteLine("You can use the following command to uninstall this service:");
Expand Down Expand Up @@ -237,21 +282,39 @@ static void Main(string[] args) {
case CONST_RunAsServiceAction: // NOTE, this is an internal action used by the installed services, not called directly by the users
#region
{
if(actionParameters.Length == 0) {
// actionParameters: 0 could be the working dir, console executable is 0 or 1, then follow by args.
if (actionParameters.Length == 0 ) {
WriteHelpMessage();
return;
}

if(!IsExecutablePath(actionParameters[0])) {
Console.WriteLine($"The first parameter after '{CONST_RunAsServiceAction}' must be the path to the console application, the path you specified was not found: {actionParameters[0]}");
string executablePath;

var prompt = $"The frist and second parameter after '{CONST_RunAsServiceAction}' should follow this syntax: [workingPath] fullPathToExecutable, but there was a problem in the paths.";
var workDir = ""; var skipArg = 1;

if (StringIsPath(actionParameters[0]) && actionParameters.Length > 1){
// WorkDir Executable...
workDir = actionParameters[0];
executablePath = actionParameters[1];
skipArg = 2;
}
else {
// Executable
executablePath = actionParameters[0];
}

if (!IsExecutablePath(executablePath)) {
Console.WriteLine(prompt);
WriteHelpMessage();
return;
}
// If empty, get the workDir from executable
workDir = ( workDir=="" ? Path.GetDirectoryName(executablePath) : workDir );

var arguments = ToArray(Skip(actionParameters, skipArg));

var executablePath = actionParameters[0];
var arguments = ToArray(Skip(actionParameters, 1));

ServiceBase.Run(new Service(executablePath, arguments));
ServiceBase.Run(new Service(executablePath, arguments, workDir));
}
break;
#endregion
Expand Down Expand Up @@ -290,7 +353,7 @@ Typing just the name of the tool without specifying any parameters.
Or specifying incorrect paramters will bring you to this help
screen you are currently reading.

{executableName} install [Name] [Display Name] PathToExecutable [Args]
{executableName} install [Name] [Display Name] [PathToWorkDirectory] PathToExecutable [Args]
Name
The name of the service, if none is specified the name will
default to the name of the executable.
Expand All @@ -310,6 +373,17 @@ Generally the display name is longer and more descriptive than
the name and gives the user a better idea of what the service
is and/or does.

PathToWorkDirectory
The working directory you want the application to work on.
In Windows usually the executable cannot write any data to it's
own location. So setting a different working path allows the
console to read/write data in that specified location.

It should be quoted like ""c:\\my working path""

If not specify the working directory, then the program will
assume it to be where the executable is.

PathToExecutable
The location of the application you want to run as a service.

Expand Down Expand Up @@ -342,18 +416,37 @@ when you performed the install action.
{executableName} install ""c:\my apps\Myapp.exe""
Installs Myapp as a service called 'Myapp'

{executableName} install ""c:\my data path"" ""c:\my apps\Myapp.exe""
Installs Myapp as a service called 'Myapp' with working directory
as 'c:\my data path'

{executableName} install ""c:\my apps\Myapp.exe"" arg0 arg1
Installs Myapp as a service called 'Myapp' passes
the arg0 and arg1 to Myapp.exe when it's started.

{executableName} install ""c:\my data path"" ""c:\my apps\Myapp.exe"" arg0 arg1
Installs Myapp as a service called 'Myapp' passes the arg0
and arg1 to Myapp.exe when it's started, using 'c:\my data path'
as the working directory.

{executableName} install ""My Service"" ""c:\my apps\Myapp.exe""
Installs Myapp as a service called ""My Service""

{executableName} install ""My Service"" ""c:\my data path"" ""c:\my apps\Myapp.exe""
Installs Myapp as a service called ""My Service""
using 'c:\my data path' as the working directory.

{executableName} install ""My Service"" ""My Super Cool Service"" ""c:\my apps\Myapp.exe""
Installs Myapp as a service internally called ""My Service""
when using commands like 'net start' and 'net stop' and shows
up as ""My Super Cool Service"" in Window's services list.

{executableName} install ""My Service"" ""My Super Cool Service"" ""c:\my data path"" ""c:\my apps\Myapp.exe""
Installs Myapp as a service internally called ""My Service""
when using commands like 'net start' and 'net stop' and shows
up as ""My Super Cool Service"" in Window's services list.
Its working directory will be 'c:\my data path'

{executableName} uninstall ""My Service""
Uninstalls the service.

Expand All @@ -371,6 +464,9 @@ net stop ""My Service""

Where ""My Service"" would be replaced with the name of your service.

Or you can run the ""Services"" in 'Control Panel->Administrative Tools'
to start/stop the service by name.

CREDITS

This tool was created by Luis Perez on April 2011. For more
Expand Down Expand Up @@ -504,12 +600,14 @@ public AlreadyMarkedForDeletion()

public partial class Service : ServiceBase {
private Process process;
private string workingDirectory;
private string consoleApplicationExecutablePath;
private string[] arguments;

public Service(string consoleApplicationExecutablePath, string[] arguments) {
public Service(string consoleApplicationExecutablePath, string[] arguments, string workDir="") {
this.consoleApplicationExecutablePath = consoleApplicationExecutablePath;
this.arguments = arguments;
this.workingDirectory = workDir;
}

protected override void OnStart(string[] args) {
Expand All @@ -518,6 +616,7 @@ protected override void OnStart(string[] args) {
CreateNoWindow = true,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = workingDirectory,
};

process = Process.Start(oProcessStartInfo);
Expand Down
Loading