[PT Run] Improve the Win32 Program Indexing speed (#11364)
This commit is contained in:
parent
9a1034e122
commit
266aafb700
8
.github/actions/spell-check/expect.txt
vendored
8
.github/actions/spell-check/expect.txt
vendored
|
@ -554,6 +554,7 @@ EXCLUDEFILES
|
|||
EXCLUDEFOLDERS
|
||||
EXCLUDESUBFOLDERS
|
||||
exe
|
||||
Executables
|
||||
executionpolicy
|
||||
exename
|
||||
exif
|
||||
|
@ -669,6 +670,7 @@ hardcoded
|
|||
Hardlines
|
||||
HARDWAREINPUT
|
||||
hashcode
|
||||
Hashset
|
||||
hbitmap
|
||||
hbmp
|
||||
hbr
|
||||
|
@ -1595,6 +1597,7 @@ REGCLS
|
|||
regedit
|
||||
regex
|
||||
REGISTERCLASSFAILED
|
||||
Registery
|
||||
registrypath
|
||||
regkey
|
||||
reimplementing
|
||||
|
@ -1706,6 +1709,7 @@ serizalization
|
|||
serverside
|
||||
SETCONTEXT
|
||||
setcursor
|
||||
setenv
|
||||
SETFOCUS
|
||||
SETFOREGROUND
|
||||
SETICON
|
||||
|
@ -1742,8 +1746,8 @@ shlwapi
|
|||
shobjidl
|
||||
SHORTCUTATLEAST
|
||||
shortcutcontrol
|
||||
shortcutguide
|
||||
Shortcutguide
|
||||
shortcutguide
|
||||
SHORTCUTMAXONEACTIONKEY
|
||||
SHORTCUTNOREPEATEDMODIFIER
|
||||
SHORTCUTONEACTIONKEY
|
||||
|
@ -2160,8 +2164,8 @@ windowsx
|
|||
windowwalker
|
||||
winerror
|
||||
WINEVENT
|
||||
winexe
|
||||
winevt
|
||||
winexe
|
||||
winforms
|
||||
winfx
|
||||
winget
|
||||
|
|
|
@ -50,6 +50,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
Name = "Microsoft Azure Command Prompt - v2.9",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
Arguments = @"/E:ON /V:ON /K ""C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\\bin\setenv.cmd""",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk",
|
||||
AppType = Win32Program.ApplicationType.Win32Application,
|
||||
};
|
||||
|
@ -59,6 +60,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
Name = "x64 Native Tools Command Prompt for VS 2019",
|
||||
ExecutableName = "cmd.exe",
|
||||
FullPath = "c:\\windows\\system32\\cmd.exe",
|
||||
Arguments = @"/k ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat""",
|
||||
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk",
|
||||
AppType = Win32Program.ApplicationType.Win32Application,
|
||||
};
|
||||
|
@ -262,10 +264,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
List<Win32Program> apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, apps.Length);
|
||||
Assert.AreEqual(1, apps.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -279,10 +281,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
List<Win32Program> apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, apps.Length);
|
||||
Assert.AreEqual(1, apps.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -295,10 +297,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
List<Win32Program> apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, apps.Length);
|
||||
Assert.AreEqual(1, apps.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -312,10 +314,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
List<Win32Program> apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, apps.Length);
|
||||
Assert.AreEqual(1, apps.Count);
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath));
|
||||
}
|
||||
|
||||
|
@ -331,10 +333,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||
};
|
||||
|
||||
// Act
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
List<Win32Program> apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(3, apps.Length);
|
||||
Assert.AreEqual(3, apps.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -258,7 +258,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
|||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFile>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
mockFile.Setup(m => m.ReadLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
string fullPath = directory + "\\" + path;
|
||||
|
@ -281,7 +281,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
|||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFile>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
mockFile.Setup(m => m.ReadLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
string oldFullPath = directory + "\\" + oldpath;
|
||||
|
|
|
@ -17,6 +17,31 @@ namespace Microsoft.Plugin.Program.Logger
|
|||
/// </summary>
|
||||
internal static class ProgramLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Logs an warning
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
internal static void Warn(string message, Exception ex, Type fullClassName, string loadingProgramPath, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
string calledMethod = "Not available";
|
||||
|
||||
if (ex != null)
|
||||
{
|
||||
string exceptionCalledMethod = ex.TargetSite != null ? ex.TargetSite.ToString() : ex.StackTrace;
|
||||
if (!string.IsNullOrEmpty(exceptionCalledMethod))
|
||||
{
|
||||
calledMethod = exceptionCalledMethod;
|
||||
}
|
||||
}
|
||||
|
||||
var msg = $"\n\t\tProgram path: {loadingProgramPath}"
|
||||
+ $"\n\t\tException thrown in called method: {calledMethod}"
|
||||
+ $"\n\t\tPossible interpretation of the error: {message}";
|
||||
|
||||
// removed looping logic since that is inside Log class
|
||||
Log.Warn(msg, fullClassName, methodName, sourceFilePath, sourceLineNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an exception
|
||||
/// </summary>
|
||||
|
@ -29,7 +54,7 @@ namespace Microsoft.Plugin.Program.Logger
|
|||
|
||||
if (IsKnownWinProgramError(ex, methodName) || IsKnownUWPProgramError(ex, methodName))
|
||||
{
|
||||
possibleResolution = "Can be ignored and Wox should still continue, however the program may not be loaded";
|
||||
possibleResolution = "Can be ignored and PowerToys Run should still continue, however the program may not be loaded";
|
||||
errorStatus = "KNOWN";
|
||||
}
|
||||
|
||||
|
|
|
@ -53,20 +53,6 @@ namespace Microsoft.Plugin.Program
|
|||
|
||||
// Initialize the Win32ProgramRepository with the settings object
|
||||
_win32ProgramRepository = new Win32ProgramRepository(_win32ProgramRepositoryHelper.FileSystemWatchers.Cast<IFileSystemWatcherWrapper>().ToList(), new BinaryStorage<IList<Programs.Win32Program>>("Win32"), Settings, _win32ProgramRepositoryHelper.PathsToWatch);
|
||||
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("Microsoft.Plugin.Program.Main - Win32Program index cost", _win32ProgramRepository.IndexPrograms);
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("Microsoft.Plugin.Program.Main - Package index cost", _packageRepository.IndexPrograms);
|
||||
});
|
||||
|
||||
Task.WaitAll(a, b);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
|
@ -118,6 +104,20 @@ namespace Microsoft.Plugin.Program
|
|||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
|
||||
UpdateUWPIconPath(_context.API.GetCurrentTheme());
|
||||
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("Microsoft.Plugin.Program.Main - Win32Program index cost", _win32ProgramRepository.IndexPrograms);
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("Microsoft.Plugin.Program.Main - Package index cost", _packageRepository.IndexPrograms);
|
||||
});
|
||||
|
||||
Task.WaitAll(a, b);
|
||||
|
||||
Settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
public void OnThemeChanged(Theme currentTheme, Theme newTheme)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
@ -11,7 +11,6 @@ using System.IO.Abstractions;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
@ -28,6 +27,8 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
[Serializable]
|
||||
public class Win32Program : IProgram
|
||||
{
|
||||
public static readonly Win32Program InvalidProgram = new Win32Program { Valid = false, Enabled = false };
|
||||
|
||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||
private static readonly IPath Path = FileSystem.Path;
|
||||
private static readonly IFile File = FileSystem.File;
|
||||
|
@ -53,7 +54,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public bool HasArguments { get; set; }
|
||||
public bool HasArguments => !string.IsNullOrEmpty(Arguments);
|
||||
|
||||
public string Arguments { get; set; } = string.Empty;
|
||||
|
||||
|
@ -104,10 +105,12 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
// 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
|
||||
// Reference : https://codereview.chromium.org/399045
|
||||
// Using Ordinal IgnoreCase since this is used internally
|
||||
bool isWebApplication = FullPath.Contains(ProxyWebApp, StringComparison.OrdinalIgnoreCase) && Arguments.Contains(AppIdArgument, StringComparison.OrdinalIgnoreCase);
|
||||
return isWebApplication;
|
||||
return !string.IsNullOrEmpty(FullPath) &&
|
||||
!string.IsNullOrEmpty(Arguments) &&
|
||||
FullPath.Contains(ProxyWebApp, StringComparison.OrdinalIgnoreCase) &&
|
||||
Arguments.Contains(AppIdArgument, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Condition to Filter pinned Web Applications or PWAs when searching for the main application
|
||||
|
@ -119,9 +122,6 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
return false;
|
||||
}
|
||||
|
||||
// Set the subtitle to 'Web Application'
|
||||
AppType = ApplicationType.WebApplication;
|
||||
|
||||
string[] subqueries = query?.Split() ?? Array.Empty<string>();
|
||||
bool nameContainsQuery = false;
|
||||
bool pathContainsQuery = false;
|
||||
|
@ -145,35 +145,26 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
|
||||
// Function to set the subtitle based on the Type of application
|
||||
private string SetSubtitle()
|
||||
private string GetSubtitle()
|
||||
{
|
||||
if (AppType == ApplicationType.Win32Application || AppType == ApplicationType.ShortcutApplication || AppType == ApplicationType.ApprefApplication)
|
||||
switch (AppType)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_win32_application;
|
||||
}
|
||||
else if (AppType == ApplicationType.InternetShortcutApplication)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_internet_shortcut_application;
|
||||
}
|
||||
else if (AppType == ApplicationType.WebApplication)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_web_application;
|
||||
}
|
||||
else if (AppType == ApplicationType.RunCommand)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_run_command;
|
||||
}
|
||||
else if (AppType == ApplicationType.Folder)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_folder_type;
|
||||
}
|
||||
else if (AppType == ApplicationType.GenericFile)
|
||||
{
|
||||
return Properties.Resources.powertoys_run_plugin_program_generic_file_type;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
case ApplicationType.Win32Application:
|
||||
case ApplicationType.ShortcutApplication:
|
||||
case ApplicationType.ApprefApplication:
|
||||
return Properties.Resources.powertoys_run_plugin_program_win32_application;
|
||||
case ApplicationType.InternetShortcutApplication:
|
||||
return Properties.Resources.powertoys_run_plugin_program_internet_shortcut_application;
|
||||
case ApplicationType.WebApplication:
|
||||
return Properties.Resources.powertoys_run_plugin_program_web_application;
|
||||
case ApplicationType.RunCommand:
|
||||
return Properties.Resources.powertoys_run_plugin_program_run_command;
|
||||
case ApplicationType.Folder:
|
||||
return Properties.Resources.powertoys_run_plugin_program_folder_type;
|
||||
case ApplicationType.GenericFile:
|
||||
return Properties.Resources.powertoys_run_plugin_program_generic_file_type;
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +217,9 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
|
||||
var result = new Result
|
||||
{
|
||||
SubTitle = SetSubtitle(),
|
||||
// To set the title for the result to always be the name of the application
|
||||
Title = Name,
|
||||
SubTitle = GetSubtitle(),
|
||||
IcoPath = IcoPath,
|
||||
Score = score,
|
||||
ContextData = this,
|
||||
|
@ -241,8 +234,6 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
},
|
||||
};
|
||||
|
||||
// To set the title for the result to always be the name of the application
|
||||
result.Title = Name;
|
||||
result.SetTitleHighlightData(StringMatcher.FuzzySearch(query, Name).MatchData);
|
||||
|
||||
// Using CurrentCulture since this is user facing
|
||||
|
@ -312,7 +303,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
try
|
||||
{
|
||||
Wox.Infrastructure.Helper.OpenInConsole(ParentDirectory);
|
||||
Helper.OpenInConsole(ParentDirectory);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -349,7 +340,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
try
|
||||
{
|
||||
var p = new Win32Program
|
||||
return new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
|
@ -364,35 +355,36 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
Enabled = true,
|
||||
AppType = ApplicationType.Win32Application,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method CreateWin32Program at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex InternetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run)\/|^com\.epicgames\.launcher:\/\/apps\/", RegexOptions.Compiled);
|
||||
|
||||
// This function filters Internet Shortcut programs
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Any error in InternetShortcutProgram should not prevent other programs from loading.")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "User facing path needs to be shown in lowercase.")]
|
||||
private static Win32Program InternetShortcutProgram(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] lines = FileWrapper.ReadAllLines(path);
|
||||
// We don't want to read the whole file if we don't need to
|
||||
var lines = FileWrapper.ReadLines(path);
|
||||
string iconPath = string.Empty;
|
||||
string urlPath = string.Empty;
|
||||
bool validApp = false;
|
||||
|
||||
Regex internetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run)\/|^com\.epicgames\.launcher:\/\/apps\/");
|
||||
|
||||
const string urlPrefix = "URL=";
|
||||
const string iconFilePrefix = "IconFile=";
|
||||
|
||||
|
@ -403,65 +395,62 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
urlPath = line.Substring(urlPrefix.Length);
|
||||
|
||||
try
|
||||
if (!Uri.TryCreate(urlPath, UriKind.RelativeOrAbsolute, out Uri _))
|
||||
{
|
||||
Uri uri = new Uri(urlPath);
|
||||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
// To catch the exception if the uri cannot be parsed.
|
||||
// Link to watson crash: https://watsonportal.microsoft.com/Failure?FailureSearchText=5f871ea7-e886-911f-1b31-131f63f6655b
|
||||
ProgramLogger.Exception($"url could not be parsed", e, MethodBase.GetCurrentMethod().DeclaringType, urlPath);
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
ProgramLogger.Warn("url could not be parsed", null, MethodBase.GetCurrentMethod().DeclaringType, urlPath);
|
||||
return InvalidProgram;
|
||||
}
|
||||
|
||||
// To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname
|
||||
if (internetShortcutURLPrefixes.Match(urlPath).Success)
|
||||
if (InternetShortcutURLPrefixes.Match(urlPath).Success)
|
||||
{
|
||||
validApp = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
else if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iconPath = line.Substring(iconFilePrefix.Length);
|
||||
}
|
||||
|
||||
// If we resolved an urlPath & and an iconPath quit reading the file
|
||||
if (!string.IsNullOrEmpty(urlPath) && !string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validApp)
|
||||
{
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var p = new Win32Program
|
||||
return new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
IcoPath = iconPath,
|
||||
FullPath = urlPath,
|
||||
FullPath = urlPath.ToLowerInvariant(),
|
||||
UniqueIdentifier = path,
|
||||
ParentDirectory = Directory.GetParent(path).FullName,
|
||||
Valid = true,
|
||||
Enabled = true,
|
||||
AppType = ApplicationType.InternetShortcutApplication,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method InternetShortcutProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,33 +461,33 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
try
|
||||
{
|
||||
var program = CreateWin32Program(path);
|
||||
const int MAX_PATH = 260;
|
||||
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
||||
|
||||
string target = ShellLinkHelper.RetrieveTargetPath(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
if (!string.IsNullOrEmpty(target) && (File.Exists(target) || Directory.Exists(target)))
|
||||
{
|
||||
if (File.Exists(target) || Directory.Exists(target))
|
||||
program.LnkResolvedPath = program.FullPath;
|
||||
|
||||
// Using CurrentCulture since this is user facing
|
||||
program.FullPath = Path.GetFullPath(target).ToLowerInvariant();
|
||||
|
||||
program.Arguments = ShellLinkHelper.Arguments;
|
||||
|
||||
// A .lnk could be a (Chrome) PWA, set correct AppType
|
||||
program.AppType = program.IsWebApplication()
|
||||
? ApplicationType.WebApplication
|
||||
: GetAppTypeFromPath(target);
|
||||
|
||||
var description = ShellLinkHelper.Description;
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
program.LnkResolvedPath = program.FullPath;
|
||||
|
||||
// Using InvariantCulture since this is user facing
|
||||
program.FullPath = Path.GetFullPath(target).ToLowerInvariant();
|
||||
program.AppType = GetAppTypeFromPath(target);
|
||||
|
||||
var description = ShellLinkHelper.Description;
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
program.Description = description;
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = FileVersionInfoWrapper.GetVersionInfo(target);
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = description;
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = FileVersionInfoWrapper.GetVersionInfo(target);
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = info.FileDescription;
|
||||
}
|
||||
program.Description = info.FileDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +501,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method LnkProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,7 +512,6 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
var program = CreateWin32Program(path);
|
||||
var info = FileVersionInfoWrapper.GetVersionInfo(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = info.FileDescription;
|
||||
|
@ -533,21 +521,21 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.Exception($"|Unable to locate exe file at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Warn($"|Unable to locate exe file at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method ExeProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,33 +548,30 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
|
||||
string extension = Extension(path);
|
||||
ApplicationType appType = ApplicationType.GenericFile;
|
||||
|
||||
// Using OrdinalIgnoreCase since these are used internally with paths
|
||||
if (ExecutableApplicationExtensions.Contains(extension))
|
||||
{
|
||||
appType = ApplicationType.Win32Application;
|
||||
return ApplicationType.Win32Application;
|
||||
}
|
||||
else if (extension.Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
appType = ApplicationType.ShortcutApplication;
|
||||
return ApplicationType.ShortcutApplication;
|
||||
}
|
||||
else if (extension.Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
appType = ApplicationType.ApprefApplication;
|
||||
return ApplicationType.ApprefApplication;
|
||||
}
|
||||
else if (extension.Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
appType = ApplicationType.InternetShortcutApplication;
|
||||
return ApplicationType.InternetShortcutApplication;
|
||||
}
|
||||
|
||||
// If the path exists, check if it is a directory
|
||||
else if (DirectoryWrapper.Exists(path))
|
||||
else if (string.IsNullOrEmpty(extension) && DirectoryWrapper.Exists(path))
|
||||
{
|
||||
appType = ApplicationType.Folder;
|
||||
return ApplicationType.Folder;
|
||||
}
|
||||
|
||||
return appType;
|
||||
return ApplicationType.GenericFile;
|
||||
}
|
||||
|
||||
// Function to get the Win32 application, given the path to the application
|
||||
|
@ -597,37 +582,35 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Win32Program app = null;
|
||||
|
||||
ApplicationType appType = GetAppTypeFromPath(path);
|
||||
|
||||
if (appType == ApplicationType.Win32Application)
|
||||
Win32Program app;
|
||||
switch (GetAppTypeFromPath(path))
|
||||
{
|
||||
app = ExeProgram(path);
|
||||
}
|
||||
else if (appType == ApplicationType.ShortcutApplication)
|
||||
{
|
||||
app = LnkProgram(path);
|
||||
}
|
||||
else if (appType == ApplicationType.ApprefApplication)
|
||||
{
|
||||
app = CreateWin32Program(path);
|
||||
app.AppType = ApplicationType.ApprefApplication;
|
||||
}
|
||||
else if (appType == ApplicationType.InternetShortcutApplication)
|
||||
{
|
||||
app = InternetShortcutProgram(path);
|
||||
case ApplicationType.Win32Application:
|
||||
app = ExeProgram(path);
|
||||
break;
|
||||
case ApplicationType.ShortcutApplication:
|
||||
app = LnkProgram(path);
|
||||
break;
|
||||
case ApplicationType.ApprefApplication:
|
||||
app = CreateWin32Program(path);
|
||||
app.AppType = ApplicationType.ApprefApplication;
|
||||
break;
|
||||
case ApplicationType.InternetShortcutApplication:
|
||||
app = InternetShortcutProgram(path);
|
||||
break;
|
||||
case ApplicationType.WebApplication:
|
||||
case ApplicationType.RunCommand:
|
||||
case ApplicationType.Folder:
|
||||
case ApplicationType.GenericFile:
|
||||
default:
|
||||
app = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// if the app is valid, only then return the application, else return null
|
||||
if (app?.Valid ?? false)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return app?.Valid == true
|
||||
? app
|
||||
: null;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Minimise the effect of error on other programs")]
|
||||
|
@ -655,13 +638,13 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
catch (DirectoryNotFoundException e)
|
||||
{
|
||||
ProgramLogger.Exception("|The directory trying to load the program from does not exist", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
ProgramLogger.Warn("|The directory trying to load the program from does not exist", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -683,14 +666,14 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.Exception($"|An unexpected error occurred in the calling method ProgramPaths at {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
}
|
||||
while (folderQueue.Any());
|
||||
while (folderQueue.Count > 0);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
@ -701,44 +684,23 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
// Using InvariantCulture since this is user facing
|
||||
var extension = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return extension.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return !string.IsNullOrEmpty(extension)
|
||||
? extension.Substring(1)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32Program> UnregisteredPrograms(List<ProgramSource> sources, IList<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 => ExecutableApplicationExtensions.Contains(Extension(p))).Select(ExeProgram);
|
||||
var programs2 = paths.AsParallel().Where(p => Extension(p) == ShortcutExtension).Select(LnkProgram);
|
||||
var programs3 = from p in paths.AsParallel()
|
||||
let e = Extension(p)
|
||||
where e != ShortcutExtension && !ExecutableApplicationExtensions.Contains(e)
|
||||
select CreateWin32Program(p);
|
||||
return programs1.Concat(programs2).Where(p => p.Valid).Concat(programs3).Where(p => p.Valid);
|
||||
}
|
||||
private static IEnumerable<string> CustomProgramPaths(IEnumerable<ProgramSource> sources, IList<string> suffixes)
|
||||
=> sources?.Where(programSource => Directory.Exists(programSource.Location) && programSource.Enabled)
|
||||
.SelectMany(programSource => ProgramPaths(programSource.Location, suffixes))
|
||||
.ToList() ?? Enumerable.Empty<string>();
|
||||
|
||||
// Function to obtain the list of applications, the locations of which have been added to the env variable PATH
|
||||
private static ParallelQuery<Win32Program> PathEnvironmentPrograms(IList<string> suffixes)
|
||||
private static IEnumerable<string> PathEnvironmentProgramPaths(IList<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>();
|
||||
var toFilterAllPaths = new List<string>();
|
||||
bool isRecursiveSearch = true;
|
||||
|
||||
foreach (string path in searchPaths)
|
||||
|
@ -748,89 +710,47 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
// to expand any environment variables present in the path
|
||||
string directory = Environment.ExpandEnvironmentVariables(path);
|
||||
var paths = ProgramPaths(directory, suffixes, !isRecursiveSearch);
|
||||
toFilterAllPaths = toFilterAllPaths.Concat(paths);
|
||||
toFilterAllPaths.AddRange(paths);
|
||||
}
|
||||
}
|
||||
|
||||
var allPaths = toFilterAllPaths
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
// Using OrdinalIgnoreCase since this is used internally with paths
|
||||
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(CreateWin32Program);
|
||||
var programs3 = allPaths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
||||
var programs4 = allPaths.AsParallel().Where(p => ExecutableApplicationExtensions.Contains(Extension(p))).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 = ApplicationType.RunCommand;
|
||||
return p;
|
||||
});
|
||||
|
||||
return allPrograms;
|
||||
return toFilterAllPaths;
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32Program> IndexPath(IList<string> suffixes, List<string> indexLocation)
|
||||
{
|
||||
var disabledProgramsList = Main.Settings.DisabledProgramSources;
|
||||
private static List<string> IndexPath(IList<string> suffixes, List<string> indexLocations)
|
||||
=> indexLocations
|
||||
.SelectMany(indexLocation => ProgramPaths(indexLocation, suffixes))
|
||||
.ToList();
|
||||
|
||||
IEnumerable<string> toFilter = new List<string>();
|
||||
foreach (string location in indexLocation)
|
||||
{
|
||||
var programPaths = ProgramPaths(location, suffixes);
|
||||
toFilter = toFilter.Concat(programPaths);
|
||||
}
|
||||
|
||||
var paths = toFilter
|
||||
.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1))
|
||||
.Select(t1 => t1)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
// Using OrdinalIgnoreCase since this is used internally with paths
|
||||
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(CreateWin32Program);
|
||||
var programs3 = paths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
||||
var programs4 = paths.AsParallel().Where(p => ExecutableApplicationExtensions.Contains(Extension(p))).Select(ExeProgram);
|
||||
|
||||
return programs1.Concat(programs2).Where(p => p.Valid)
|
||||
.Concat(programs3).Where(p => p.Valid)
|
||||
.Concat(programs4).Where(p => p.Valid);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32Program> StartMenuPrograms(IList<string> suffixes)
|
||||
private static IEnumerable<string> StartMenuProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
|
||||
List<string> indexLocation = new List<string>() { directory1, directory2 };
|
||||
var indexLocation = new List<string>() { directory1, directory2 };
|
||||
|
||||
return IndexPath(suffixes, indexLocation);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32Program> DesktopPrograms(IList<string> suffixes)
|
||||
private static IEnumerable<string> DesktopProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
|
||||
|
||||
List<string> indexLocation = new List<string>() { directory1, directory2 };
|
||||
var indexLocation = new List<string>() { directory1, directory2 };
|
||||
|
||||
return IndexPath(suffixes, indexLocation);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32Program> AppPathsPrograms(IList<string> suffixes)
|
||||
private static IEnumerable<string> RegisteryAppProgramPaths(IList<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<Win32Program>();
|
||||
var paths = new List<string>();
|
||||
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
programs.AddRange(GetProgramsFromRegistry(root));
|
||||
paths.AddRange(GetPathsFromRegistry(root));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -838,28 +758,22 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
{
|
||||
if (root != null)
|
||||
{
|
||||
programs.AddRange(GetProgramsFromRegistry(root));
|
||||
paths.AddRange(GetPathsFromRegistry(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;
|
||||
return paths
|
||||
.Where(path => suffixes.Any(suffix => path.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)))
|
||||
.Select(ExpandEnvironmentVariables)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32Program> GetProgramsFromRegistry(RegistryKey root)
|
||||
{
|
||||
return root
|
||||
.GetSubKeyNames()
|
||||
.Select(x => GetProgramPathFromRegistrySubKeys(root, x))
|
||||
.Distinct()
|
||||
.Select(x => GetProgramFromPath(x));
|
||||
}
|
||||
private static IEnumerable<string> GetPathsFromRegistry(RegistryKey root)
|
||||
=> root
|
||||
.GetSubKeyNames()
|
||||
.Select(x => GetPathFromRegisterySubkey(root, x));
|
||||
|
||||
private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey)
|
||||
private static string GetPathFromRegisterySubkey(RegistryKey root, string subkey)
|
||||
{
|
||||
var path = string.Empty;
|
||||
try
|
||||
|
@ -885,89 +799,78 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.Exception($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static Win32Program GetProgramFromPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return new Win32Program();
|
||||
}
|
||||
|
||||
path = Environment.ExpandEnvironmentVariables(path);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new Win32Program();
|
||||
}
|
||||
|
||||
var entry = CreateWin32Program(path);
|
||||
entry.ExecutableName = Path.GetFileName(path);
|
||||
|
||||
return entry;
|
||||
}
|
||||
private static string ExpandEnvironmentVariables(string path) =>
|
||||
path != null
|
||||
? Environment.ExpandEnvironmentVariables(path)
|
||||
: null;
|
||||
|
||||
// Overriding the object.GetHashCode() function to aid in removing duplicates while adding and removing apps from the concurrent dictionary storage
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return RemoveDuplicatesComparer.Default.GetHashCode(this);
|
||||
}
|
||||
=> Win32ProgramEqualityComparer.Default.GetHashCode(this);
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Win32Program win && RemoveDuplicatesComparer.Default.Equals(this, win);
|
||||
}
|
||||
=> obj is Win32Program win32Program && Win32ProgramEqualityComparer.Default.Equals(this, win32Program);
|
||||
|
||||
private class RemoveDuplicatesComparer : IEqualityComparer<Win32Program>
|
||||
private class Win32ProgramEqualityComparer : IEqualityComparer<Win32Program>
|
||||
{
|
||||
public static readonly RemoveDuplicatesComparer Default = new RemoveDuplicatesComparer();
|
||||
public static readonly Win32ProgramEqualityComparer Default = new Win32ProgramEqualityComparer();
|
||||
|
||||
public bool Equals(Win32Program app1, Win32Program 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))
|
||||
if (app1 == null && app2 == null)
|
||||
{
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
return app1.Name.Equals(app2.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& app1.ExecutableName.Equals(app2.ExecutableName, StringComparison.OrdinalIgnoreCase)
|
||||
&& app1.FullPath.Equals(app2.FullPath, StringComparison.OrdinalIgnoreCase);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return app1 != null
|
||||
&& app2 != null
|
||||
&& (app1.Name?.ToUpperInvariant(), app1.ExecutableName?.ToUpperInvariant(), app1.FullPath?.ToUpperInvariant())
|
||||
.Equals((app2.Name?.ToUpperInvariant(), app2.ExecutableName?.ToUpperInvariant(), app2.FullPath?.ToUpperInvariant()));
|
||||
}
|
||||
|
||||
// Ref : https://stackoverflow.com/questions/2730865/how-do-i-calculate-a-good-hash-code-for-a-list-of-strings
|
||||
public int GetHashCode(Win32Program obj)
|
||||
=> (obj.Name?.ToUpperInvariant(), obj.ExecutableName?.ToUpperInvariant(), obj.FullPath?.ToUpperInvariant()).GetHashCode();
|
||||
}
|
||||
|
||||
public static List<Win32Program> DeduplicatePrograms(IEnumerable<Win32Program> programs)
|
||||
=> new HashSet<Win32Program>(programs, Win32ProgramEqualityComparer.Default).ToList();
|
||||
|
||||
private static Win32Program GetProgramFromPath(string path)
|
||||
{
|
||||
var extension = Extension(path);
|
||||
if (ExecutableApplicationExtensions.Contains(extension))
|
||||
{
|
||||
int namePrime = 13;
|
||||
int executablePrime = 17;
|
||||
int fullPathPrime = 31;
|
||||
return ExeProgram(path);
|
||||
}
|
||||
|
||||
int result = 1;
|
||||
|
||||
// Using Ordinal since this is used internally
|
||||
result = (result * namePrime) + obj.Name.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
result = (result * executablePrime) + obj.ExecutableName.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
result = (result * fullPathPrime) + obj.FullPath.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
|
||||
return result;
|
||||
switch (extension)
|
||||
{
|
||||
case ShortcutExtension:
|
||||
return LnkProgram(path);
|
||||
case ApplicationReferenceExtension:
|
||||
return CreateWin32Program(path);
|
||||
case InternetShortcutExtension:
|
||||
return InternetShortcutProgram(path);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplication code
|
||||
public static Win32Program[] DeduplicatePrograms(ParallelQuery<Win32Program> programs)
|
||||
private static Win32Program GetRunCommandProgramFromPath(string path)
|
||||
{
|
||||
var uniqueExePrograms = programs.Where(x => !(string.IsNullOrEmpty(x.LnkResolvedPath) && ExecutableApplicationExtensions.Contains(Extension(x.FullPath)) && x.AppType != ApplicationType.RunCommand));
|
||||
return new HashSet<Win32Program>(uniqueExePrograms, new RemoveDuplicatesComparer()).ToArray();
|
||||
var program = GetProgramFromPath(path);
|
||||
program.AppType = ApplicationType.RunCommand;
|
||||
return program;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Keeping the process alive but logging the exception")]
|
||||
public static Win32Program[] All(ProgramPluginSettings settings)
|
||||
public static IList<Win32Program> All(ProgramPluginSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
|
@ -976,36 +879,45 @@ namespace Microsoft.Plugin.Program.Programs
|
|||
|
||||
try
|
||||
{
|
||||
var programs = new List<Win32Program>().AsParallel();
|
||||
// Set an initial size to an expected size to prevent multiple hashSet resizes
|
||||
const int defaultHashsetSize = 1000;
|
||||
|
||||
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
|
||||
programs = programs.Concat(unregistered);
|
||||
// Multiple paths could have the same programPaths and we don't want to resolve / lookup them multiple times
|
||||
var paths = new HashSet<string>(defaultHashsetSize);
|
||||
var runCommandPaths = new HashSet<string>(defaultHashsetSize);
|
||||
|
||||
if (settings.EnableRegistrySource)
|
||||
// Parallelize multiple sources, and priority based on paths which most likely contain .lnks which are formatted
|
||||
var sources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
|
||||
{
|
||||
var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
|
||||
programs = programs.Concat(appPaths);
|
||||
}
|
||||
(true, () => CustomProgramPaths(settings.ProgramSources, settings.ProgramSuffixes)),
|
||||
(settings.EnableStartMenuSource, () => StartMenuProgramPaths(settings.ProgramSuffixes)),
|
||||
(settings.EnableDesktopSource, () => DesktopProgramPaths(settings.ProgramSuffixes)),
|
||||
(settings.EnableRegistrySource, () => RegisteryAppProgramPaths(settings.ProgramSuffixes)),
|
||||
};
|
||||
|
||||
if (settings.EnableStartMenuSource)
|
||||
// Run commands are always set as AppType "RunCommand"
|
||||
var runCommandSources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
|
||||
{
|
||||
var startMenu = StartMenuPrograms(settings.ProgramSuffixes);
|
||||
programs = programs.Concat(startMenu);
|
||||
}
|
||||
(settings.EnablePathEnvironmentVariableSource, () => PathEnvironmentProgramPaths(settings.ProgramSuffixes)),
|
||||
};
|
||||
|
||||
if (settings.EnablePathEnvironmentVariableSource)
|
||||
{
|
||||
var appPathEnvironment = PathEnvironmentPrograms(settings.ProgramSuffixes);
|
||||
programs = programs.Concat(appPathEnvironment);
|
||||
}
|
||||
var disabledProgramsList = settings.DisabledProgramSources;
|
||||
|
||||
if (settings.EnableDesktopSource)
|
||||
{
|
||||
var desktop = DesktopPrograms(settings.ProgramSuffixes);
|
||||
programs = programs.Concat(desktop);
|
||||
}
|
||||
// Get all paths but exclude all normal .Executables
|
||||
paths.UnionWith(sources
|
||||
.AsParallel()
|
||||
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>())
|
||||
.Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath))
|
||||
.Where(path => !ExecutableApplicationExtensions.Contains(Extension(path))));
|
||||
runCommandPaths.UnionWith(runCommandSources
|
||||
.AsParallel()
|
||||
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>())
|
||||
.Where(programPath => disabledProgramsList.All(x => x.UniqueIdentifier != programPath)));
|
||||
|
||||
return DeduplicatePrograms(programs);
|
||||
var programs = paths.AsParallel().Select(source => GetProgramFromPath(source));
|
||||
var runCommandPrograms = runCommandPaths.AsParallel().Select(source => GetRunCommandProgramFromPath(source));
|
||||
|
||||
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -243,7 +243,7 @@ namespace Microsoft.Plugin.Program.Storage
|
|||
public void IndexPrograms()
|
||||
{
|
||||
var applications = Programs.Win32Program.All(_settings);
|
||||
Log.Info($"Indexed {applications.Length} win32 applications", GetType());
|
||||
Log.Info($"Indexed {applications.Count} win32 applications", GetType());
|
||||
SetList(applications);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue