control the Windows Service - c#

We need to control the Windows Service remotely using the .net application. Example: we have 10 windows services on different services. Now I want to create the application(I guess it is also a windows service) to start and stop these service remotely, and also install or uninstall them via command line.
First thing is to collect these services name. Then using a loop or if/else I guess. Before that I want to make sure all services are installed. I don't want to access service manager to install services, I want to implement it in the code.
I got a hint from somewhere but still not sure exactly.
ServiceInstaller.InstallService("\"" + _applicationPath + "\\" + _applicationName + ".exe\" -service", _applicationName, _applicationName, autoInstall, autoRun);
internal static bool InstallService(string svcPath, string svcName, string svcDispName, bool autoStart, bool startNow)
{
#region Constants declaration.
int SC_MANAGER_CREATE_SERVICE = 0x0002;
int SERVICE_WIN32_OWN_PROCESS = 0x00000010;
int SERVICE_DEMAND_START = 0x00000003;
int SERVICE_ERROR_NORMAL = 0x00000001;
int STANDARD_RIGHTS_REQUIRED = 0xF0000;
int SERVICE_QUERY_CONFIG = 0x0001;
int SERVICE_CHANGE_CONFIG = 0x0002;
int SERVICE_QUERY_STATUS = 0x0004;
int SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
int SERVICE_START = 0x0010;
int SERVICE_STOP = 0x0020;
int SERVICE_PAUSE_CONTINUE = 0x0040;
int SERVICE_INTERROGATE = 0x0080;
int SERVICE_USER_DEFINED_CONTROL = 0x0100;
int SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG |
SERVICE_CHANGE_CONFIG |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS |
SERVICE_START |
SERVICE_STOP |
SERVICE_PAUSE_CONTINUE |
SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL);
int SERVICE_AUTO_START = 0x00000002;
#endregion Constants declaration.
try
{
int dwStartType = SERVICE_AUTO_START;
if (autoStart == false) dwStartType = SERVICE_DEMAND_START;
IntPtr sc_handle = OpenSCManager(null, null, SC_MANAGER_CREATE_SERVICE);
if (sc_handle.ToInt32() != 0)
{
IntPtr sv_handle = CreateService(sc_handle, svcName, svcDispName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, dwStartType, SERVICE_ERROR_NORMAL, svcPath, null, 0, null, null, null);
if (sv_handle.ToInt32() == 0)
{
CloseServiceHandle(sc_handle);
return false;
}
else
My questions:
1) How to collect services?
2) How to start/stop the services?
3) Install service code, no clue.

You can use the ServiceController class to remotely control services on another computer provided that your application is running with the necessary rights and nothing like a firewall is blocking the connection.
Get all services on a remote computer:
var services = ServiceController.GetServices(machineName);
To start and stop a specific service on a remote computer you can use LINQ to get a specific service from the list retrieved above:
var services = ServiceController.GetServices(machineName);
var service = services.First(s => s.ServiceName == "MyServiceName");
service.Start();
...
service.Stop();
Another option is to get a specific service on a specific machine:
var service = new ServiceController("MyServiceName", machineName);
To install a new service you have multiple options. If you are writing your own services in .NET you can create an MSI package using WiX. You can also use the ServiceInstaller class with or without InstallUtil.exe or you can install any service even remotely using SC.exe.
However, to install a service remotely you still should place the executable file of the service locally on the remote computer. So installing a service remotely really involves getting the service onto the remote computer and then run some process to perform the actual installation which will create the correct entries in the registry database. You will have to decide if you want to use file shares, WMI, remote PowerShell or perhaps Active Directory to distribute the software.

Maybe using net command from the shell is a way to go? It is able to list, start, stop services etc. You'll feel a little 'dirty' if you use it, but heck, it will work.
How do I restart a service on a remote machine in Windows?
An yes, I would recommend wrapping that under simple WEB service app.
Also to read: Simplest way to restart service on a remote computer

