WaitForExit for a process on a remote computer - c#

I'm using WMI to start a process on a remote machine. The call to create the process returns immediately and I also get the id of the process on the remote machine.
I would like to wait for the remote process to be completed. One option would be to poll whether a process on the remote machine with the given id still exists.
However, I was wondering whether there is a better way to achieve this, maybe using native WinAPI functions?
Just for additional information, this is the code that I am currently using to start the remote process:
ConnectionOptions connOptions = new ConnectionOptions();
connOptions.Impersonation = ImpersonationLevel.Impersonate;
connOptions.EnablePrivileges = true;
connOptions.Username = domainUserName;
connOptions.Password = password;
ManagementScope manScope = new ManagementScope(String.Format(#"\\{0}\ROOT\CIMV2", host), connOptions);
manScope.Connect();
ObjectGetOptions objectGetOptions = new ObjectGetOptions();
ManagementPath managementPath = new ManagementPath("Win32_Process");
ManagementClass processClass = new ManagementClass(manScope, managementPath, objectGetOptions);
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
inParams["CommandLine"] = commandLine;
ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null);

I don't know how effective this can be, you can use ManagementEventWatcher to watch a query.
Here is something I found on the net.
WqlEventQuery wQuery =
new WqlEventQuery("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'");
using (ManagementEventWatcher wWatcher = new ManagementEventWatcher(scope, wQuery))
{
bool stopped = false;
while (stopped == false)
{
using (ManagementBaseObject MBOobj = wWatcher.WaitForNextEvent())
{
if (((ManagementBaseObject)MBOobj["TargetInstance"])["ProcessID"].ToString() == ProcID)
{
// the process has stopped
stopped = true;
}
}
}
wWatcher.Stop();
}

The native Win32 way of achieving this would be to perform a WaitForSingleObject() on the process handle returned by CreateProcess(), however I don't think this handle is made available to you from WMI.
This article offers another option you could consider - instead of polling the process list and waiting for your process to disappear, it repeatedly queries for process deletion events matching your process ID:
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
objWMIService.Create "notepad.exe", null, null, intProcessID
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _
("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'")
Do Until i = 1
Set objLatestProcess = colMonitoredProcesses.NextEvent
If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
i = 1
End If
Loop
You could also improve on this by using a ManagementEventWatcher object and its WaitForNextEvent method to avoid having to poll for the deletion events.

If the process on the remote machine is your code then you could open up a socket on the calling machine and let the remote machine 'ping' it when it has finished.
If you want to use this method for any remote process you could have a helper app/service on the remote computer that monitors your process and returns the completed ping.

I havent had chance to check this yet,
int pid = (int)managementBaseObject["processId"];
Process remPrc = Process.GetProcessById(pid, RemoteMachine);
remPrc.WaitForExit();

Related

Change service startup type to Delayed (Automatic) in remote machine

I am using the below code to change the service start in a remote server up type to manual/automatic using C#.
public static void ChangeServiceStartupType()
{
string query1 = "select * from Win32_Service where name = 'myservice' ";
string server = "servername";
ConnectionOptions connectoptions = new ConnectionOptions();
connectoptions.Username = #"username";
connectoptions.Password = "password";
ManagementScope scope = new ManagementScope(#"\\" + server + #"\root\cimv2");
scope.Options = connectoptions;
scope.Connect();
ObjectQuery query = new ObjectQuery(query1);
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
foreach (ManagementObject mo in managementObjectSearcher.Get())
{
string name = mo.Properties["Name"].Value.ToString().Trim().ToLower();
string state = mo.Properties["State"].Value.ToString().Trim();
string startmode = mo.Properties["StartMode"].Value.ToString().Trim();
changemode(mo, "Automatic");
}
}
Here is the changemode method
private static void changemode(ManagementObject mo, string startmode)
{
ManagementBaseObject inParams = mo.GetMethodParameters("ChangeStartMode");
inParams["startmode"] = startmode;
ManagementBaseObject outParams = mo.InvokeMethod("ChangeStartMode", inParams, null);
startmode = mo.Properties["StartMode"].Value.ToString().Trim();
}
When I pass the parameters Manual or Automatic in the changemode(object,startmode parameter) the service start up type changes from automatic to manual and vice-versa. However, I am unable to change it to Automatic(Delayed Start).
I tried Auto-Delayed , Delayed-Auto, Automatic (Delayed Start) How do I achieve this?
If there's a way through ManagementObject than I haven't found it, but it IS possible through the registry:
public void SetDalayedAutoStart(string machineName, string serviceName)
{
using (var regKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machineName))
{
using (RegistryKey serviceKey = regKey.OpenSubKey(#"System\CurrentControlSet\Services\" + serviceName, true))
{
serviceKey.SetValue("DelayedAutostart", 1, RegistryValueKind.DWord);
}
}
}
Note 1: This is only relevant for services with the Automatic start type
Note 2: The new state will be visible in the Services control (services.msc) only after a restart (don't ask me why)
Looking at the documentation, it doesn't seem to be possible to make the service start with a delay using the ChangeStartMode Win32 method. Doing what you want is fairly straightforward if you use ServiceInstaller, like so:
myServiceInstaller.StartType = ServiceStartMode.Automatic;
myServiceInstaller.DelayedAutoStart = true;
However I'm guessing that isn't an option, so we will have to dig deeper.
The Service class does have a DelayedAutoStart property, but it is read only. If you want to set it to delayed, you're going to have to mess around with P/Invokes. All information I could find points to ChangeServiceConfig2 and this struct.
Alternatively, you can just execute this command it it will have the same effect. However, it isn't really an answer to your question, just a workaround.
sc.exe config myService start= delayed-auto
Finally, check out this (very) long answer by user Kramii, and this by Peter Kelly. Both of them wrote helper classes to make doing this sort of thing a lot easier. I haven't tested them but they look promising.
Sty's answer lets you set a service to automatic delayed at the time of creation; not post deployment.
There is a command line that can do that
sc \\computername config *servicename* start= delayed-auto
I ran this command line in the remote server using WMI Management Class and it works fine.
More documentation on how to remote start a process here

can't terminate process using WMI but taskkill works

My user is in the Administrators group. I am using the .NET WMI API (System.Management) to kill a process using this code:
var scope = new ManagementScope("\root\cimv2");
scope.Connect();
var query = new ObjectQuery(
string.Format("Select * from Win32_Process Where ProcessID = {0}",
processId));
var searcher = new ManagementObjectSearcher(scope, query);
var coll = searcher.Get().GetEnumerator();
coll.MoveNext();
var mgmtObj = (ManagementObject)coll.Current;
var ret = (uint) mgmtObj.InvokeMethod("Terminate");
// ret == 2 here, meaning "Access Denied"
It's failing to kill the process and returning a 2 (Access Denied). However if I use:
Process.Start("cmd", string.Format("/c \"taskkill /f /pid {0}\"", processId));
the process gets killed. (but if I leave out /f it fails).
Is there any way to terminate the process using WMI?
EDIT: I found the following on
http://msdn.microsoft.com/en-us/library/windows/desktop/aa393907(v=vs.85).aspx:
To terminate a process that you do not own, enable the
SeDebugPrivilege privilege.
The page provides VBScript code but how would I do this in C#?
That is only possible with some API calls which are only available via pinvoke - for complete C# source code see here.

check if a process runs on a remote machine for a certain user

I have to create an executable which checks if a certain process is running for a certain user (a service account) on a remote machine, the input parameters are 3 strings, machine name, user name and process name.
I have the idea to do this using either System.Diagnostics or WMI, just wanted to double check if anybody has another idea like powershell or even a window functionality which could make the task even easier.
since we want to make sure that process is always running on a dedicated server we will configure a scheduled task to execute a small console application which does this check. Not sure if coding it in C# is the best option or am I ignoring a builtin feature of windows server? Thanks!
I'm pretty sure you can accomplish this with tasklist cmd: tasklist /S \\<server> /V > tasklist.txt. this will give you a file you can grep through.
namespace not referenced
using System.Management;
I have ended up by implementing following solution in C#
this retrieves the username without domain name of the user running processName on machineName
public static string GetProcessOwner()
{
try
{
var resultUserName = string.Empty;
ConnectionOptions opt = new ConnectionOptions();
string path = string.Format(#"\\{0}\root\cimv2", machineName);
ManagementScope scope = new ManagementScope(path, opt);
scope.Connect();
var query = new ObjectQuery(string.Format("Select * From Win32_Process Where Name = '{0}'", processName));
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
var processList = searcher.Get();
foreach (ManagementObject obj in processList)
{
string[] argList = new string[] { string.Empty, string.Empty };
int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
if (returnVal == 0)
{
// return DOMAIN\user
//return argList[1] + "\\" + argList[0];
resultUserName = argList[0];
}
}
return resultUserName;
}
catch (Exception exc)
{
Debug.WriteLine(exc.Message);
return string.Empty;
}
}
GetOwner can return empty array for remote comp, so it may not work

stop / start service or close the sql server process / service on a remote computer

I am trying to shutdown and start the sql server on a remote computer (on the same network), i have used this code
ConnectionOptions options = new ConnectionOptions();
options.Username = userName;
options.Password = password;
ManagementScope scope =
new ManagementScope(
string.Format(#"\\{0}\root\cimv2", serverFullName),
options);
scope.Connect();
ObjectQuery query = new ObjectQuery(
string.Format(#"SELECT * FROM Win32_Process WHERE Name='{0}'",
processToTerminate));
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
m.InvokeMethod("Terminate", null);
}
is there another way of doing that ?
how can i start the process (if Terminate close it)?
Thank you
What about using the ServiceController class? (see MSDN)
// just replace the params with your values
ServiceController sc = new ServiceController("SERVICENAME", "MACHINENAME");
if (sc.Status.Equals(ServiceControllerStatus.Running))
sc.Stop();
This should do the trick.
hth
It would seem a lot easier to just use the ServiceController class, that you can give a service name and computer name to, and then call methods such as Start and Stop.
Killing a process and stoping a service are two different things. A service could spawn other processes that will remain lingering. Also, you are effectively pulling the plug on the process. It isn't being given any time to stop gracefully, write everything to disk, etc.
Instead you should use the Win32_Service WMI object to find your service. This has a StartService and StopService method, which will allow you to stop and start it as you need.
Mind you, this WMI object is about services, not processes, so you will have to tweak your code to stop it by the service name, not the process name. Something like this:
ConnectionOptions options = new ConnectionOptions();
options.Username = userName;
options.Password = password;
ManagementScope scope = new ManagementScope(string.Format(#"\\{0}\root\cimv2", serverFullName), options);
scope.Connect();
ObjectQuery query = new ObjectQuery(string.Format(#"SELECT * FROM Win32_Service WHERE Name='{0}'",serviceToStop));
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
m.InvokeMethod("StopService", null);
}
Then later on you can use InvokeMethod on StartService.
you can do some like this to start and stop your sql server
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "net start \"Sql Server (SQLEXPRESS)\"";
process.Start();

WMI + Bitlocker + C# Get encrypted Volumes produces timeout

i want to create a wrapper class for specific WMI functions that affect Bitlocker functionality. The first step is to get all the Bitlocker volumes of a machine so I created a Console Application and did this:
private static ManagementClass management;
private static ManagementObjectCollection Volumes = null;
static void Main(string[] args)
{
ManagementPath path = new ManagementPath();
path.Server = "";
path.NamespacePath = "\\ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption";
path.ClassName = "Win32_EncryptableVolume";
ConnectionOptions options = new ConnectionOptions();
options.Authentication = AuthenticationLevel.PacketPrivacy;
options.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope scope = new ManagementScope(path, options);
ObjectGetOptions getOptions = new ObjectGetOptions();
management = new ManagementClass(scope, path, getOptions);
management.Get();
Volumes = management.GetInstances();
}
When I run this on a non-Bitlocker machine the Volumes Collection gets initialized OK, only that it has a Count of 0 of course. Now I copied the code over to a WinForms App and when I click a button to run this code it steps through OK but when I try to expand the collection during debugging the App hangs and I get a "Function evaluation timed out". It's the same code just in another Application. What could be the reason for this?
Hm. I got a null reference exception if I didn't run it as administrator, but when I ran it as administrator (Win 7 x64, btw), I got four Volumes back.
I just had a similar issue, I will post my code for you hopefully it helps.
ManagementObjectSearcher Encryption = new ManagementObjectSearcher(#"root\cimv2\Security\MicrosoftVolumeEncryption", "SELECT * FROM Win32_EncryptableVolume");
foreach (ManagementObject QueryObj in Encryption.Get())
{
string EncryptionStatus = QueryObj.GetPropertyValue("ProtectionStatus").ToString();
if (EncryptionStatus == "0")
{
EncryptionDialog.Text = "Unencrypted";
}
else if (EncryptionStatus == "1")
{
EncryptionDialog.Text = "Encrypted - SysPrep will not complete";
}
else if (EncryptionStatus == "2")
{
EncryptionDialog.Text = "Cannot Determine Encryption";
}
}
I'm using this to display the status for a sysprep tool i'm creating so the "EncryptionDialog.Text = ..." can be replaced with any other calls you may need. you also need to remember "which caused me issues at least" if you are using visual studio you will need to add a file to your project labeled "Application Manifest File" in the "Add New File" Dialog. The reason for this is that the application will need to be opened in Administrator mode(Just an FYI in case you haven't made it that far)

Categories

Resources