Using FindExecutable - Finding the registered executable given a file path

 

I had a need to find the registered executable for a particular file extension. Now, it is possible to determine this via the registry. You could figure it out by looking into HKEY_CLASSES_ROOT but that is sometimes a tedious exercise if you've multiple potential applications that can handle the file type. The route I chose was to make use of a native function called FindExecutable exposed by shell32.dll.

 

FindExecutable function (Windows)

https://msdn.microsoft.com/en-us/library/windows/desktop/bb776419(v=vs.85).aspx

 

image

 

Looks fun, right?

How do I incorporate this inside of my Windows PowerShell script?

 

It isn't too bad. Most people know that Windows PowerShell is built on top of the .NET Framework. Well, with the .NET Framework comes the ability to reference native or unmanaged code (above) using the Add-Type cmdlet. This is probably an under-utilized feature of Windows PowerShell because there are cmdlets for almost everything now. So all your C# and C++ code isn't completely useless! It can be re-used.

 

Add-Type cmdlet

Per the documentation, the Add-Type cmdlet lets you define a .NET Framework class in your Windows PowerShell session. You can then instantiate objects (by using the New-Object cmdlet) and use the objects, just as you would use any .NET Framework object.  If you add an Add-Type command to your Windows PowerShell profile, the class will be available in all Windows PowerShell sessions.

You can specify the type by specifying an existing assembly or source code files, or you can specify the source code inline or saved in a variable. You can even specify only a method and Add-Type will define and generate the class. You can use this feature to make Platform Invoke (P/Invoke) calls to unmanaged functions in Windows PowerShell. If you specify source code, Add-Type compiles the specified source code and generates an in-memory assembly that contains the new .NET Framework types.

You can use the parameters of Add-Type to specify an alternate language and compiler (CSharp is the default), compiler options, assembly dependencies, the class namespace, the names of the type, and the resulting assembly.

 

The Code

  
 $Source = @"

using System;
using System.Text;
using System.Runtime.InteropServices;
public class Win32API
    {
        [DllImport("shell32.dll", EntryPoint="FindExecutable")] 

        public static extern long FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult);

        public static string FindExecutable(string pv_strFilename)
        {
            StringBuilder objResultBuffer = new StringBuilder(1024);
            long lngResult = 0;

            lngResult = FindExecutableA(pv_strFilename, string.Empty, objResultBuffer);

            if(lngResult >= 32)
            {
                return objResultBuffer.ToString();
            }

            return string.Format("Error: ({0})", lngResult);
        }
    }

"@

Add-Type -TypeDefinition $Source -ErrorAction SilentlyContinue

Get-ChildItem -Path D:\MyFolder | 
    Where-Object { -not $_.PSIsContainer } | 
            ForEach-Object {
                $FullName = $_.FullName
                $Executable = [Win32API]::FindExecutable($FullName)
                $OutputString = "`nFile: {0}`nExecutable: {1}" -f $FullName, $Executable
                Write-Host -Object $OutputString
  
            }