mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-12-23 08:27:58 +08:00
ec803d63c8
* Added custom tooltip style (with theming support) that represents the UWP tooltip style. * Removed unnecessary space in text * Fixed run as admin casing * Fixed casing for Indexer plugin
808 lines
32 KiB
C#
808 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Win32;
|
|
using Wox.Infrastructure;
|
|
using Microsoft.Plugin.Program.Logger;
|
|
using Wox.Plugin;
|
|
using System.Windows.Input;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using Wox.Infrastructure.Logger;
|
|
|
|
namespace Microsoft.Plugin.Program.Programs
|
|
{
|
|
|
|
[Serializable]
|
|
public class Win32 : IProgram
|
|
{
|
|
public string Name { get; set; }
|
|
public string UniqueIdentifier { get; set; }
|
|
public string IcoPath { get; set; }
|
|
public string FullPath { get; set; }
|
|
public string LnkResolvedPath { get; set; }
|
|
public string ParentDirectory { get; set; }
|
|
public string ExecutableName { get; set; }
|
|
public string Description { get; set; } = String.Empty;
|
|
public bool Valid { get; set; }
|
|
public bool Enabled { get; set; }
|
|
public bool hasArguments { get; set; } = false;
|
|
public string Arguments { get; set; } = String.Empty;
|
|
public string Location => ParentDirectory;
|
|
public uint AppType { get; set; }
|
|
|
|
private const string ShortcutExtension = "lnk";
|
|
private const string ApplicationReferenceExtension = "appref-ms";
|
|
private const string ExeExtension = "exe";
|
|
private const string InternetShortcutExtension = "url";
|
|
|
|
private const string proxyWebApp = "_proxy.exe";
|
|
private const string appIdArgument = "--app-id";
|
|
|
|
private enum ApplicationTypes
|
|
{
|
|
WEB_APPLICATION = 0,
|
|
INTERNET_SHORTCUT_APPLICATION = 1,
|
|
WIN32_APPLICATION = 2,
|
|
RUN_COMMAND = 3
|
|
}
|
|
|
|
// Function to calculate the score of a result
|
|
private int Score(string query)
|
|
{
|
|
var nameMatch = StringMatcher.FuzzySearch(query, Name);
|
|
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
|
|
var executableNameMatch = StringMatcher.FuzzySearch(query, ExecutableName);
|
|
var score = new[] { nameMatch.Score, descriptionMatch.Score/2, executableNameMatch.Score }.Max();
|
|
return score;
|
|
}
|
|
|
|
public bool IsWebApplication()
|
|
{
|
|
// To Filter PWAs when the user searches for the main application
|
|
// All Chromium based applications contain the --app-id argument
|
|
// Reference : https://codereview.chromium.org/399045/show
|
|
bool isWebApplication = FullPath.Contains(proxyWebApp) && Arguments.Contains(appIdArgument);
|
|
return isWebApplication;
|
|
}
|
|
|
|
// Condition to Filter pinned Web Applications or PWAs when searching for the main application
|
|
public bool FilterWebApplication(string query)
|
|
{
|
|
// If the app is not a web application, then do not filter it
|
|
if(!IsWebApplication())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Set the subtitle to 'Web Application'
|
|
AppType = (uint)ApplicationTypes.WEB_APPLICATION;
|
|
|
|
string[] subqueries = query.Split();
|
|
bool nameContainsQuery = false;
|
|
bool pathContainsQuery = false;
|
|
|
|
// check if any space separated query is a part of the app name or path name
|
|
foreach (var subquery in subqueries)
|
|
{
|
|
if (FullPath.Contains(subquery, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
pathContainsQuery = true;
|
|
}
|
|
|
|
if(Name.Contains(subquery, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
nameContainsQuery = true;
|
|
}
|
|
}
|
|
return pathContainsQuery && !nameContainsQuery;
|
|
}
|
|
|
|
// Function to set the subtitle based on the Type of application
|
|
private string SetSubtitle(IPublicAPI api)
|
|
{
|
|
if(AppType == (uint)ApplicationTypes.WIN32_APPLICATION)
|
|
{
|
|
return api.GetTranslation("powertoys_run_plugin_program_win32_application");
|
|
}
|
|
else if(AppType == (uint)ApplicationTypes.INTERNET_SHORTCUT_APPLICATION)
|
|
{
|
|
return api.GetTranslation("powertoys_run_plugin_program_internet_shortcut_application");
|
|
}
|
|
else if(AppType == (uint)ApplicationTypes.WEB_APPLICATION)
|
|
{
|
|
return api.GetTranslation("powertoys_run_plugin_program_web_application");
|
|
}
|
|
else if(AppType == (uint)ApplicationTypes.RUN_COMMAND)
|
|
{
|
|
return api.GetTranslation("powertoys_run_plugin_program_run_command");
|
|
}
|
|
else
|
|
{
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
public bool QueryEqualsNameForRunCommands(string query)
|
|
{
|
|
if (AppType == (uint)ApplicationTypes.RUN_COMMAND
|
|
&& !query.Equals(Name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public Result Result(string query, IPublicAPI api)
|
|
{
|
|
var score = Score(query);
|
|
if (score <= 0)
|
|
{ // no need to create result if this is zero
|
|
return null;
|
|
}
|
|
|
|
if(!hasArguments)
|
|
{
|
|
var noArgumentScoreModifier = 5;
|
|
score += noArgumentScoreModifier;
|
|
}
|
|
else
|
|
{
|
|
// Filter Web Applications when searching for the main application
|
|
if (FilterWebApplication(query))
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// NOTE: This is to display run commands only when there is an exact match, like in start menu
|
|
if(!QueryEqualsNameForRunCommands(query))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var result = new Result
|
|
{
|
|
SubTitle = SetSubtitle(api),
|
|
IcoPath = IcoPath,
|
|
Score = score,
|
|
ContextData = this,
|
|
Action = e =>
|
|
{
|
|
var info = new ProcessStartInfo
|
|
{
|
|
FileName = LnkResolvedPath ?? FullPath,
|
|
WorkingDirectory = ParentDirectory,
|
|
UseShellExecute = true
|
|
};
|
|
|
|
Main.StartProcess(Process.Start, info);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// To set the title for the result to always be the name of the application
|
|
result.Title = Name;
|
|
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
|
|
|
|
var toolTipTitle = string.Format("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
|
|
var toolTipText = string.Format("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), FullPath);
|
|
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
|
|
{
|
|
var contextMenus = new List<ContextMenuResult>();
|
|
|
|
if (AppType != (uint)ApplicationTypes.INTERNET_SHORTCUT_APPLICATION)
|
|
{
|
|
contextMenus.Add(new ContextMenuResult
|
|
{
|
|
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
|
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
|
|
Glyph = "\xE7EF",
|
|
FontFamily = "Segoe MDL2 Assets",
|
|
AcceleratorKey = Key.Enter,
|
|
AcceleratorModifiers = (ModifierKeys.Control | ModifierKeys.Shift),
|
|
Action = _ =>
|
|
{
|
|
var info = new ProcessStartInfo
|
|
{
|
|
FileName = FullPath,
|
|
WorkingDirectory = ParentDirectory,
|
|
Verb = "runas",
|
|
UseShellExecute = true
|
|
};
|
|
|
|
Task.Run(() => Main.StartProcess(Process.Start, info));
|
|
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
contextMenus.Add(
|
|
new ContextMenuResult
|
|
{
|
|
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
|
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
|
|
Glyph = "\xE838",
|
|
FontFamily = "Segoe MDL2 Assets",
|
|
AcceleratorKey = Key.E,
|
|
AcceleratorModifiers = (ModifierKeys.Control | ModifierKeys.Shift),
|
|
Action = _ =>
|
|
{
|
|
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", ParentDirectory));
|
|
return true;
|
|
}
|
|
});
|
|
|
|
contextMenus.Add(
|
|
new ContextMenuResult
|
|
{
|
|
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
|
|
Title = api.GetTranslation("wox_plugin_program_open_in_console"),
|
|
Glyph = "\xE756",
|
|
FontFamily = "Segoe MDL2 Assets",
|
|
AcceleratorKey = Key.C,
|
|
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
|
|
Action = (context) =>
|
|
{
|
|
try
|
|
{
|
|
Helper.OpenInConsole(ParentDirectory);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Exception($"|Microsoft.Plugin.Program.Win32.ContextMenu| Failed to open {Name} in console, {e.Message}", e);
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
return contextMenus;
|
|
}
|
|
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return ExecutableName;
|
|
}
|
|
|
|
private static Win32 Win32Program(string path)
|
|
{
|
|
try
|
|
{
|
|
var p = new Win32
|
|
{
|
|
Name = Path.GetFileNameWithoutExtension(path),
|
|
ExecutableName = Path.GetFileName(path),
|
|
IcoPath = path,
|
|
FullPath = path.ToLower(),
|
|
UniqueIdentifier = path,
|
|
ParentDirectory = Directory.GetParent(path).FullName,
|
|
Description = string.Empty,
|
|
Valid = true,
|
|
Enabled = true,
|
|
AppType = (uint)ApplicationTypes.WIN32_APPLICATION
|
|
};
|
|
return p;
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
|
|
$"|Permission denied when trying to load the program from {path}", e);
|
|
|
|
return new Win32() { Valid = false, Enabled = false };
|
|
}
|
|
}
|
|
|
|
// This function filters Internet Shortcut programs
|
|
private static Win32 InternetShortcutProgram(string path)
|
|
{
|
|
string[] lines = System.IO.File.ReadAllLines(path);
|
|
string appName = string.Empty;
|
|
string iconPath = string.Empty;
|
|
string urlPath = string.Empty;
|
|
string scheme = string.Empty;
|
|
bool validApp = false;
|
|
|
|
Regex InternetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run)\/|^com\.epicgames\.launcher:\/\/apps\/");
|
|
|
|
const string urlPrefix = "URL=";
|
|
const string iconFilePrefix = "IconFile=";
|
|
|
|
foreach(string line in lines)
|
|
{
|
|
if(line.StartsWith(urlPrefix))
|
|
{
|
|
urlPath = line.Substring(urlPrefix.Length);
|
|
Uri uri = new Uri(urlPath);
|
|
|
|
// To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname
|
|
if(InternetShortcutURLPrefixes.Match(urlPath).Success)
|
|
{
|
|
validApp = true;
|
|
}
|
|
}
|
|
|
|
if(line.StartsWith(iconFilePrefix))
|
|
{
|
|
iconPath = line.Substring(iconFilePrefix.Length);
|
|
}
|
|
}
|
|
|
|
if(!validApp)
|
|
{
|
|
return new Win32() { Valid = false, Enabled = false };
|
|
}
|
|
|
|
try
|
|
{
|
|
var p = new Win32
|
|
{
|
|
Name = Path.GetFileNameWithoutExtension(path),
|
|
ExecutableName = Path.GetFileName(path),
|
|
IcoPath = iconPath,
|
|
FullPath = urlPath,
|
|
UniqueIdentifier = path,
|
|
ParentDirectory = Directory.GetParent(path).FullName,
|
|
Valid = true,
|
|
Enabled = true,
|
|
AppType = (uint)ApplicationTypes.INTERNET_SHORTCUT_APPLICATION
|
|
};
|
|
return p;
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|InternetShortcutProgram|{path}" +
|
|
$"|Permission denied when trying to load the program from {path}", e);
|
|
|
|
return new Win32() { Valid = false, Enabled = false };
|
|
}
|
|
}
|
|
|
|
private static Win32 LnkProgram(string path)
|
|
{
|
|
var program = Win32Program(path);
|
|
try
|
|
{
|
|
const int MAX_PATH = 260;
|
|
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
|
ShellLinkHelper _helper = new ShellLinkHelper();
|
|
string target = _helper.retrieveTargetPath(path);
|
|
|
|
if (!string.IsNullOrEmpty(target))
|
|
{
|
|
var extension = Extension(target);
|
|
if (extension == ExeExtension && File.Exists(target))
|
|
{
|
|
program.LnkResolvedPath = program.FullPath;
|
|
program.FullPath = Path.GetFullPath(target).ToLower();
|
|
program.ExecutableName = Path.GetFileName(target);
|
|
program.hasArguments = _helper.hasArguments;
|
|
program.Arguments = _helper.Arguments;
|
|
|
|
var description = _helper.description;
|
|
if (!string.IsNullOrEmpty(description))
|
|
{
|
|
program.Description = description;
|
|
}
|
|
else
|
|
{
|
|
var info = FileVersionInfo.GetVersionInfo(target);
|
|
if (!string.IsNullOrEmpty(info.FileDescription))
|
|
{
|
|
program.Description = info.FileDescription;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return program;
|
|
}
|
|
catch (COMException e)
|
|
{
|
|
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
|
|
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
|
"|Error caused likely due to trying to get the description of the program", e);
|
|
|
|
program.Valid = false;
|
|
return program;
|
|
}
|
|
#if !DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
|
|
catch (Exception e)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
|
"|An unexpected error occurred in the calling method LnkProgram", e);
|
|
|
|
program.Valid = false;
|
|
return program;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private static Win32 ExeProgram(string path)
|
|
{
|
|
try
|
|
{
|
|
var program = Win32Program(path);
|
|
var info = FileVersionInfo.GetVersionInfo(path);
|
|
|
|
if (!string.IsNullOrEmpty(info.FileDescription))
|
|
{
|
|
program.Description = info.FileDescription;
|
|
}
|
|
|
|
return program;
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
|
|
$"|Permission denied when trying to load the program from {path}", e);
|
|
|
|
return new Win32() { Valid = false, Enabled = false };
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes, bool recursiveSearch = true)
|
|
{
|
|
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)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
|
|
"|The directory trying to load the program from does not exist", e);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
|
|
$"|Permission denied when trying to load programs from {currentDirectory}", e);
|
|
}
|
|
|
|
try
|
|
{
|
|
// If the search is set to be non-recursive, then do not enqueue the child directories.
|
|
if(!recursiveSearch)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", SearchOption.TopDirectoryOnly))
|
|
{
|
|
folderQueue.Enqueue(childDirectory);
|
|
}
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|ProgramPaths|{currentDirectory}" +
|
|
$"|Permission denied when trying to load programs from {currentDirectory}", e);
|
|
}
|
|
} while (folderQueue.Any());
|
|
|
|
return files;
|
|
}
|
|
|
|
private static string Extension(string path)
|
|
{
|
|
var extension = Path.GetExtension(path)?.ToLower();
|
|
|
|
if (!string.IsNullOrEmpty(extension))
|
|
{
|
|
return extension.Substring(1);
|
|
}
|
|
else
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private static ParallelQuery<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
|
|
{
|
|
var listToAdd = new List<string>();
|
|
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);
|
|
}
|
|
|
|
|
|
// Function to obtain the list of applications, the locations of which have been added to the env variable PATH
|
|
private static ParallelQuery<Win32> PathEnvironmentPrograms(string[] suffixes)
|
|
{
|
|
|
|
// To get all the locations stored in the PATH env variable
|
|
var pathEnvVariable = Environment.GetEnvironmentVariable("PATH");
|
|
string[] searchPaths = pathEnvVariable.Split(Path.PathSeparator);
|
|
IEnumerable<String> toFilterAllPaths = new List<String>();
|
|
bool isRecursiveSearch = true;
|
|
|
|
foreach(string path in searchPaths)
|
|
{
|
|
if(path.Length > 0)
|
|
{
|
|
// to expand any environment variables present in the path
|
|
string directory = Environment.ExpandEnvironmentVariables(path);
|
|
var paths = ProgramPaths(directory, suffixes, !isRecursiveSearch);
|
|
toFilterAllPaths = toFilterAllPaths.Concat(paths);
|
|
}
|
|
}
|
|
|
|
var allPaths = toFilterAllPaths
|
|
.Distinct()
|
|
.ToArray();
|
|
|
|
var programs1 = allPaths.AsParallel().Where(p => Extension(p).Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(LnkProgram);
|
|
var programs2 = allPaths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(Win32Program);
|
|
var programs3 = allPaths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
|
var programs4 = allPaths.AsParallel().Where(p => Extension(p).Equals(ExeExtension, StringComparison.OrdinalIgnoreCase)).Select(ExeProgram);
|
|
|
|
var allPrograms = programs1.Concat(programs2).Where(p => p.Valid)
|
|
.Concat(programs3).Where(p => p.Valid)
|
|
.Concat(programs4).Where(p => p.Valid)
|
|
.Select( p => { p.AppType = (uint)ApplicationTypes.RUN_COMMAND; return p; });
|
|
|
|
return allPrograms;
|
|
}
|
|
|
|
private static ParallelQuery<Win32> IndexPath(string[] suffixes, List<string> IndexLocation)
|
|
{
|
|
var disabledProgramsList = Main._settings.DisabledProgramSources;
|
|
|
|
IEnumerable<string> toFilter = new List<string>();
|
|
foreach (string location in IndexLocation)
|
|
{
|
|
var _paths = ProgramPaths(location, suffixes);
|
|
toFilter = toFilter.Concat(_paths);
|
|
}
|
|
|
|
var paths = toFilter
|
|
.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1))
|
|
.Select(t1 => t1)
|
|
.Distinct()
|
|
.ToArray();
|
|
|
|
var programs1 = paths.AsParallel().Where(p => Extension(p).Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(LnkProgram);
|
|
var programs2 = paths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(Win32Program);
|
|
var programs3 = paths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
|
|
|
return programs1.Concat(programs2).Where(p => p.Valid).Concat(programs3).Where(p => p.Valid);
|
|
}
|
|
|
|
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes)
|
|
{
|
|
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
|
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
|
|
List<string> IndexLocation = new List<string>() { directory1, directory2};
|
|
|
|
return IndexPath(suffixes, IndexLocation);
|
|
}
|
|
|
|
private static ParallelQuery<Win32> DesktopPrograms(string[] suffixes)
|
|
{
|
|
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
|
List<string> IndexLocation = new List<string>() { directory1 };
|
|
|
|
return IndexPath(suffixes, IndexLocation);
|
|
}
|
|
|
|
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))
|
|
{
|
|
if (root != null)
|
|
{
|
|
programs.AddRange(GetProgramsFromRegistry(root));
|
|
}
|
|
}
|
|
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
|
|
{
|
|
if (root != null)
|
|
{
|
|
programs.AddRange(GetProgramsFromRegistry(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> GetProgramsFromRegistry(RegistryKey root)
|
|
{
|
|
return root
|
|
.GetSubKeyNames()
|
|
.Select(x => GetProgramPathFromRegistrySubKeys(root, x))
|
|
.Distinct()
|
|
.Select(x => GetProgramFromPath(x));
|
|
}
|
|
|
|
private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey)
|
|
{
|
|
var path = string.Empty;
|
|
try
|
|
{
|
|
using (var key = root.OpenSubKey(subkey))
|
|
{
|
|
if (key == null)
|
|
return string.Empty;
|
|
|
|
var defaultValue = string.Empty;
|
|
path = key.GetValue(defaultValue) as string;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(path))
|
|
return string.Empty;
|
|
|
|
// fix path like this: ""\"C:\\folder\\executable.exe\""
|
|
return path = path.Trim('"', ' ');
|
|
}
|
|
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
|
{
|
|
ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" +
|
|
$"|Permission denied when trying to load the program from {path}", e);
|
|
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private static Win32 GetProgramFromPath(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 class removeDuplicatesComparer : IEqualityComparer<Win32>
|
|
{
|
|
public bool Equals(Win32 app1, Win32 app2)
|
|
{
|
|
|
|
if(!string.IsNullOrEmpty(app1.Name) && !string.IsNullOrEmpty(app2.Name)
|
|
&& !string.IsNullOrEmpty(app1.ExecutableName) && !string.IsNullOrEmpty(app2.ExecutableName)
|
|
&& !string.IsNullOrEmpty(app1.FullPath) && !string.IsNullOrEmpty(app2.FullPath))
|
|
{
|
|
return app1.Name.Equals(app2.Name, StringComparison.OrdinalIgnoreCase)
|
|
&& app1.ExecutableName.Equals(app2.ExecutableName, StringComparison.OrdinalIgnoreCase)
|
|
&& app1.FullPath.Equals(app2.FullPath, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Ref : https://stackoverflow.com/questions/2730865/how-do-i-calculate-a-good-hash-code-for-a-list-of-strings
|
|
public int GetHashCode(Win32 obj)
|
|
{
|
|
int namePrime = 13;
|
|
int executablePrime = 17;
|
|
int fullPathPrime = 31;
|
|
|
|
int result = 1;
|
|
result = result * namePrime + obj.Name.ToLowerInvariant().GetHashCode();
|
|
result = result * executablePrime + obj.ExecutableName.ToLowerInvariant().GetHashCode();
|
|
result = result * fullPathPrime + obj.FullPath.ToLowerInvariant().GetHashCode();
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Deduplication code
|
|
public static Func<ParallelQuery<Win32>, Win32[]> DeduplicatePrograms = (programs) =>
|
|
{
|
|
var uniqueExePrograms = programs.Where(x => !(string.IsNullOrEmpty(x.LnkResolvedPath) && (Extension(x.FullPath) == ExeExtension) && !(x.AppType == (uint)ApplicationTypes.RUN_COMMAND)));
|
|
var uniquePrograms = uniqueExePrograms.Distinct(new removeDuplicatesComparer());
|
|
return uniquePrograms.ToArray();
|
|
};
|
|
|
|
public static Win32[] All(Settings settings)
|
|
{
|
|
try
|
|
{
|
|
var 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)
|
|
{
|
|
var startMenu = StartMenuPrograms(settings.ProgramSuffixes);
|
|
programs = programs.Concat(startMenu);
|
|
}
|
|
|
|
if (settings.EnablePathEnvironmentVariableSource)
|
|
{
|
|
var appPathEnvironment = PathEnvironmentPrograms(settings.ProgramSuffixes);
|
|
programs = programs.Concat(appPathEnvironment);
|
|
}
|
|
|
|
if (settings.EnableDesktopSource)
|
|
{
|
|
var desktop = DesktopPrograms(settings.ProgramSuffixes);
|
|
programs = programs.Concat(desktop);
|
|
}
|
|
|
|
return DeduplicatePrograms(programs);
|
|
}
|
|
#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
|
|
catch (Exception e)
|
|
{
|
|
throw e;
|
|
}
|
|
#endif
|
|
|
|
#if !DEBUG //Only do a catch all in production.
|
|
catch (Exception e)
|
|
{
|
|
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
|
|
|
|
return new Win32[0];
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|