Installing C# service with ManagedInstallerClass - c#

I have been trying with no avail to get this service to install.
I am currently using InnoSetup since the Visual Studio installer just didn't entirely make sense to me, to be honest (It is also 1am. D:)
I took some of the code from this thread: Inno Setup for Windows service?
And everyone there says it worked perfectly for them, but they don't entirely explain what they did or where they put that code. Was it a console application? Where?
So, I stuck it where I thought it might have supposed to go. When you add an installer class to a service, a 'Program.cs' class gets created, so that is where I put it.
Here is my 'Program.cs':
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Configuration.Install;
using System.Reflection;
namespace Installer
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
Console.WriteLine("MASDjhd");
string parameter = string.Concat(args);
switch (parameter)
{
case "--install":
ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });
break;
case "--uninstall":
ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });
break;
}
}
}
}
Here is my InnoScript:
[Setup]
AppName=MachineVerification
AppVersion=1.0
DefaultDirName={pf}\MachineVerification
DefaultGroupName=MachineVerification
UninstallDisplayIcon={app}\MachineVerification.exe
Compression=lzma2
SolidCompression=yes
[Files]
Source: "Installer.exe"; DestDir: "{app}"
[Run]
Filename:"{app}\Installer.exe"; Parameters: "--install"
[UninstallRun]
Filename: "{app}\Installer.exe"; Parameters: "--uninstall"
Help? D:

Found my answer here: Self install windows service in .NET c#
For those you who want to follow the link, the solution is to add:
var processInstaller = new ServiceProcessInstaller();
var serviceInstaller = new ServiceInstaller();
//set the privileges
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.DisplayName = "MachineVerification";
serviceInstaller.StartType = ServiceStartMode.Automatic;
//must be the same as what was set in Program's constructor
serviceInstaller.ServiceName = "MachineVerification";
this.Installers.Add(processInstaller);
this.Installers.Add(serviceInstaller);
to the constructor of your install class in your service.

Related

Wixsharp how to reference custom action project

I currently have two projects in my solution, a deployment project which builds the msi and another project which contains my custom actions. I am having trouble referencing my custom actions the same two errros keep appearing:
..\WixSharp Setup\bin\Debug\WixSharpSetup.exe" "/MSBUILD:WixSharp Setup" "/WIXBIN:"" exited with code -532462766. WixSharp Setup ..\WixSharp Setup\packages\WixSharp.1.9.2\build\WixSharp.targets 6
No CA or UI entry points found in module: ..\WixSharp Setup\WixSharp Setup\WixSharpSetup.exe WixSharp Setup ..\WixSharp Setup\WixSharp Setup\EXEC
Deployment project
using System;
using System.Windows.Forms;
using Deploy.CustomAction;
using WixSharp;
using WixSharp.Forms;
namespace WixSharp_Setup
{
class Program
{
static void Main()
{
var project = new ManagedProject("MyProduct",
new Dir(#"%ProgramFiles%\My Company\My Product",
new File("Program.cs")),
new ManagedAction(SearchAPIActions.SearchAPIInstall));
project.GUID = new Guid("6fe30b47-2577-43ad-9095-1861ba25889b");
project.ManagedUI = ManagedUI.Default; //all standard UI dialogs
project.BuildMsi();
}
CustomAction project
public class SearchAPIActions
{
[CustomAction]
public static ActionResult SearchAPIInstall(Session session)
{
session.Log("Begin CustomAction1");
return ActionResult.Success;
}
In case anyone is interested i found the solution to my problem, as the Custom action was compiling to a .dll you need to give a direct reference to it when you declare a managedAction.
new ManagedAction(CustomActions.IISReset, #"Your full Path\Customs.dll"));

Creating running and installing windows services - How to appropriately send a control request to the SCM

I've been struggling with this windows service now for almost two weeks, I have scoured the internet for a resolution and in the process I have learned a lot except that I have not been able to resolve my issue.
I can't seem to find the right way to compose and run a service. There are some articles and opinions on this question even on SO but most of the questions on SO don't even have an acceptable answer, I'm hoping my question will be better accepted by the community so we can settle this windows service issue once and for all.
First of all I have set my configuration mode to debug on x86 (Internal reason for this). I have an installer class as follows:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;
using System.Threading.Tasks;
namespace Practique
{
[RunInstaller(true)]
public partial class Installer1 : System.Configuration.Install.Installer
{
public Installer1()
{
InitializeComponent();
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();
//# Service Account Information
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Password = null;
//# Service Information
serviceInstaller.DisplayName = "Practique";
serviceInstaller.StartType = ServiceStartMode.Manual;
//# This must be identical to the WindowsService.ServiceBase name
//# set in the constructor of WindowsService.cs
serviceInstaller.ServiceName = "Practique";
//S.Nsibande - Add service description.
serviceInstaller.Description = "Practique - application is for testing how I should send control messages to the SCM in best practice manner so as not to get stupid errors on start and stop control requests to the Microsoft OS.";
this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
}
}
My entry point into my service application is as follows:
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace Practique
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
#if DEBUG
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]{ new Service1() };
ServiceBase.Run(ServicesToRun);
#endif
}
}
}
And then the logic performed by my service is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace Practique
{
//Service class inheriting from the ServiceBase class
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
public void OnDebug()
{
OnStart(null);
}
//Two required overides... OnStart() and OnStop()
protected override void OnStart(string[] args)
{
EventLog log = new System.Diagnostics.EventLog();
log.Source = "Application";
try
{
System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt");
System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Log.txt");
}
catch (Exception ex)
{
log.WriteEntry(ex.Message + ".Stack trace - " + ex.StackTrace);
if(ex.InnerException != null)
{
log.WriteEntry(ex.InnerException.Message);
}
}
}
protected override void OnStop()
{
System.IO.File.Delete(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt");
}
private void ServiceStatus()
{
// Toggle the Practique service -
// If it is started (running, paused, etc), stop the service.
// If it is stopped, start the service.
ServiceController sc = new ServiceController("Practique");
string path = AppDomain.CurrentDomain.BaseDirectory + "Log.txt";
// Open the stream and write to it.
using (FileStream fs = File.OpenWrite(path))
{
Byte[] info =
new UTF8Encoding(true).GetBytes("The Ptractique service status is currently set to " + sc.Status.ToString() + ".");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
if ((sc.Status.Equals(ServiceControllerStatus.Stopped)) || (sc.Status.Equals(ServiceControllerStatus.StopPending)))
{
// Start the service if the current status is stopped.
// Open the stream and write to it.
using (FileStream fs = File.OpenWrite(path))
{
Byte[] info =
new UTF8Encoding(true).GetBytes("Starting the Practique service...");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
sc.Start();
}
else
{
// Stop the service if its status is not set to "Stopped".
// Open the stream and write to it.
using (FileStream fs = File.OpenWrite(path))
{
Byte[] info =
new UTF8Encoding(true).GetBytes("Stopping the Practique service...");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
sc.Stop();
}
// Refresh and display the current service status.
sc.Refresh();
// Open the stream and write to it.
using (FileStream fs = File.OpenWrite(path))
{
Byte[] info =
new UTF8Encoding(true).GetBytes("The Practique service status is now set to " + sc.Status.ToString() + ".");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
}
}
}
I might have made some very stupid basic mistake, but that is all the code on my application. And on debug, it runs just fine, it does what it is expected to do. But once installed successfully using a batch file with the following instructions:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
"C:\Programming\Test\Practique.exe"
Pause
My question which I hope will be answered by someone that has been creating and using windows services successfully for a long time, is why does my service keep causing the following error:
This is the information from event viewer after a successful install:
A service was installed in the system.
Service Name: Practique Service File Name:
"C:\Programming\Test\Practique.exe" Service Type: user mode
service Service Start Type: demand start Service Account:
LocalSystem
Then when I attempt to start the service, I get the following error in event viewer:
A timeout was reached (30000 milliseconds) while waiting for the
Practique service to connect.
And...
The Practique service failed to start due to the following error: The
service did not respond to the start or control request in a timely
fashion.
Then I also get this popup when trying to start the service:
Please tell me if my approach is wrong, have I missed a basic principle here, what have I done wrong or have I done too much. Any assistance is greatly appreciated.
(Edit) - I am compiling in Debug mode, although I have tried release mode just in case there might be a difference, but this did not help.
With the code you have you must compile in RELEASE mode to install your service: the SCM requires a response from your service that it has started before the service starts doing any work: when your service is compiled in DEBUG mode it starts working straight away, so never reports back to the SCM, thus resulting in the error.

How to release COM handle in .NET

I am using the following code under ASP.NET 4.0 framework to obtain the version of MSI file from a web app:
string strVersion = "";
try
{
Type InstallerType;
WindowsInstaller.Installer installer;
InstallerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
installer = (WindowsInstaller.Installer)Activator.CreateInstance(InstallerType);
WindowsInstaller.Database db = installer.OpenDatabase(strMSIFilePath, 0);
WindowsInstaller.View dv = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'");
WindowsInstaller.Record record = null;
dv.Execute(record);
record = dv.Fetch();
strVersion = record.get_StringData(1).ToString();
dv.Close();
//db.Commit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dv);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(db);
}
catch
{
//Failed
strVersion = "";
}
It works fine except that when the code finishes running it holds an internal MSI file handle so when I try to move or rename the MSI file I get the error that the file is still in use. This continues until I actually navigate away from the ASPX page that calls the method above.
My question is, I obviously didn't close some handle or object in the code above. But what could that be?
PS. I'm testing it in a development IDE from VS2010.
EDIT: Edited the code like it should be after Adriano's suggestion. Thanks!
The COM object has not been released (it should be auto-released when it goes out of scope but in .NET this doesn't work really well). Because it does not implement the IDisposable interface you can't call its Dispose() method and you can't use it inside an using statement. You have to explicitly call Marshal.FinalReleaseComObject. For example:
try
{
// Your stuffs
}
finally
{
dv.Close();
Marshal.FinalReleaseComObject(dv);
Marshal.FinalReleaseComObject(db);
}
Moreover note that you do not really need a call to the Commit() method because you didn't make any change but just a query.
FWIW, you should be using Windows Installer XML (WiX) Deployment Tools Foundation (DTF). It's an FOSS project from Microsoft that can be found on CodePlex. It has MSI interop libraries with classes that are very similar to the COM classes but implement IDisosable and use P/Invoke instead of COM behind the scenes. There is even support for Linq to MSI if you want. And the full source code is available.
DTF is the gold standard for MSI interop in a .NET world. Here are two examples:
using System;
using System.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Linq;
namespace ConsoleApplication3
{
class Program
{
const string DATABASE_PATH = #"C:\FOO..MSI";
const string SQL_SELECT_PRODUCTVERSION = "SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'";
static void Main(string[] args)
{
using (Database database = new Database(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
{
Console.WriteLine(database.ExecuteScalar(SQL_SELECT_PRODUCTVERSION).ToString());
}
using (QDatabase database = new QDatabase(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
{
var results = from property in database.Properties where property.Property == "ProductVersion" select property.Value;
Console.WriteLine(results.AsEnumerable<string>().First());
}
}
}
}
try to Dispose the Objects.
dv.Dispose();
db.Dispose();

Create shortcuts programmatically from C# and set "Run as administrator" property

I already know how to create shortcuts programmatically from my C# applications using IWshRuntimeLibrary and WshShellClass. Or I could use IShellLink.
Now, if the user's PC is running Windows Vista or Windows 7, I would like to be able to set the "Run as administrator" property of that shortcut programmactically as well.
Is that possible? If so, how?
This example is in PowerShell, but is uses the same objects and classes as C#.
Use the following code to get the byte number to activate:
# Find the missing admin byte (use this code, when changing the link):
$adminon = [System.IO.File]::ReadAllBytes($shortCutLocation)
$adminoff = [System.IO.File]::ReadAllBytes($shortCutLocation)
for ($i = 0; $i -lt $adminon.Count; $i++) {
if ($adminon[$i] -ne $adminoff[$i]) {
Write-Host Location: $i Value: $($adminon[$i])
}
}
I got byte number 21 and its value was 34.
So this is the script I user:
# Turning on the byte of "Run as Admin"
$lnkBytes = [System.IO.File]::ReadAllBytes($shortCutLocation)
$lnkBytes[21] = 34
[System.IO.File]::WriteAllBytes($shortCutLocation, $lnkBytes)
While Doug's answer is the correct solution to this problem, it is not the answer to this specific question...
To set that property on a .lnk, you need to use the IShellLinkDataList COM interface. The great Raymond Chen has c++ sample code on his blog for this
With this method you can create a shortcut that its “Run as administrator” property is set:
first, you need to add a reference to the "Windows Script Host Object Model" library, it is a COM library, so in the project, right-click on the reference go to the COM section, and add the library.
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using IWshRuntimeLibrary;
class Solution
{
static void CreateShortcut(string shortcutPath, string sourcePath, bool runAsAdmin, params string[] args)
{
var shortcut = new IWshShell_Class().CreateShortcut(shortcutPath) as IWshShortcut;
shortcut.TargetPath = System.IO.Path.GetFullPath(sourcePath);
shortcut.Arguments = "\"" + string.Join("\" \"", args) + "\"";
shortcut.Save();
if (runAsAdmin)
using (var fs = new FileStream(shortcutPath, FileMode.Open, FileAccess.ReadWrite))
{
fs.Seek(21, SeekOrigin.Begin);
fs.WriteByte(0x22);
}
}
static void Main(string[] args)
{
CreateShortcut(Directory.GetCurrentDirectory() + "\\" + "shortcutName" + ".lnk", #"C:\...... path to file ... .exe", true);
}
}
Credit for run as admin section belongs to here
You will need to create a manifest file for your application in order to get it to request run as an administrator privileges. Here is a nice tutorial you can follow.
Enjoy!

Restrict plugin access to file system and network via appdomain

I asked a while ago how to restrict plugins access ( I want to prevent them from writing to the disk or network ) and i was told to use AppDomain. I have searched and tried and failed on how to get this working.
Can anyone provide some information so i can get started, simply put make a AppDomain that does not allows writing to the file or network.
For .net framework 4.0, please follow the following code from this MSDN article.
The following example implements the procedure in the previous section. In the example, a project named Sandboxer in a Visual Studio solution also contains a project named UntrustedCode, which implements the class UntrustedClass. This scenario assumes that you have downloaded a library assembly containing a method that is expected to return true or false to indicate whether the number you provided is a Fibonacci number. Instead, the method attempts to read a file from your computer. The following example shows the untrusted code.
using System;
using System.IO;
namespace UntrustedCode
{
public class UntrustedClass
{
// Pretend to be a method checking if a number is a Fibonacci
// but which actually attempts to read a file.
public static bool IsFibonacci(int number)
{
File.ReadAllText("C:\\Temp\\file.txt");
return false;
}
}
}
The following example shows the Sandboxer application code that executes the untrusted code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Remoting;
//The Sandboxer class needs to derive from MarshalByRefObject so that we can create it in another
// AppDomain and refer to it from the default AppDomain.
class Sandboxer : MarshalByRefObject
{
const string pathToUntrusted = #"..\..\..\UntrustedCode\bin\Debug";
const string untrustedAssembly = "UntrustedCode";
const string untrustedClass = "UntrustedCode.UntrustedClass";
const string entryPoint = "IsFibonacci";
private static Object[] parameters = { 45 };
static void Main()
{
//Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder
//other than the one in which the sandboxer resides.
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
//Setting the permissions for the AppDomain. We give the permission to execute and to
//read/discover the location where the untrusted code is loaded.
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
//We want the sandboxer assembly's strong name, so that we can add it to the full trust list.
StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
//Now we have everything we need to create the AppDomain, so let's create it.
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
//Use CreateInstanceFrom to load an instance of the Sandboxer class into the
//new AppDomain.
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
//Unwrap the new domain instance into a reference in this domain and use it to execute the
//untrusted code.
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
}
public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
{
//Load the MethodInfo for a method in the new Assembly. This might be a method you know, or
//you can use Assembly.EntryPoint to get to the main function in an executable.
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
//Now invoke the method.
bool retVal = (bool)target.Invoke(null, parameters);
}
catch (Exception ex)
{
// When we print informations from a SecurityException extra information can be printed if we are
//calling it with a full-trust stack.
(new PermissionSet(PermissionState.Unrestricted)).Assert();
Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
CodeAccessPermission.RevertAssert();
Console.ReadLine();
}
}
}
I guess this is what you need, if I understand correctly your point.
System.Security.PermissionSet ps =
new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None);
ps.AddPermission(new System.Security.Permissions.FileIOPermission(System.Security.Permissions.FileIOPermissionAccess.NoAccess, "C:\\"));
System.Security.Policy.PolicyLevel pl = System.Security.Policy.PolicyLevel.CreateAppDomainLevel();
pl.RootCodeGroup.PolicyStatement = new System.Security.Policy.PolicyStatement(ps);
AppDomain.CurrentDomain.SetAppDomainPolicy(pl);
System.Reflection.Assembly myPluginAssembly = AppDomain.CurrentDomain.Load("MyPluginAssembly");
Is this more precisely what you meant?
Notice that you may provide an array of string containg the paths where you don't want the plugin to have access. You may provide if when initializing the new instance of FileIOPermission class.
Let me know if this helps. :-)
If you're using plugins, you might perhaps know about proxies.
While loading your assembly through a proxy, you can specify the security policy level for this particular assembly through the LoadAssembly() method or so, if I remember correctly. In other words, this is done through reflection.
I know my answer isn't that much detailed, but I hope it will give you an idea of where to look for your solution. I shall take an eye out to find further details on the subject so that I may be of better help. =)
Hope you will share your findings when you've done it.

Categories

Resources