What about ServiceController class?
http://msdn.microsoft.com/library/system.serviceprocess.servicecontroller.aspx
If service isn't installed ServiceController.Status will throw InvalidOperationException
ServiceController.GetServices() will get you a list of services. It can work on remote computers too, but the code must have correct permissions (impersonation, maybe?)
There is also this: http://www.codeproject.com/Articles/4242/Command-Line-Windows-Services-Manager
In Windows InstallUtil.exe can be used to install/uninstall services. But you must use the correct version: there can be more than one, depending on .NET version and system (x64, x32). On your own machine (I don't know about remote machines) the correct path is:
string path = Path.Combine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), "InstallUtil.exe");
You can execute a process on another machine using PsExec or WMI
EDIT:
.bat script to install a service on your own machine (x64 version of installutil installs x64 version of service):
#ECHO OFF
REM The following directory is for .NET 2.0
set DOTNETFX2=%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727
set PATH=%PATH%;%DOTNETFX2%
echo Installing WindowsService...
echo ---------------------------------------------------
InstallUtil /i "service path goes here"
echo ---------------------------------------------------
echo Done.

Related

UWP C# pipe client fails to connect to Win32 C++ pipe server

I am trying to communicate between client UWP C# app and server Win32 C++ app, both apps are in same package and I am using the Win32 app as desktop extension to provide additional functionality to UWP app.
From the docs it is clear that named pipe communication between UWP apps in same package but it isn't clear about UWP and Win32 app in same package.
The pipe created by UWP process with name \\\\.\\pipe\\Local\\PipeName is converted to \\\\.\\pipe\\Sessions\\<SessionId>\\AppContainerNamedObjects\\<AppContainerSid>\\PipeName. I can use this to communicate between UWP as server and Win32 as client. But I can't do the reverse even after I set up the ACLs as done in the official RPC sample. Instead of using custom capability I used DeriveAppContainerSidFromAppContainerName to to derive a SID from package family name.
My Win32 C++ server code looks like this:
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
PSID everyoneSid = NULL;
PSID packageSid = NULL;
EXPLICIT_ACCESS ea[2] = {};
PACL acl = NULL;
SECURITY_DESCRIPTOR pipeSecurityDescriptor = {};
if (DeriveAppContainerSidFromAppContainerName(Package::Current().Id().FamilyName().c_str(), &packageSid) == S_OK &&
// Get the SID that represents 'everyone' (this doesn't include AppContainers)
AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid))
{
// Now create the Access Control List (ACL) for the Security descriptor
// Everyone GENERIC_ALL access
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfAccessPermissions = GENERIC_ALL;
ea[0].grfInheritance = NO_INHERITANCE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName = static_cast<LPWSTR>(everyoneSid);
// Package Family GENERIC_ALL access
ea[1].grfAccessMode = SET_ACCESS;
ea[1].grfAccessPermissions = GENERIC_ALL;
ea[1].grfInheritance = NO_INHERITANCE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
ea[1].Trustee.ptstrName = static_cast<LPWSTR>(packageSid);
if (SetEntriesInAcl(ARRAYSIZE(ea), ea, NULL, &acl) != ERROR_SUCCESS &&
// Initialize an empty security descriptor
InitializeSecurityDescriptor(&pipeSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) &&
// Assign the ACL to the security descriptor
SetSecurityDescriptorDacl(&pipeSecurityDescriptor, TRUE, acl, FALSE))
{
SECURITY_ATTRIBUTES pipeSecurityAttributes{ .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = &pipeSecurityDescriptor, .bInheritHandle = FALSE };
HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Sessions\\<SessionId>\\AppContainerNamedObjects\\<AppContainerSid>\\PipeName}", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, &pipeSecurityAttributes);
if (hPipe)
{
ConnectNamedPipe(hPipe, NULL);
// Do something
}
}
}
if (everyoneSid) FreeSid(everyoneSid);
if (packageSid) FreeSid(packageSid);
if (acl) LocalFree(acl);
My UWP C# client code looks like this:
using (var client = new NamedPipeClientStream(".", "Local\\PipeName", PipeDirection.InOut, PipeOptions.Asynchronous))
{
await client.ConnectAsync();
// Do something
}
When I am trying to connect from client I am getting "Access to the path is denied." error.
I was able to get a version of this working-- a C# UWP app pipe client successfully connecting to a non-UWP C++ pipe server. My setup is very similar to yours, but there are a few differences.
I did not have much luck at all using the C# version of connecting with the client. I was successful with using CreateFileW. You can call this function inside of your C# by using an DllImport.
My pipe names are slightly different. My non-UWP program is what is making my pipe server, so I think I do not need to use the "Local" sub-path. My server pipe path is: \\\\.\\pipe\\Pipe. My client pipe path is: \\.\pipe\\Pipe. I was able to get a connection with these names.
In my own experience with using DeriveAppContainerSidFromAppContainerName, I could not seem to get the correct SID from it. I would get one back, but when I would compare it the SID from "CheckNetIsolation.exe LoopbackExempt -s" in Powershell, it was different. Not sure why that is, but I hard-coded the correct SID and that worked. Having the incorrect SID does result in an access denied error.

