I've looked and couldn't find what should be a simple question:
How can a Windows Service determine the ServiceName for which it was started?
I know the installation can hack at the registry and add a command line argument, but logically that seems like it should be unnecessary, hence this question.
I'm hoping to run multiple copies of a single binary more cleanly than the registry hack.
Edit:
This is written in C#. My apps Main() entry point does different things, depending on
command line arguments:
Install or Uninstall the service. The command line can provide a non-default
ServiceName and can change the number of worker threads.
Run as a command-line executable (for debugging),
Run as a "Windows Service". Here, it creates an instance of my ServiceBase-derived
class, then calls System.ServiceProcess.ServiceBase.Run(instance);
Currently, the installation step appends the service name and thread count to the ImagePath in the registry so the app can determine it's ServiceName.
From: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024
Here is a WMI solution. Overriding the ServiceBase.ServiceMainCallback() might also work, but this seems to work for me...
protected String GetServiceName()
{
// Calling System.ServiceProcess.ServiceBase::ServiceNamea allways returns
// an empty string,
// see https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024
// So we have to do some more work to find out our service name, this only works if
// the process contains a single service, if there are more than one services hosted
// in the process you will have to do something else
int processId = System.Diagnostics.Process.GetCurrentProcess().Id;
String query = "SELECT * FROM Win32_Service where ProcessId = " + processId;
System.Management.ManagementObjectSearcher searcher =
new System.Management.ManagementObjectSearcher(query);
foreach (System.Management.ManagementObject queryObj in searcher.Get()) {
return queryObj["Name"].ToString();
}
throw new Exception("Can not get the ServiceName");
}
ServiceBase.ServiceName property gives the compile-time name of service. If you specify a different name when installing the service, then ServiceName attribute will not give correct name. So, I had to use below code to obtain the service name of my service.
It's an alternative (without using LINQ) to NVRAM's method:
/**
* Returns the service name of currently running windows service.
*/
static String getServiceName()
{
ServiceController[] scServices;
scServices = ServiceController.GetServices();
// Display the list of services currently running on this computer.
int my_pid = System.Diagnostics.Process.GetCurrentProcess().Id;
foreach (ServiceController scTemp in scServices)
{
// Write the service name and the display name
// for each running service.
// Query WMI for additional information about this service.
// Display the start name (LocalSytem, etc) and the service
// description.
ManagementObject wmiService;
wmiService = new ManagementObject("Win32_Service.Name='" + scTemp.ServiceName + "'");
wmiService.Get();
int id = Convert.ToInt32(wmiService["ProcessId"]);
if (id == my_pid)
{
return scTemp.ServiceName;
#if IS_CONSOLE
Console.WriteLine();
Console.WriteLine(" Service : {0}", scTemp.ServiceName);
Console.WriteLine(" Display name: {0}", scTemp.DisplayName);
Console.WriteLine(" Start name: {0}", wmiService["StartName"]);
Console.WriteLine(" Description: {0}", wmiService["Description"]);
Console.WriteLine(" Found.......");
#endif
}
}
return "NotFound";
}
I was incorrectly trying to obtain the name of windows service as first line in main() without first calling ServiceBase.Run(). We must register our executable as service using ServiceBase.Run() before obtaining its name.
Ref.: http://msdn.microsoft.com/en-us/library/hde9d63a.aspx#Y320
Short version with Linq
int processId = System.Diagnostics.Process.GetCurrentProcess().Id;
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Service where ProcessId = " + processId);
ManagementObjectCollection collection = searcher.Get();
var serviceName = (string)collection.Cast<ManagementBaseObject>().First()["Name"];
By searching for a better solution i tried this:
string serviceName = "myDynamicServiceName";
string serviceBin = "path\\to\\Service.exe";
string configFile = "path\\to\\myConfig.xml";
string credentials = "obj= .\\mytestuser password= test";
string scCommand = string.Format( "sc create {0} start= auto binPath= \"\\\"{1}\\\" -ini={2} -sn={3}\" type= own{4}", serviceName, serviceBin, configFile , serviceName ,credentials );
I passed the servicename and an configuration file to the binpath.
The service was installed by using the SC.exe (i don't use the installutil!)
On the service you can get the Commandline-Arguments
protected override void OnStart(string[] args){
string binpath = new System.IO.FileInfo(System.Reflection.Assembly.GetAssembly(this.GetType()).Location).DirectoryName + "\\";
System.IO.StreamWriter sw = new System.IO.StreamWriter( binpath + "test.log");
sw.WriteLine( binpath );
string[] cmdArgs = System.Environment.GetCommandLineArgs();
foreach (string item in cmdArgs) {
sw.WriteLine(item);
}
sw.Flush();
sw.Dispose();
sw = null;
}
I had a chicken-and-egg problem where I needed to know the service location before completing Service.Run() (Service could be part of a client or server installation, installer named them appropriately, and I needed to detect which it was on startup)
I relied on the registry to get me the name.
public String IdentifySelfFromRegistry()
{
String executionPath = Assembly.GetEntryAssembly().Location;
Microsoft.Win32.RegistryKey services = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
#"SYSTEM\CurrentControlSet\services");
if (services != null)
{
foreach(String subkey in services.GetSubKeyNames())
{
if (executionPath.Equals(ServicePathFromServiceKey(services.OpenSubKey(subkey))))
return subkey;
}
}
return String.Empty;
}
protected static String ServicePathFromServiceKey(Microsoft.Win32.RegistryKey serviceKey)
{
if (serviceKey != null)
{
String exec = serviceKey.GetValue(ServicePathEntry) as String;
if (exec != null)
return exec.Trim('\"');
}
return String.Empty;
}
The ServiceMain() entry point that every service executable must implement receives the ServiceName as its first input argument.
If you are writing your service using .NET, the ServiceMain() entry point is implemented by .NET for you. The ServiceName is assigned when the service is installed using the ServiceProcess.ServiceBase.ServiceName property. If you are trying to customize a .NET service to support dynamic ServiceName values, I have no clue how to access the actual ServiceName at runtime.
public static bool IsServiceInstalled(string serviceName)
{
// get list of Windows services
ServiceController[] services = ServiceController.GetServices();
// try to find service name
foreach (ServiceController service in services)
{
if (service.ServiceName == serviceName)
return true;
}
return false;
}
What's wrong with this.ServiceName, if you're inside the service.cs?
i.e.:
protected override void OnStart(string[] args)
{
Logger.Info($"{this.ServiceName} started on {Environment.MachineName}...");
}
Related
How can one get the corresponding process name of the installed programs in Windows (10)? For now, I'm using this
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(uninstallKey))
{
foreach (string skName in rk.GetSubKeyNames())
{
using (RegistryKey sk = rk.OpenSubKey(skName))
{
//returns installed programs
}
}
}
to return the installed software. Despite not every installed program being shown, how can I get the name of the process, like it would be shown in Task Manager, that the program would start if it was started?
I want to make an application blacklist. If an application gets started it compares its process with the blacklist. If the process matches with an entry in the list, the process gets killed.
Use static method GetProcesses of Process class to create component for each running process on the local computer.
You can get their names like this:
var processNames = Process.GetProcesses().Select(x => x.ProcessName).ToList();
More about Process class here:
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=net-6.0
You should consider to use the Windows integrated feature to block applications via the registry. You can create such entries programmatically.
However, you can implement your own, but you must know that you can't prevent applications from starting using your approach. You can only kill it after it was started and after it has allocated resources.
Create your blacklist first: collect all installed application paths and let the user pick the application to blacklist (see CreateInstalledApplicationIndex method).
Use WMI to observe any process starts by registering a corresponding event handler.
In the event handler retrieve the started Process and compare its filename to your blacklisted filenames to identify and handle a forbidden process.
private List<FileInfo> InstallationInfos { get; } = new List<FileInfo>();
private List<FileInfo> BlacklistedExecutables { get; } = new List<FileInfo>();
public void ApplyBlacklist()
{
CreateInstalledApplicationIndex();
WatchProcessStarts();
}
private void CreateInstalledApplicationIndex()
{
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(uninstallKey);
foreach (string subKeyName in registryKey.GetSubKeyNames())
{
using RegistryKey subKey = registryKey.OpenSubKey(subKeyName);
var installationPath = subKey.GetValue("InstallLocation") as string;
if (string.IsNullOrWhiteSpace(installationPath))
{
continue;
}
IEnumerable<FileInfo> fileInfos = Enumerable.Empty<FileInfo>();
try
{
var installationDirectoryInfo = new DirectoryInfo(installationPath);
fileInfos = installationDirectoryInfo.EnumerateFiles("*.exe", new EnumerationOptions());
}
catch (IOException)
{
continue;
}
foreach (FileInfo fileInfo in fileInfos)
{
this.InstallationInfos.Add(fileInfo);
// For demo, all executables are blacklisted.
// TODO::Let user fill Blacklisted collection.
this.BlacklistedExecutables.Add(fileInfo);
}
}
}
private void WatchProcessStarts()
{
WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
watcher.EventArrived += OnProcessStarted;
// Start listening for process start events
watcher.Start();
// Stop listening for process start events
//watcher.Stop();
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
uint startedProcessId = (uint)e.NewEvent["ProcessID"];
// Note: Convert.ToInt32 will throw an OverflowException
// in case uint does not fit into an int.
// You must decide whether to handle this particular exception or to let it crash your application.
// Since it is very very unlikely that a machine runs Int32.MaxValue processes,
// I recommend not to handle this exception.
Process startedProcess = Process.GetProcessById(Convert.ToInt32(startedProcessId));
bool isProcessBlacklisted = this.BlacklistedExecutables
.Select(fileInfo => fileInfo.FullName)
.Contains(startedProcess.MainModule.FileName);
// TODO::Handle blacklisted process e.g., by killing it
if (isProcessBlacklisted)
{
startedProcess.Kill(entireProcessTree: true);
}
}
It is possible that you have to run your application as administrator in order to observe process starts and to kill them. In this case ensure to prompt the user to elevate your application's rights by restarting it with administrator permissions.
I got a solution which looks like this:
First I get all installed programs based on this
public static void LoadInstalledPrograms()
{
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);
foreach (var app in (IKnownFolder)appsFolder)
{
//regular installed programs
if (app.Properties.System.Link.TargetParsingPath.Value != null)
{
AddToInstalledProgramsList(app.Name, app.Properties.System.Link.TargetParsingPath.Value, "reg");
}
//Windows apps/Microsoft store apps
/*else
{
AddToInstalledProgramsList(app.Name, app.Properties.GetProperty("System.AppUserModel.PackageInstallPath").ValueAsObject.ToString(), "win");
}*/
}
}
and then write them to a dictionary which is observed by a BackgroundWorker who kills every process from the list
static Dictionary<String, String> programs = new Dictionary<String, String>();
public static void AddToInstalledProgramsList(string programName, string programPath, string programType)
{
string processName = "";
if (programType == "reg")
{
programPath = programPath.Replace("/", "\\");
processName = programPath.Split("\\").Last();
if (!programs.ContainsKey(programName))
{
programs.Add(programName, processName);
}
else
{
AddDuplicateEntry(programName, processName, 1);
}
}
else if (programType == "win")
{
//...
}
Debug.WriteLine(programName + ": " + processName);
}
If I stumble across problems with this approach I will update this thread.
I'm trying to get a list of processes currently owned by the current user (Environment.UserName). Unfortunately, the Process class doesn't have any way of getting the UserName of the user owning a process.
How do you get the UserName of the user which is the owner of a process using the Process class so I can compare it to Environment.UserName?
If your solution requires a pinvoke, please provide a code example.
Thanks, your answers put me on the proper path. For those who needs a code sample:
public class App
{
public static void Main(string[] Args)
{
Management.ManagementObjectSearcher Processes = new Management.ManagementObjectSearcher("SELECT * FROM Win32_Process");
foreach (Management.ManagementObject Process in Processes.Get()) {
if (Process["ExecutablePath"] != null) {
string ExecutablePath = Process["ExecutablePath"].ToString();
string[] OwnerInfo = new string[2];
Process.InvokeMethod("GetOwner", (object[]) OwnerInfo);
Console.WriteLine(string.Format("{0}: {1}", IO.Path.GetFileName(ExecutablePath), OwnerInfo[0]));
}
}
Console.ReadLine();
}
}
The CodeProject article How To Get Process Owner ID and Current User SID by Warlib describes how to do this using both WMI and using the Win32 API via PInvoke.
The WMI code is much simpler but is slower to execute. Your question doesn't indicate which would be more appropriate for your scenario.
You will have a hard time getting the username without being an administrator on the computer.
None of the methods with WMI, through the OpenProcess or using the WTSEnumerateProcesses will give you the username unless you are an administrator. Trying to enable SeDebugPrivilege etc does not work either. I have still to see a code that works without being the admin.
If anyone know how to get this WITHOUT being an admin on the machine it is being run, please write how to do it, as I have not found out how to enable that level of access to a service user.
You might look at using System.Management (WMI). With that you can query the Win32_Process tree.
here is the MS link labelled "GetOwner Method of the Win32_Process Class"
Props to Andrew Moore for his answer, I'm merely formatting it because it didn't compile in C# 3.5.
private string GetUserName(string procName)
{
string query = "SELECT * FROM Win32_Process WHERE Name = \'" + procName + "\'";
var procs = new System.Management.ManagementObjectSearcher(query);
foreach (System.Management.ManagementObject p in procs.Get())
{
var path = p["ExecutablePath"];
if (path != null)
{
string executablePath = path.ToString();
string[] ownerInfo = new string[2];
p.InvokeMethod("GetOwner", (object[])ownerInfo);
return ownerInfo[0];
}
}
return null;
}
You'll need to add a reference to System.Management.dll for this to work.
Here's what I ended up using. It works in .NET 3.5:
using System.Linq;
using System.Management;
class Program
{
/// <summary>
/// Adapted from https://www.codeproject.com/Articles/14828/How-To-Get-Process-Owner-ID-and-Current-User-SID
/// </summary>
public static void GetProcessOwnerByProcessId(int processId, out string user, out string domain)
{
user = "???";
domain = "???";
var sq = new ObjectQuery("Select * from Win32_Process Where ProcessID = '" + processId + "'");
var searcher = new ManagementObjectSearcher(sq);
if (searcher.Get().Count != 1)
{
return;
}
var process = searcher.Get().Cast<ManagementObject>().First();
var ownerInfo = new string[2];
process.InvokeMethod("GetOwner", ownerInfo);
if (user != null)
{
user = ownerInfo[0];
}
if (domain != null)
{
domain = ownerInfo[1];
}
}
public static void Main()
{
var processId = System.Diagnostics.Process.GetCurrentProcess().Id;
string user;
string domain;
GetProcessOwnerByProcessId(processId, out user, out domain);
System.Console.WriteLine(domain + "\\" + user);
}
}
I am trying to rename a computer name from a C# application.
public class ComputerSystem : IComputerSystem
{
private readonly ManagementObject computerSystemObject;
public ComputerSystem()
{
var computerPath = string.Format("Win32_ComputerSystem.Name='{0}'", Environment.MachineName);
computerSystemObject = new ManagementObject(new ManagementPath(computerPath));
}
public bool Rename(string newComputerName)
{
var result = false;
var renameParameters = computerSystemObject.GetMethodParameters("Rename");
renameParameters["Name"] = newComputerName;
var output = computerSystemObject.InvokeMethod("Rename", renameParameters, null);
if (output != null)
{
var returnValue = (uint)Convert.ChangeType(output.Properties["ReturnValue"].Value, typeof(uint));
result = returnValue == 0;
}
return result;
}
}
The WMI call returns error code 1355.
MSDN doesn't mention much about error codes, what does it mean and how can I fix it?
Error code 1355 means ERROR_NO_SUCH_DOMAIN: "The specified domain either does not exist or could not be contacted.".
The documentation for the Rename method states that the name must contain the domain name. For a non-domain-joined machine, try .\NewName instead of just NewName.
It's very difficult to update the PC name using any external methods due to protection of the system. The best way to do so is to use the Windows own utility of WMIC.exe to rename the PC. Just launch the wmic.exe from C# and pass rename command as argument.
exit code 0
>
public void SetMachineName(string newName)
{
// Create a new process
ProcessStartInfo process = new ProcessStartInfo();
// set name of process to "WMIC.exe"
process.FileName = "WMIC.exe";
// pass rename PC command as argument
process.Arguments = "computersystem where caption='" + System.Environment.MachineName + "' rename " + newName;
// Run the external process & wait for it to finish
using (Process proc = Process.Start(process))
{
proc.WaitForExit();
// print the status of command
Console.WriteLine("Exit code = " + proc.ExitCode);
}
}
I'm developing a WCF service running on IIS and I need to count the messages i = for each private queue in MSMQ. The fastest way seems to be the powershell method.
The benchmark is here:
http://www.codeproject.com/Articles/346575/Message-Queue-Counting-Comparisions)
When debugging on Visual Studio 2012, it works great but when deployed on my local IIS 7.5 server, it returns 0.
Here is the method I'm using:
private int GetPowerShellCount()
{
return GetPowerShellCount(".\\private$\pingpong", Environment.MachineName, "", "");
}
private int GetPowerShellCount(string queuePath, string machine,string username, string password)
{
var path = string.Format(#"\\{0}\root\CIMv2", machine);
ManagementScope scope;
if (string.IsNullOrEmpty(username))
{
scope = new ManagementScope(path);
}
else
{
var options = new ConnectionOptions {Username = username, Password = password};
scope = new ManagementScope(path, options);
}
scope.Connect();
if (queuePath.StartsWith(".\\")) queuePath=queuePath.Replace(".\\",string.Format("{0}\\",machine));
string queryString = String.Format("SELECT * FROM Win32_PerfFormattedData_msmq_MSMQQueue");
var query = new ObjectQuery(queryString);
var searcher = new ManagementObjectSearcher(scope, query);
IEnumerable<int> messageCountEnumerable =
from ManagementObject queue in searcher.Get()
select (int)(UInt64)queue.GetPropertyValue("MessagesInQueue");
//IEnumerable<string> messageCountEnumerable =
// from ManagementObject queue in searcher.Get()
// select (string)queue.GetPropertyValue("Name");
var x = messageCountEnumerable.First();
return x;
}
please note that I'm not using the user/pass params so it's all local (WCF service and MSMQ on the same machine).
Why it's returning 0 when deployed to IIS?
What do you think I should try out?
Your problem might be related to IIS specific behavior.
Have a look at Tom Hollander's blog: MSMQ, WCF and IIS: Getting them to play nice (Part 1)
In general, message queues can be called whatever you want. However
when you are hosting your MSMQ-enabled service in IIS 7 WAS, the queue
name must match the URI of your service's .svc file. In this example
we'll be hosting the service in an application called MsmqService
with an .svc file called MsmqService.svc, so the queue must be
called MsmqService/MsmqService.svc.
Is there a .NET (C#) method or API call that I can use to query if a Windows Service is disabled? The relevant MSDN article is here.
I want to avoid querying the registry directly. Below is some of the code that I am using right now (and it works). However I am looking for something more elegant and less invasive.
const String basepathStr = #"System\CurrentControlSet\services\";
String subKeyStr = basepathStr + servicenameStr;
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(subKeyStr))
{
return (int) key.GetValue("Start");
}
I did find a simliar question but I was hoping for a better answer since the answers are presumably outdated (3 years have passed).
This the most relevant section of the code I decided to use...thanks for the help all!
StartupState state = StartupState.Unknown;
try
{
PermissionSet fullTrust = new PermissionSet(System.Security.Permissions.PermissionState.Unrestricted);
fullTrust.Demand();
string wmiQuery = #"SELECT * FROM Win32_Service WHERE Name='" + servicenameStr + #"'";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery);
ManagementObjectCollection results = searcher.Get();
foreach (ManagementObject service in results)
{
if (service["StartMode"].ToString() == "Disabled")
state = StartupState.Disabled;
else
state = StartupState.Enabled;
}
return state;
}
catch (SecurityException se)
{
return StartupState.Refused;
}
catch (Exception e)
{
return StartupState.Error;
}
Use the ServiceController class to get information about services.
EDIT
Seems one of the things you can't do with the ServiceController is get the startup type. Googling showed the following blog post that has code that uses P/Invoke to get the service startup type: http://peterkellyonline.blogspot.de/2011/04/configuring-windows-service.html
Add a ref to System.Management and the following code will get you the StartMode
string wmiQuery = "SELECT * FROM Win32_Service WHERE Name='YourServiceName'";
var searcher = new ManagementObjectSearcher(wmiQuery);
var results = searcher.Get();
foreach (ManagementObject service in results)
{
Console.WriteLine(service["StartMode"]);
}
ServiceController class doesn't provide this information. You should use WMI. See here for detailed solution
WMI can be another way for querying the status of the windows services
You can use:
using System.ServiceProcess;
And then link the service you want to view the satus by:
// Link by service name
ServiceController TheServiceName = new ServiceController();
TheServiceName.ServiceName = "Spooler";
// Link by display name
ServiceController TheDisplayName = new ServiceController();
TheDisplayName.ServiceName = "Print Spooler";
To check for example the isRunning Status :
if (TheServiceName.Status == ServiceControllerStatus.Running)
MessageBox.Show("The service is running.");