Search code examples
c#wmi

Unable to remotely terminate a process using WMI and C#


I am trying to write a method that will terminate a service on a remote system, by process ID, if it fails to stop using the StopService method. I have tried two different ways of invoking the "Terminate" method on a ManagementObject, and I get two different errors. It's also important to me that I be able to get the return code from the Terminate method.

If I declare a ManagementPath directly to the process I want to terminate, I get the error "System.Management.ManagementException: Invalid object path" at line:

ManagementBaseObject processParams = processObj.InvokeMethod("Terminate", (ManagementBaseObject)null, null);

If I get a ManagementObjectCollection and loop through it looking for the Process ID I want to terminate, I get the error "Invalid parameter" at line:

ManagementBaseObject termParams = currentObj.InvokeMethod("Terminate", (ManagementBaseObject)null, null);

So, in both cases, I get an error when I try to invoke the Terminate method, but the error differs depending on how I arrive at the object (direct path or loop through collection).

I don't think this is related to SeDebugPrivilege, as I believe I'd be getting "access denied" or "insufficient privilege" if it was.

Code if I attempt to directly specify the path to the process:

public int KillServiceWMI(string serviceName, string serverName, string serverUser, string serverDomain, string serverPassword)
{
    try
    {
        ConnectionOptions options = new ConnectionOptions();
        options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
        options.Username = serverDomain + "\\" + serverUser;
        options.Password = serverPassword;

        ManagementScope scope = new ManagementScope("\\\\" + serverName + "\\root\\cimv2", options);
        Console.WriteLine("Connecting to scope");
        scope.Connect();

        Console.WriteLine("Getting ManagementPath");
        ManagementPath servicePath = new ManagementPath("Win32_Service.Name='" + serviceName + "'");
        Console.WriteLine("Getting ManagementObject");
        ManagementObject serviceObj = new ManagementObject(scope, servicePath, new ObjectGetOptions());
        Console.WriteLine("Name of service is " + serviceObj["DisplayName"].ToString());
        Console.WriteLine("Process ID of service is " + serviceObj["ProcessId"].ToString());
        ManagementPath processPath = new ManagementPath("Win32_Process.ProcessId='" + serviceObj["ProcessId"] + "'");
        ManagementObject processObj = new ManagementObject(scope, processPath, new ObjectGetOptions());
        ManagementBaseObject processParams = processObj.InvokeMethod("Terminate", (ManagementBaseObject)null, null);
        int returnCode = System.Convert.ToInt32(processParams.Properties["ReturnValue"].Value);
        return returnCode;
    }
    catch (Exception connectEx)
    {
        Console.WriteLine("Connecting to " + serverName + " caused an exception");
        Console.Write(connectEx);
        return 99;
    }
}

Code if I loop through a collection of processes:

public int KillServiceWMI(string serviceName, string serverName, string serverUser, string serverDomain, string serverPassword)
{
    try
    {
        ConnectionOptions options = new ConnectionOptions();
        options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
        options.Username = serverDomain + "\\" + serverUser;
        options.Password = serverPassword;

        ManagementScope scope = new ManagementScope("\\\\" + serverName + "\\root\\cimv2", options);
        Console.WriteLine("Connecting to scope");
        scope.Connect();

        Console.WriteLine("Getting ManagementPath");
        ManagementPath servicePath = new ManagementPath("Win32_Service.Name='" + serviceName + "'");
        Console.WriteLine("Getting ManagementObject");
        ManagementObject serviceObj = new ManagementObject(scope, servicePath, new ObjectGetOptions());
        Console.WriteLine("Name of service is " + serviceObj["DisplayName"].ToString());
        Console.WriteLine("Process ID of service is " + serviceObj["ProcessId"].ToString());
        ObjectQuery serviceQuery = new ObjectQuery("SELECT * from Win32_Process WHERE ProcessID = '" + serviceObj["ProcessId"].ToString() + "'");
        ManagementObjectSearcher serviceSearcher = new ManagementObjectSearcher(scope, serviceQuery);
        ManagementObjectCollection serviceColl = serviceSearcher.Get();
        int returnCode = 0;
        foreach (ManagementObject currentObj in serviceColl)
        {
            if (currentObj["ProcessId"].ToString().Equals(serviceObj["ProcessId"].ToString(), StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("Found process " + currentObj["ProcessId"].ToString() + ". Terminating...");

                ManagementBaseObject termParams = currentObj.InvokeMethod("Terminate", (ManagementBaseObject)null, null);
                returnCode = System.Convert.ToInt32(termParams.Properties["ReturnValue"].Value);
            }
        }
        return returnCode;
    }
    catch (Exception connectEx)
    {
        Console.WriteLine("Connecting to " + vaultName + " caused an exception");
        Console.Write(connectEx);
        return 99;
    }
}

Solution

  • I eventually gave up trying to use the Terminate method on Win32_Process, and instead I'm using Create to call TaskKill.exe remotely. Because the return info is now hidden behind taskkill.exe, I then have to get the process list again and look for the target pid to make sure that the process was actually terminated.

    ConnectionOptions options = new ConnectionOptions();
    options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
    options.Username = serverDomain + "\\" + serverUser;
    options.Password = serverPassword;
    
    ManagementScope scope = new ManagementScope("\\\\" + serverName + "\\root\\cimv2", options);
    Console.WriteLine("Connecting to scope");
    scope.Connect();
    
    Console.WriteLine("Getting ManagementPath");
    ManagementPath servicePath = new ManagementPath("Win32_Service.Name='" + serviceName + "'");
    Console.WriteLine("Getting ManagementObject");
    ManagementObject serviceObj = new ManagementObject(scope, servicePath, new ObjectGetOptions());
    Console.WriteLine("Name of service is " + serviceObj["DisplayName"].ToString());
    Console.WriteLine("Process ID of service is " + serviceObj["ProcessId"].ToString());
    
    // use processid to kill process with taskkill
    ObjectGetOptions processObjGetOpt = new ObjectGetOptions();
    ManagementPath processPath = new ManagementPath("Win32_Process");
    ManagementClass processClass = new ManagementClass(scope, processPath, processObjGetOpt);
    ManagementBaseObject processInParams = processClass.GetMethodParameters("Create");
    processInParams["CommandLine"] = string.Format("cmd /c \"taskkill /f /pid {0}\"", serviceObj["ProcessId"].ToString());
    ManagementBaseObject outParams = processClass.InvokeMethod("Create", processInParams, null);
    Console.WriteLine("Return code for taskkill: " + outParams["returnValue"]);
    int returnCode = System.Convert.ToInt32(outParams["returnValue"]);