Why result from WMI is different in standard mode and administrator user in Windows 7?

I want to protect my application. So I read hard drive serial number and compare. The application has good result in administrator user mode but it has bad result in standard user mode.
I wrote my application with C#. But for reading hard drive serial number I used a dll file that I wrote in Delphi.
hDevice := CreateFile( '\\.\PhysicalDrive0:', GENERIC_READ or GENERIC_WRITE ,
FILE_SHARE_READ or FILE_SHARE_WRITE , nil, OPEN_EXISTING, 0, 0 );
I try using NET so I used WMI class Win32_DiskDrive but this method has bad results in standard user mode too.
private string getserial()
{
string SerialNumber = "";
string dataForSerial = string.Empty;
ManagementObjectSearcher Finder = new ManagementObjectSearcher("Select * from Win32_OperatingSystem");
string Name = "";
foreach (ManagementObject OS in Finder.Get()) Name = OS["Name"].ToString();
// Name = "Microsoft Windows XP Professional|C:\WINDOWS|\Device\Harddisk0\Partition1"
int ind = Name.IndexOf("Harddisk") + 8;
int HardIndex = Convert.ToInt16(Name.Substring(ind, 1));
Finder = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive WHERE Index=" + HardIndex);
foreach (ManagementObject HardDisks in Finder.Get())
foreach (ManagementObject HardDisk in HardDisks.GetRelated("Win32_PhysicalMedia"))
SerialNumber = HardDisk["SerialNumber"].ToString();
// SerialNumber = dataForSerial;
return SerialNumber;
}
In standard user mode:
In case of the Delphi dll it throws an access denied error
In case of the WMI the output is different in standard user vs administrator user.
Note: this problem (NO.2) is only in Windows 7.
Please use the following code when calling CreateFile to access the physical disk. It works without admin rights and allows one to read the drive's properties:
hDisk := CreateFile ('\\.\PHYSICALDRIVE0', 0, FILE_SHARE_WRITE, NIL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_NO_BUFFERING, 0);
Please also take note that the name of the drive passed as the first parameter to CreateFile does not include a colon ":" at the end.
Please see this Link. Results are varying depending on Windows version, on whether the code is run as admin or not, and whether the Win32_PhysicalMedia class is used or the Win32_DiskDrive class. Seems pretty unreliable, you may have to write your own abstraction layer to handle it yourself, as described in these forum posts.
I tried it myself and found I got two different serial numbers depending on admin vs normal and Win32_PhysicalMedia vs Win32_DiskDrive:
VB38bb50ab-0de50c12
and
42563833626230356261302d6564303531632032
Notice that the second string is actually a hex-encoded and byte-reversed version of the first string!

WCF service Process.Start under network service account impersonating as a different user

