Reputation: 282875
I'm writing a program that (part of what is does is) executes other programs. I want to to be able to run as many types of programs (written in different languages) as possible using Process.Start
. So, I'm thinking I should:
#!
#!
as the program to execute, and pass in the filename as an argument instead#!
is found, check the file extension against a dictionary of known programs (e.g., .py -> python
) and execute that program insteadBut, I'm thinking it might be easier/more efficient to actually check if the file is executable first and if so, jump to 6. Is there a way to do this?
Upvotes: 5
Views: 16872
Reputation: 10479
Using some information from Scott's answer, I wrote my own method which returns an Enum value specifying the type of the file. What I noticed with his solution is that it will return 'Unknown' even if the file does not exist. Additionally, I simplified a couple of the if
conditions and added an additional condition for when none of the conditions were met.
public static class ShellFileGetInfo
{
[DllImport("shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
// Enum for return of the file type
public enum ShellFileType
{
FileNotFound,
Unknown,
Dos,
Windows,
Console
}
// Apply the appropriate overlays to the file's icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_ADDOVERLAYS = 0x000000020;
// Modify SHGFI_ATTRIBUTES to indicate that the dwAttributes member of the SHFILEINFO structure at psfi contains the specific attributes that are desired. These attributes are passed to IShellFolder::GetAttributesOf. If this flag is not specified, 0xFFFFFFFF is passed to IShellFolder::GetAttributesOf, requesting all attributes. This flag cannot be specified with the SHGFI_ICON flag.
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000;
// Retrieve the item attributes. The attributes are copied to the dwAttributes member of the structure specified in the psfi parameter. These are the same attributes that are obtained from IShellFolder::GetAttributesOf.
public const uint SHGFI_ATTRIBUTES = 0x000000800;
// Retrieve the display name for the file, which is the name as it appears in Windows Explorer. The name is copied to the szDisplayName member of the structure specified in psfi. The returned display name uses the long file name, if there is one, rather than the 8.3 form of the file name. Note that the display name can be affected by settings such as whether extensions are shown.
public const uint SHGFI_DISPLAYNAME = 0x000000200;
// Retrieve the type of the executable file if pszPath identifies an executable file. The information is packed into the return value. This flag cannot be specified with any other flags.
public const uint SHGFI_EXETYPE = 0x000002000;
// Retrieve the handle to the icon that represents the file and the index of the icon within the system image list. The handle is copied to the hIcon member of the structure specified by psfi, and the index is copied to the iIcon member.
public const uint SHGFI_ICON = 0x000000100;
// Retrieve the name of the file that contains the icon representing the file specified by pszPath, as returned by the IExtractIcon::GetIconLocation method of the file's icon handler. Also retrieve the icon index within that file. The name of the file containing the icon is copied to the szDisplayName member of the structure specified by psfi. The icon's index is copied to that structure's iIcon member.
public const uint SHGFI_ICONLOCATION = 0x000001000;
// Modify SHGFI_ICON, causing the function to retrieve the file's large icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_LARGEICON = 0x000000000;
// Modify SHGFI_ICON, causing the function to add the link overlay to the file's icon. The SHGFI_ICON flag must also be set.
public const uint SHGFI_LINKOVERLAY = 0x000008000;
// Modify SHGFI_ICON, causing the function to retrieve the file's open icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains the file's small open icon. A container object displays an open icon to indicate that the container is open. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
public const uint SHGFI_OPENICON = 0x000000002;
// Version 5.0. Return the index of the overlay icon. The value of the overlay index is returned in the upper eight bits of the iIcon member of the structure specified by psfi. This flag requires that the SHGFI_ICON be set as well.
public const uint SHGFI_OVERLAYINDEX = 0x000000040;
// Indicate that pszPath is the address of an ITEMIDLIST structure rather than a path name.
public const uint SHGFI_PIDL = 0x000000008;
// Modify SHGFI_ICON, causing the function to blend the file's icon with the system highlight color. The SHGFI_ICON flag must also be set.
public const uint SHGFI_SELECTED = 0x000010000;
// Modify SHGFI_ICON, causing the function to retrieve a Shell-sized icon. If this flag is not specified the function sizes the icon according to the system metric values. The SHGFI_ICON flag must also be set.
public const uint SHGFI_SHELLICONSIZE = 0x000000004;
// Modify SHGFI_ICON, causing the function to retrieve the file's small icon. Also used to modify SHGFI_SYSICONINDEX, causing the function to return the handle to the system image list that contains small icon images. The SHGFI_ICON and/or SHGFI_SYSICONINDEX flag must also be set.
public const uint SHGFI_SMALLICON = 0x000000001;
// Retrieve the index of a system image list icon. If successful, the index is copied to the iIcon member of psfi. The return value is a handle to the system image list. Only those images whose indices are successfully copied to iIcon are valid. Attempting to access other images in the system image list will result in undefined behavior.
public const uint SHGFI_SYSICONINDEX = 0x000004000;
// Retrieve the string that describes the file's type. The string is copied to the szTypeName member of the structure specified in psfi.
public const uint SHGFI_TYPENAME = 0x000000400;
// Indicates that the function should not attempt to access the file specified by pszPath. Rather, it should act as if the file specified by pszPath exists with the file attributes passed in dwFileAttributes. This flag cannot be combined with the SHGFI_ATTRIBUTES, SHGFI_EXETYPE, or SHGFI_PIDL flags.
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
}
public static ShellFileGetInfo.ShellFileType GetExeType(string file)
{
ShellFileGetInfo.ShellFileType type = ShellFileGetInfo.ShellFileType.FileNotFound;
if (File.Exists(file))
{
ShellFileGetInfo.SHFILEINFO shinfo = new ShellFileGetInfo.SHFILEINFO();
IntPtr ptr = ShellFileGetInfo.SHGetFileInfo(file, 128, ref shinfo, (uint)Marshal.SizeOf(shinfo), ShellFileGetInfo.SHGFI_EXETYPE);
int wparam = ptr.ToInt32();
int loWord = wparam & 0xffff;
int hiWord = wparam >> 16;
type = ShellFileGetInfo.ShellFileType.Unknown;
if(wparam != 0)
{
if (hiWord == 0x0000 && loWord == 0x5a4d)
{
type = ShellFileGetInfo.ShellFileType.Dos;
}
else if (hiWord == 0x0000 && loWord == 0x4550)
{
type = ShellFileGetInfo.ShellFileType.Console;
}
else if ((hiWord != 0x0000) && (loWord == 0x454E || loWord == 0x4550 || loWord == 0x454C))
{
type = ShellFileGetInfo.ShellFileType.Windows;
}
}
}
return type;
}
switch(GetExeType( file )) {
case ShellFileGetInfo.ShellFileType.Unknown:
System.Diagnostics.Debug.WriteLine( "Unknown: " + file );
break;
case ShellFileGetInfo.ShellFileType.Dos:
System.Diagnostics.Debug.WriteLine( "DOS: " + file );
break;
case ShellFileGetInfo.ShellFileType.Windows:
System.Diagnostics.Debug.WriteLine( "Windows: " + file );
break;
case ShellFileGetInfo.ShellFileType.Console:
System.Diagnostics.Debug.WriteLine( "Console: " + file );
break;
case ShellFileGetInfo.ShellFileType.FileNotFound:
System.Diagnostics.Debug.WriteLine( "Missing: " + file );
break;
}
Upvotes: 1
Reputation: 666
Since you mention Linux, you might consider using the 'file' command. I believe gnuwin32 has a port of this command for windows. Of course, that'd mean parsing the output returned by 'file' (the file MIME type such as "application/x-executable"). So depending on the number of executables you want to be able to recognise, this might not be the easiest solution.
[Edit: added example output]
file.exe d:\Downloads\tabview.py
d:\Downloads\tabview.py; a /usr/local/bin/python script text executablefile.exe d:\Downloads\tabview.txt
d:\Downloads\tabview.txt; a /usr/local/bin/python script text executable>file.exe d:\Downloads\7zbv14ww.exe
d:\Downloads\7zbv14ww.exe; PE32 executable for MS Windows (GUI) Intel 80386 32- itfile.exe -b d:\Downloads\AlbumArtSmall.jpg
JPEG image data, JFIF standard 1.01
Upvotes: 1
Reputation:
You should be able to pretty much start with step 5. Ie check the file extension first. Windows lives for file extensions. There's not much you can do without them.
If you recognise the extension as an executable, then you can pass it to Process.Start or open the file and find out which executable you should be passing it to. I would also look for the .net equivalent to ShellExecute, because I'm not 100% convinced it's Process.Start. (I've not really done much .net/c# coding in the last 5 years though, so I could be wrong here.)
Upvotes: 1
Reputation: 42516
The only way to do this is to use P/Invoke calls in to the Win32 API. You need to use the SHGetFileInfo method and then unpack the return value:
[Flags]
internal enum SHGFI : uint
{
ADDOVERLAYS = 0x20,
ATTR_SPECIFIED = 0x20000,
ATTRIBUTES = 0x800,
DISPLAYNAME = 0x200,
EXETYPE = 0x2000,
ICON = 0x100,
ICONLOCATION = 0x1000,
LARGEICON = 0,
LINKOVERLAY = 0x8000,
OPENICON = 2,
OVERLAYINDEX = 0x40,
PIDL = 8,
SELECTED = 0x10000,
SHELLICONSIZE = 4,
SMALLICON = 1,
SYSICONINDEX = 0x4000,
TYPENAME = 0x400,
USEFILEATTRIBUTES = 0x10
}
/// <summary>
/// This structure contains information about a file object.
/// </summary>
/// <remarks>
/// This structure is used with the SHGetFileInfo function.
/// </remarks>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct SHFILEINFO
{
/// <summary>
/// Handle to the icon that represents the file.
/// </summary>
internal IntPtr hIcon;
/// <summary>
/// Index of the icon image within the system image list.
/// </summary>
internal int iIcon;
/// <summary>
/// Specifies the attributes of the file object.
/// </summary>
internal SFGAO dwAttributes;
/// <summary>
/// Null-terminated string that contains the name of the file as it
/// appears in the Windows shell, or the path and name of the file that
/// contains the icon representing the file.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.MAX_PATH)]
internal string szDisplayName;
/// <summary>
/// Null-terminated string that describes the type of file.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
internal string szTypeName;
}
/// <summary>
/// Specifies the executable file type.
/// </summary>
public enum ExecutableType : int
{
/// <summary>
/// The file executable type is not able to be determined.
/// </summary>
Unknown = 0,
/// <summary>
/// The file is an MS-DOS .exe, .com, or .bat file.
/// </summary>
DOS,
/// <summary>
/// The file is a Microsoft Win32®-based console application.
/// </summary>
Win32Console,
/// <summary>
/// The file is a Windows application.
/// </summary>
Windows,
}
// Retrieves information about an object in the file system,
// such as a file, a folder, a directory, or a drive root.
[DllImport("shell32",
EntryPoint = "SHGetFileInfo",
ExactSpelling = false,
CharSet = CharSet.Auto,
SetLastError = true)]
internal static extern IntPtr SHGetFileInfo(
string pszPath,
FileAttributes dwFileAttributes,
ref SHFILEINFO sfi,
int cbFileInfo,
SHGFI uFlags);
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
private ExecutableType IsExecutable(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException("fileName");
}
ExecutableType executableType = ExecutableType.Unknown;
if (File.Exists(fileName)
{
// Try to fill the same SHFILEINFO struct for the exe type. The returned pointer contains the encoded
// executable type data.
ptr = IntPtr.Zero;
ptr = SHGetFileInfo(fileName, FileAttributes.Normal, ref this.shellFileInfo, Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI.EXETYPE);
// We need to split the returned pointer up into the high and low order words. These are important
// because they help distinguish some of the types. The possible values are:
//
// Value Meaning
// ----------------------------------------------------------------------------------------------
// 0 Nonexecutable file or an error condition.
// LOWORD = NE or PE and HIWORD = Windows version Microsoft Windows application.
// LOWORD = MZ and HIWORD = 0 Windows 95, Windows 98: Microsoft MS-DOS .exe, .com, or .bat file
// Microsoft Windows NT, Windows 2000, Windows XP: MS-DOS .exe or .com file
// LOWORD = PE and HIWORD = 0 Windows 95, Windows 98: Microsoft Win32 console application
// Windows NT, Windows 2000, Windows XP: Win32 console application or .bat file
// MZ = 0x5A4D - DOS signature.
// NE = 0x454E - OS/2 signature.
// LE = 0x454C - OS/2 LE or VXD signature.
// PE = 0x4550 - Win32/NT signature.
int wparam = ptr.ToInt32();
int loWord = wparam & 0xffff;
int hiWord = wparam >> 16;
if (wparam == 0)
{
executableType = ExecutableType.Unknown;
}
else
{
if (hiWord == 0x0000)
{
if (loWord == 0x5A4D)
{
// The file is an MS-DOS .exe, .com, or .bat
executableType = ExecutableType.DOS;
}
else if (loWord == 0x4550)
{
executableType = ExecutableType.Win32Console;
}
}
else
{
if (loWord == 0x454E || loWord == 0x4550)
{
executableType = ExecutableType.Windows;
}
else if (loWord == 0x454C)
{
executableType = ExecutableType.Windows;
}
}
}
}
return executableType;
}
(This should work, but was extracted from a larger library so there may be minor issues. It should, however, be complete enough to get you most of the way there.)
Upvotes: 6
Reputation: 5525
In Windows there is no real notion of "executable", like the specific permission that exists in *NIX systems.
You have two options. The first one, like saurabh had suggested before me, is to rely on the system to associate between the file extension and the command to be performed. This approach (of using Process.Start) has many advantages - it leaves the power of association to the user, as in letting the user pick the correct way to run the various file types.
The second option is to mimic the Windows file association process, by having a dictionary from an extension to the command that can run the file, and falling back to checking the first line of the file if needed. This has the advantage of you having the power of setting the associations, but it also requires constant modifications and maintenance on your side, in addition to losing the flexibility on the user side - which may be a good thing or a bad thing.
Upvotes: 2
Reputation: 17556
if you are using .net than Process.Start do lot of things for you.
if you pass a exe , it will run the exe.
If you pass a word document , it will open the word document
and may more
Upvotes: 0