Search code examples
powershellwinapi32bit-64bit

Find/Kill 64 bit processes by path in 32 bit powershell


I have PowerShell cleanup script for an application. Ideally processes are discovered by their path, so as not to recklessly kill other processes on the system which may have similar names. We noticed some processes are not being detected/killed, and after much experimentation realized the bit-ness is the issue. The script is bootstrapped in 32-bit for compatibility, but some of the processes are not.

Get-Process can be called in 32-bit PowerShell and returns all the processes including the 64 bit ones, however as noted in This Ref:

On computers that are running a 64-bit version of Windows, the 64-bit version of PowerShell gets only 64-bit process modules and the 32-bit version of PowerShell gets only 32-bit process modules.

And indeed while the processes are discovered, the process module information (including the Path of the process) is not available for processes whose bit-ness does not match the shell.

This question has some discussion about it: How can I get the executable path of a 64-bit process given its PID from a 32-bit process?

The suggested Get-WmiObject query does not work for me as shown, it returns 64- bit processes with missing ExecutablePath information, basically the same as Get-Process.

So my question is: Is it possible to call the WinAPI functions like QueryFullProcessImageName() or GetModuleFileNameEx() from a PowerShell script as a workaround to get this information? Or is there any other way to do this I am not considering?


Solution

  • In order to meet this need this is what I cobbled together. Maybe it will help someone else. Criticism welcome.

    $pinvoke = Add-Type -PassThru -Name pinvoke -MemberDefinition @'
        [DllImport("kernel32.dll", SetLastError=true)]
        private static extern bool CloseHandle(
            IntPtr hObject);
    
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(
            uint processAccess,
            bool bInheritHandle,
            int processId);
    
        [DllImport("kernel32.dll", SetLastError=true)]
        private static extern bool QueryFullProcessImageName(
            IntPtr hProcess,
            int dwFlags,
            System.Text.StringBuilder lpExeName,
            ref int lpdwSize);
        private const int QueryLimitedInformation = 0x00001000;
    
        public static string GetProcessPath(int pid)
        {
            var size = 1024;
            var sb = new System.Text.StringBuilder(size);
            var handle = OpenProcess(QueryLimitedInformation, false, pid);
            if (handle == IntPtr.Zero) return null;
            var success = QueryFullProcessImageName(handle, 0, sb, ref size);
            CloseHandle(handle);
            if (!success) return null;
            return sb.ToString();
        }
    '@
    

    And then you can get the path of a 32 or 64 bit process by

    $pinvoke::GetProcessPath($pid)
    

    To filter processes instead of:

    Get-Process | Where-Object {$_.Path -like "*$filePath*"}
    

    which has the problems with 32/64 bit, you can use

    Get-Process | Where-Object {$pinvoke::GetProcessPath($_.Id) -like "*$filePath*"}
    

    Pipe that into Stop-Process or whatever you want to do with it.