Determine whether assembly is a gui application

2019-07-09 02:11发布

问题:

I am trying to determine whether a C# assembly is a GUI or a Console application in order to build a tool which will automatically recreate lost short cuts.

Currently, I have a routine which recursively steps all directories in Program Files (and the x86 directory).

For each EXE it finds, the tool calls IsGuiApplication, passing the name of the EXE.

From there, I create an Assembly object using LoadFrom. I want to check whether this assembly is has a GUI output, but I'm unsure how to test this in C#.

My current idea is to use GetStdHandle, but I'm not sure how to apply this to an assembly outside of the running application.

My experience with reflection in C# is limited, so any help would be appreciated.

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace BatchShortcutBuild
{
    class Program
    {
        //I'm uncertain that I need to use this method
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr GetStdHandle(int nStdHandle);

        static void Main(string[] args) {
            BuildShortcuts();
            Console.ReadLine();
        }

        public static void BuildShortcuts() {
            String dirRoot = "C:\\Program Files\\";
            processRoot(dirRoot);

            dirRoot = "C:\\Program Files (x86)\\";
            processRoot(dirRoot);

            Console.WriteLine("Finished enumerating files");
            Console.ReadLine();
        }


        public static void processRoot(String path) {
            try {
                foreach (String theDir in Directory.EnumerateDirectories(path)) {
                    processRoot(theDir);
                }

                foreach (String theFile in Directory.EnumerateFiles(path, "*.exe")) {
                    if (IsGuiApplication(theFile)) {
                        //I would generate a shortcut here
                    }
                }
            } catch { }
        }

        public static bool IsGuiApplication(String filePath) {
            Console.WriteLine(filePath);
            Assembly a = Assembly.LoadFrom(filePath);
            //How to get the program type from the assembly?
            return false;
        }

    }
}

回答1:

As several times mentioned before, you can read the Subsystem Field.

    private PEFileKinds GetFileType(string inFilename)
    {
        using (var fs = new FileStream(inFilename, FileMode.Open, FileAccess.Read))
        {
            var buffer = new byte[4];
            fs.Seek(0x3C, SeekOrigin.Begin);
            fs.Read(buffer, 0, 4);
            var peoffset = BitConverter.ToUInt32(buffer, 0);
            fs.Seek(peoffset + 0x5C, SeekOrigin.Begin);
            fs.Read(buffer, 0, 1);
            if (buffer[0] == 3)
            {
                return PEFileKinds.ConsoleApplication;
            }
            else if (buffer[0] == 2)
            {
                return PEFileKinds.WindowApplication;
            }
            else
            {
                return PEFileKinds.Dll;
            }
        }
    }


回答2:

Just to be safe here, the method suggested by @Killany and @Nissim suggest is not 100% accurate, as console applications can reference the System.Windows.* dlls (either by mistake or by a need of other functionality given by the 'System.Windows' assembly).

I'm not sure a 100% method exist, as some applications can be given a parameter to run with/without ui (i.e. silently)



回答3:

Use GetReferencedAssemblies() to get all referenced assemblies and look for the system.windows.forms assembly

    AssemblyName[] referencedAssemblies = assm.GetReferencedAssemblies();
    foreach (var assmName in referencedAssemblies)
    {
      if (assmName.Name.StartsWith("System.Windows"))
       //bingo
    }


回答4:

A basic idea to detect GUI apps is that GUI apps always use assembly System.Windows.*.

bool isGui(Assembly exeAsm) {
   foreach (var asm in exeAsm.GetReferencedAssemblies()) {
       if (asm.FullName.Contains("System.Windows"))
          return true;
   }
   return false;
}

This will detect all .NET applications that are windows forms, or even WPF



回答5:

One thing you could check is the .subsystem of the file's PE header. If you open up the file in ILDASM and check the manifest, you'll see this if it uses the Windows GUI subsystem:

I don't think there's any method in the Assembly class to check this, so you'll probably need to check the file itself.

Another way to check would be to go through the types in the assembly and see if any of them derive from System.Windows.Forms.Form (Windows Forms) or System.Windows.Window (WPF):

private static bool HasGui(Assembly a)
{
    return a.DefinedTypes
        .Any(t => typeof(System.Windows.Forms.Form).IsAssignableFrom(t) ||
                  typeof(System.Windows.Window).IsAssignableFrom(t));
}

Note that you'll need to add references to System.Windows.Forms.dll and PresentationFramework.dll to gain access to these types.

You can use Assembly.LoadFrom(string) to load the assembly. I tested this method myself and it seemed a bit slow so perhaps you can make it faster by involving Parallel.ForEach.