PowerToys/Plugins/Wox.Plugin.Program/Programs/Win32.cs

406 lines
15 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
2016-11-29 09:55:25 +08:00
using System.Runtime.InteropServices;
2017-03-02 07:21:34 +08:00
using System.Security;
2016-08-21 01:18:41 +08:00
using System.Text;
using Microsoft.Win32;
2016-08-21 01:18:41 +08:00
using Shell;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
2016-08-20 08:17:28 +08:00
namespace Wox.Plugin.Program.Programs
{
[Serializable]
public class Win32 : IProgram
{
2016-08-21 01:50:14 +08:00
public string Name { get; set; }
public string UniqueIdentifier { get; set; }
2016-08-20 08:17:28 +08:00
public string IcoPath { get; set; }
public string FullPath { get; set; }
public string ParentDirectory { get; set; }
2016-08-20 08:17:28 +08:00
public string ExecutableName { get; set; }
2016-08-21 01:50:14 +08:00
public string Description { get; set; }
public bool Valid { get; set; }
2019-09-08 20:18:55 +08:00
public bool Enabled { get; set; }
public string Location => ParentDirectory;
private const string ShortcutExtension = "lnk";
private const string ApplicationReferenceExtension = "appref-ms";
private const string ExeExtension = "exe";
private int Score(string query)
{
2019-09-29 13:03:30 +08:00
var score1 = StringMatcher.FuzzySearch(query, Name).ScoreAfterSearchPrecisionFilter();
var score2 = StringMatcher.ScoreForPinyin(Name, query);
2019-09-29 13:03:30 +08:00
var score3 = StringMatcher.FuzzySearch(query, Description).ScoreAfterSearchPrecisionFilter();
var score4 = StringMatcher.ScoreForPinyin(Description, query);
2019-09-29 13:03:30 +08:00
var score5 = StringMatcher.FuzzySearch(query, ExecutableName).ScoreAfterSearchPrecisionFilter();
var score = new[] { score1, score2, score3, score4, score5 }.Max();
return score;
}
public Result Result(string query, IPublicAPI api)
{
var result = new Result
{
SubTitle = FullPath,
IcoPath = IcoPath,
Score = Score(query),
ContextData = this,
Action = e =>
{
var info = new ProcessStartInfo
{
FileName = FullPath,
WorkingDirectory = ParentDirectory
};
var hide = Main.StartProcess(info);
return hide;
}
};
if (Description.Length >= Name.Length &&
Description.Substring(0, Name.Length) == Name)
{
result.Title = Description;
}
else if (!string.IsNullOrEmpty(Description))
{
result.Title = $"{Name}: {Description}";
}
else
{
result.Title = Name;
}
return result;
}
public List<Result> ContextMenus(IPublicAPI api)
{
var contextMenus = new List<Result>
{
new Result
{
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
Action = _ =>
{
var info = new ProcessStartInfo
{
FileName = FullPath,
WorkingDirectory = ParentDirectory,
Verb = "runas"
};
var hide = Main.StartProcess(info);
return hide;
},
IcoPath = "Images/cmd.png"
},
new Result
{
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
Action = _ =>
{
var hide = Main.StartProcess(new ProcessStartInfo(ParentDirectory));
return hide;
},
IcoPath = "Images/folder.png"
}
};
return contextMenus;
}
public override string ToString()
{
return ExecutableName;
}
private static Win32 Win32Program(string path)
{
2016-08-20 08:17:28 +08:00
var p = new Win32
{
2016-08-21 01:50:14 +08:00
Name = Path.GetFileNameWithoutExtension(path),
IcoPath = path,
FullPath = path,
UniqueIdentifier = path,
ParentDirectory = Directory.GetParent(path).FullName,
Description = string.Empty,
2019-09-08 20:18:55 +08:00
Valid = true,
Enabled = true
};
return p;
}
private static Win32 LnkProgram(string path)
{
var program = Win32Program(path);
try
{
2016-08-21 01:18:41 +08:00
var link = new ShellLink();
const uint STGM_READ = 0;
2017-01-24 08:24:20 +08:00
((IPersistFile)link).Load(path, STGM_READ);
2016-08-21 01:18:41 +08:00
var hwnd = new _RemotableHandle();
link.Resolve(ref hwnd, 0);
const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH);
var data = new _WIN32_FIND_DATAW();
const uint SLGP_SHORTPATH = 1;
link.GetPath(buffer, buffer.Capacity, ref data, SLGP_SHORTPATH);
var target = buffer.ToString();
2016-11-29 09:55:25 +08:00
if (!string.IsNullOrEmpty(target))
{
2016-11-29 09:55:25 +08:00
var extension = Extension(target);
if (extension == ExeExtension && File.Exists(target))
{
2016-11-29 09:55:25 +08:00
buffer = new StringBuilder(MAX_PATH);
link.GetDescription(buffer, MAX_PATH);
var description = buffer.ToString();
if (!string.IsNullOrEmpty(description))
{
program.Description = description;
}
else
{
2016-11-29 09:55:25 +08:00
var info = FileVersionInfo.GetVersionInfo(target);
if (!string.IsNullOrEmpty(info.FileDescription))
{
program.Description = info.FileDescription;
}
}
}
}
return program;
}
2016-11-29 09:55:25 +08:00
catch (COMException e)
{
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
2017-01-24 08:24:20 +08:00
Log.Exception($"|Win32.LnkProgram|COMException when parsing shortcut <{path}> with HResult <{e.HResult}>", e);
2016-11-29 09:55:25 +08:00
program.Valid = false;
return program;
}
catch (Exception e)
{
2017-01-24 08:24:20 +08:00
Log.Exception($"|Win32.LnkProgram|Exception when parsing shortcut <{path}>", e);
2016-11-29 09:55:25 +08:00
program.Valid = false;
return program;
}
}
private static Win32 ExeProgram(string path)
{
var program = Win32Program(path);
2016-08-21 01:18:41 +08:00
var info = FileVersionInfo.GetVersionInfo(path);
if (!string.IsNullOrEmpty(info.FileDescription))
{
2016-08-21 01:50:14 +08:00
program.Description = info.FileDescription;
2016-08-20 08:17:28 +08:00
}
return program;
}
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes)
{
if (!Directory.Exists(directory))
return new string[] { };
var files = new List<string>();
var folderQueue = new Queue<string>();
folderQueue.Enqueue(directory);
do
{
var currentDirectory = folderQueue.Dequeue();
try
{
foreach (var suffix in suffixes)
{
try
{
files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly));
}
catch (DirectoryNotFoundException e)
{
Log.Exception($"|Program.Win32.ProgramPaths|skip directory(<{currentDirectory}>)", e);
continue;
}
}
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Log.Exception($"|Program.Win32.ProgramPaths|Don't have permission on <{currentDirectory}>", e);
}
2017-03-02 07:21:34 +08:00
try
{
foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", SearchOption.TopDirectoryOnly))
{
folderQueue.Enqueue(childDirectory);
}
2017-03-02 07:21:34 +08:00
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Log.Exception($"|Program.Win32.ProgramPaths|Don't have permission on <{currentDirectory}>", e);
2017-03-02 07:21:34 +08:00
}
} while (folderQueue.Any());
return files;
}
private static string Extension(string path)
{
var extension = Path.GetExtension(path)?.ToLower();
if (!string.IsNullOrEmpty(extension))
2016-08-20 08:17:28 +08:00
{
return extension.Substring(1);
2016-08-20 08:17:28 +08:00
}
else
{
return string.Empty;
2016-08-20 08:17:28 +08:00
}
}
2016-08-20 08:17:28 +08:00
private static ParallelQuery<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
{
var listToAdd = new List<string>();
2019-09-06 06:06:51 +08:00
sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes))
.ToList()
.Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier))
.ToList()
.ForEach(x => listToAdd.Add(x));
var paths = listToAdd.Distinct().ToArray();
var programs1 = paths.AsParallel().Where(p => Extension(p) == ExeExtension).Select(ExeProgram);
var programs2 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(ExeProgram);
var programs3 = from p in paths.AsParallel()
let e = Extension(p)
where e != ShortcutExtension && e != ExeExtension
select Win32Program(p);
return programs1.Concat(programs2).Concat(programs3);
}
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes)
{
var disabledProgramsList = Main._settings.DisabledProgramSources;
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
var paths1 = ProgramPaths(directory1, suffixes);
var paths2 = ProgramPaths(directory2, suffixes);
var toFilter = paths1.Concat(paths2);
2019-09-13 05:26:24 +08:00
var paths = toFilter
.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1))
2019-09-13 05:26:24 +08:00
.Select(t1 => t1)
.ToArray();
var programs1 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(LnkProgram);
var programs2 = paths.AsParallel().Where(p => Extension(p) == ApplicationReferenceExtension).Select(Win32Program);
var programs = programs1.Concat(programs2).Where(p => p.Valid);
return programs;
}
private static ParallelQuery<Win32> AppPathsPrograms(string[] suffixes)
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
var programs = new List<Win32>();
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
2016-08-20 08:17:28 +08:00
{
if (root != null)
{
programs.AddRange(ProgramsFromRegistryKey(root));
}
}
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
{
if (root != null)
{
programs.AddRange(ProgramsFromRegistryKey(root));
}
}
var disabledProgramsList = Main._settings.DisabledProgramSources;
var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName)));
var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1);
return filtered;
}
private static IEnumerable<Win32> ProgramsFromRegistryKey(RegistryKey root)
{
return root
.GetSubKeyNames()
.Select(x => GetProgramRegistryPath(root, x))
.Distinct()
.Select(x => ProgramFromRegistrySubkey(x));
}
private static string GetProgramRegistryPath(RegistryKey root, string subkey)
{
var path = string.Empty;
using (var key = root.OpenSubKey(subkey))
{
if (key == null)
return string.Empty;
var defaultValue = string.Empty;
path = key.GetValue(defaultValue) as string;
2016-08-20 08:17:28 +08:00
}
if (string.IsNullOrEmpty(path))
return string.Empty;
// fix path like this: ""\"C:\\folder\\executable.exe\""
return path = path.Trim('"', ' ');
}
private static Win32 ProgramFromRegistrySubkey(string path)
{
if (string.IsNullOrEmpty(path))
return new Win32();
path = Environment.ExpandEnvironmentVariables(path);
if (!File.Exists(path))
return new Win32();
var entry = Win32Program(path);
entry.ExecutableName = Path.GetFileName(path);
return entry;
}
public static Win32[] All(Settings settings)
{
ParallelQuery<Win32> programs = new List<Win32>().AsParallel();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
programs = programs.Concat(unregistered);
if (settings.EnableRegistrySource)
{
var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
programs = programs.Concat(appPaths);
}
if (settings.EnableStartMenuSource)
2016-08-20 08:17:28 +08:00
{
var startMenu = StartMenuPrograms(settings.ProgramSuffixes);
programs = programs.Concat(startMenu);
}
//.Select(ScoreFilter);
return programs.ToArray();
}
}
}