I have Wcf Service hosted in IIS, Windows Server 2008 R2, using an AppPool .NET 4.0 with NETWORK SERVICE Identity.
My Wcf Service has a method that calls an command EXE using Process.Start.
I need use an different user as credentials for execute command EXE, an domain user account.
I try execute it but it doesn't works for me: it seems not execute the command EXE.
update: process exited, but not execute code
I get errors like:
exit code -1073741502
and eventvwr:
Process Information:
Process ID: 0xc50
Process Name: C:\DeployTools\DeployTools.Commands.Ejecutar.exe
Exit Status: 0xc0000142
The appplication was unable to start correctly (0xC0000142). Click OK
to close the application
Any suggestions?
Code:
StreamReader sr = null;
StreamReader serr = null;
try
{
var psi = new ProcessStartInfo(MY_COMMAND_EXE);
psi.WorkingDirectory = Path.GetDirectoryName(MY_COMMAND_EXE);
psi.Arguments = arguments;
psi.Domain = DOMAIN;
psi.UserName = USER_IN_DOMAIN;
psi.Password = SecureStringHelper.ToSecureString(pwd);
psi.LoadUserProfile = true;
psi.UseShellExecute = false;
psi.ErrorDialog = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
psi.WindowStyle = ProcessWindowStyle.Minimized;
using (Process pr = Process.Start(psi))
{
sr = pr.StandardOutput;
serr = pr.StandardError;
if (!pr.HasExited)
{
pr.WaitForExit(300000);
}
output = pr.StandardOutput.ReadToEnd();
errors = pr.StandardError.ReadToEnd();
exitCode = pr.ExitCode;
return output;
}
}
catch (Exception exc)
{
return "EXCEPCIÓN: " + exc.Message;
}
finally
{
if (sr != null)
{
sr.Close();
sr.Dispose();
sr = null;
}
if (serr != null)
{
serr.Close();
serr.Dispose();
serr = null;
}
}
I had to add a reference to AsproLock.dll and associated code in order to permit the user account to have access to the running resource.
//The following security adjustments are necessary to give the new
//process sufficient permission to run in the service's window station
//and desktop. This uses classes from the AsproLock library also from
//Asprosys.
IntPtr hWinSta = NativeMethods.GetProcessWindowStation();
WindowStationSecurity ws = new WindowStationSecurity(hWinSta,
System.Security.AccessControl.AccessControlSections.Access);
ws.AddAccessRule(new WindowStationAccessRule(userPassDto.Usuario,
WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AcceptChanges();
IntPtr hDesk = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId());
DesktopSecurity ds = new DesktopSecurity(hDesk,
System.Security.AccessControl.AccessControlSections.Access);
ds.AddAccessRule(new DesktopAccessRule(userPassDto.Usuario,
DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AcceptChanges();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetCurrentThreadId();
The Perils and Pitfalls of Launching a Process Under New Credentials by Asprosys
This isn't a common need but it also isn't that rare, so I thought I had better post this step by step guide to troubleshooting the launching of a process under impersonated credentials. This is based on using the Start method of the .Net Process class but it is also applicable to the underlying API calls: CreateProcessWithLogonW and CreateProcessWithTokenW.
Access Denied - The first attempt and an access denied exception right off the bat. This is the most common initial problem and is caused by the fact that the service is running under the LOCAL SYSTEM account. Strangely, the SYSTEM account is the most powerful account on the computer but one of the few things it cannot do is launch a process using CreateProcessWithLogonW which is the API underlying the call to Process.Start. So change your service account to Local Service, it's probably the more appropriate account anyway.
Access Denied Again - Aargh, I thought we solved this. Oops, double check the permissions on the application that you are trying to launch. Remember that the system tries to access the application file as the user account that the process will be running under, not the service account.
Invalid Directory Error - What? All the paths are correct. All the directories are spelled correctly, no invalid characters. This is an incredibly annoying error and is not very consistent. Usually when we run a process we don't bother setting the WorkingDirectory property and just accept the default from the parent process. When starting a process with new credentials you can't do that, you must explicitly set a path for the WorkingDirectory or you'll get a "The directory name is invalid." Win32Exception.
Failure: No Error? - Process.Start handles the creation of the Environment block for the new process for you quite well. So this is a problem only if you are using the underlying API. When calling one of the CreateProcess* APIs it is normal to leave the lpEnvironment parameter as NULL and have the system use the default of copying the block from the parent process. But when launching under new credentials you must create an environment block explicitly, either manually or using CreateEnvironmentBlock. What makes this worse is, if you leave this out the CreateProcess* call will fail but GetLastError will return ERROR_SUCCESS and if you make an error creating your environment block there will be no error but the process may just not run at all.
Application Failed to Initialize Properly - No more exceptions, you've solved all the problems and the process has been launched. Oops again, where is the process? Check the event log (or you may have received an Application Error pop-up). There should be an entry for Application Error that says that your process was the faulting application, either user32.dll or kernel32.dll was the faulting module and the exception was: 0xC0000142. There may be some minor variation in this but basically it is saying that your application could not initialize. The reason for this is that on initialization, before any application code is run, all processes are attached to a Window Station and all threads are attached to a Desktop but the user you are launching under does not have permission to access the Window Station and Desktop in which your process is being launched, ergo it can't initialize. The security descriptors for the Window Station and Desktop must be adjusted to give AllAccess permission to the user the process is being launched under. This is a devil to do directly in .Net, so you might find the security wrapper classes here useful.
No More Errors - Really, no more errors, your process should be running smoothly now. There may be some variations in what you need to do based on who the user is (for instance an administrator will already have the correct permissions in some cases) or what kind of session you are launching in. But following these steps should make your life smooth and easy (well maybe not your whole life).
References:
The Perils and Pitfalls of Launching a Process Under New Credentials
Aspro Lock - Access Control
Code Samples
Creating New Process Under Alternate Credentials (createprocessasuser)
processstart-hangs

How to force Windows to reconnect to network drive

We try to access a directory which is within a network directory but get wrong results (C#/Windows):
var exists = Directory.Exists("Z:\\Sessions\\Data1");
"Z" is the network directory, "Sessions" is a directory where a recording software constantly creates directories (e.g. "Data1") and puts some data in it.
It seems that Windows caches the wrong state about Data1: The method returns false. But when I access the directory via Explorer it's here. When I run the method (Directory.Exists) after accessing the directory with Explorer, it returns true. Of course I can guarantee that the directory actually exists at the first attempt.
What is the reason for this behaviour? What can I do about it?
Edit:
It seems that windows could not connect the network drive to the remote computer. When I try to navigate into the directory with Explorer it automatically tries to connect the drive.
So the question changes:
Is there a way to force windows to try a reconnect via .NET?
Solution:
Reconnecting a disconnected network drive
I'm not sure which version of mpr.dll the solution link above works with, but I am using Win7 and have a slightly different version (although similar). This entry point is:
[DllImport("mpr.dll", SetLastError = true, EntryPoint = "WNetRestoreSingleConnectionW", CharSet = CharSet.Unicode)]
internal static extern int WNetRestoreSingleConnection( IntPtr windowHandle,
[MarshalAs(UnmanagedType.LPWStr)] string localDrive,
[MarshalAs(UnmanagedType.Bool)] bool useUI);
then:
IntPtr hWnd = new IntPtr(0);
int res = WNetRestoreSingleConnection(hWnd, <your drive path>, false);
You'll have to add your own error checking / handling.
I too use a remote drive and it takes a few seconds to connect, so I just wait and works every time. If it does not connect, it will send an email and I will check it.
logger.Info("Create Z: drive ");
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = #"/C net use z: \\" + Tools.servername + #"\SHARE_NAME_HERE /user:USER_NAME_HERE PASSWORD_HERE";
process.StartInfo = startInfo;
process.Start();
logger.Info("Z: drive created ");
// wait get Z:
int xtimestimeout = 5;
while (!Directory.Exists(#"Z:\") & (xtimestimeout > 0))
{
Application.DoEvents();
SetBalloonTip(#"Program", #"connecting... please wait");
ShowBalloon();
logger.Info("Creating Z:... waiting...");
Application.DoEvents();
System.Threading.Thread.Sleep(3000);
xtimestimeout -= 1;
}
// check for sucessfull creation of Z: in server Z:\somedirectory
if (!Directory.Exists(#"Z:\"))
{
SendEmail2("Oh my... help!", "drive Z: not created <<< CHECK!");
logger.Info("Z: email sent because of not created");
}
logger.Info("Z: drive created successfully.");
What is the reason for this behaviour?
Quote from the documentation:
If you do not have at a minimum read-only permission to the directory,
the Exists method will return false.
What can I do about it?
Ensure that you are running your .NET application under an account that has at least read-only permission to access this folder. Notice that if you are writing this in an ASP.NET application this probably won't be the case so depending on which account you configured your web server to execute your application under, take the necessary steps to grant permissions to this account.

wallpaper changing on windows 7

I have a problem with changing wallpaper via code, i have below code as every SO/Codeproject threads talks about on changing wallpaper. (Have not tested on other Win OS so far)
const int SPI_SETDESKWALLPAPER = 20;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDWININICHANGE = 0x02;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SystemParametersInfo(
int uAction, int uParam, string lpvParam, int fuWinIni);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, #"C:\Temp\100_5715.JPG.bmp",
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
The actual problem i have here is that if i make the above code as windows service and install it as a "Local Service" or "Local System", it wont work at all. But if i make the above code as a ConsoleApp, then things work.
I wonder whats the problem? Yes my user account has Admin rights. Also i have Windows 7 Ultimate here.
Thanks :)
Services run in their own session, called session 0 since Vista. It is isolated from the user session for security reasons, services run with a very privileged account. You are changing the desktop of that otherwise invisible session.
You can't use a service.

Categories