@@ -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 S etSubtitle( )
private string G etSubtitle( )
{
if ( AppType = = ApplicationType . Win32Application | | AppType = = ApplicationType . ShortcutApplication | | AppType = = ApplicationType . ApprefApplication )
switch ( AppType )
{
return Properties . Resources . powertoys_run_plugin_program_w in32_a pplication;
}
else if ( AppType = = ApplicationType . InternetShortcut Application)
{
return Properties . Resources . powertoys_run_plugin_program_i nternet_s hortcut_a pplication;
}
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 . W in32A pplication:
case ApplicationType . ShortcutApplication :
case ApplicationType . Appref Application:
return Properties . Resources . powertoys_run_plugin_program_win32_application ;
case ApplicationType . I nternetS hortcutA pplication:
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 . Exceptio n( $"|Permission denied when trying to load the program from {path}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , path ) ;
ProgramLogger . War n( $"|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 ( i nternetShortcutURLPrefixes. Match ( urlPath ) . Success )
if ( I nternetShortcutURLPrefixes. 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 . Exceptio n( $"|Permission denied when trying to load the program from {path}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , path ) ;
ProgramLogger . War n( $"|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 = d escription;
}
else
{
var info = FileVersionInfoWrapper . GetVersionInfo ( target ) ;
if ( ! string . IsNullOrEmpty ( info ? . FileDescription ) )
{
program . Description = info . FileDescription ;
}
program . Description = info . FileD escription;
}
}
}
@@ -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 . Exceptio n( $"|Permission denied when trying to load the program from {path}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , path ) ;
ProgramLogger . War n( $"|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 . Exceptio n( $"|Unable to locate exe file at {path}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , path ) ;
ProgramLogger . War n( $"|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 a ppTyp e;
return A pplicationType . GenericFil e;
}
// 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 . Exceptio n( "|The directory trying to load the program from does not exist" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , currentDirectory ) ;
ProgramLogger . War n( "|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 . Exceptio n( $"|Permission denied when trying to load programs from {currentDirectory}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , currentDirectory ) ;
ProgramLogger . War n( $"|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 . Exceptio n( $"|Permission denied when trying to load programs from {currentDirectory}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , currentDirectory ) ;
ProgramLogger . War n( $"|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 Pa ral lelQuery < Win32Program > Unregistered Programs( 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 IEnume rab le< string > Custom ProgramPath s( 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 Pa ral lelQuery < Win32Program > PathEnvironmentPrograms ( IList < string > suffixes )
private static IEnume rab le< string > PathEnvironmentProgramPath s ( 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 ( I List< 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 Pa ral lelQuery < Win32Program > DesktopPrograms ( IList < string > suffixes )
private static IEnume rab le< string > DesktopProgramPath s ( 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 Pa ral lelQuery < Win32Program > AppPaths Programs( IList < string > suffixes )
private static IEnume rab le< string > RegisteryApp ProgramPath s( IList < string > suffixes )
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths" ;
var program s = new List < Win32Program > ( ) ;
var path s = new List < string > ( ) ;
using ( var root = Registry . LocalMachine . OpenSubKey ( appPaths ) )
{
if ( root ! = null )
{
program s . AddRange ( GetProgram sFromRegistry ( root ) ) ;
path s . AddRange ( GetPath sFromRegistry ( root ) ) ;
}
}
@@ -838,28 +758,22 @@ namespace Microsoft.Plugin.Program.Programs
{
if ( root ! = null )
{
program s . AddRange ( GetProgram sFromRegistry ( root ) ) ;
path s . AddRange ( GetPath sFromRegistry ( root ) ) ;
}
}
var disabledProgramsList = Main . Settings . DisabledProgramSources ;
var toFilter = programs . AsParallel ( ) . Where ( p = > suffixes . Contains ( Extension ( p . ExecutableNam e) ) ) ;
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 . InvariantCultureIgnoreCas e) ) )
. Select ( ExpandEnvironmentVariables )
. ToList ( ) ;
}
private static IEnumerable < Win32Program > GetProgram sFromRegistry ( RegistryKey root )
{
return root
. Get SubK eyNames ( )
. Select ( x = > GetProgramPathFromRegistrySubKeys ( root , x ) )
. Distinct ( )
. Select ( x = > GetProgramFromPath ( x ) ) ;
}
private static IEnumerable < string > GetPath sFromRegistry ( RegistryKey root )
= > root
. GetSubKeyNames ( )
. Select ( x = > GetPathFromRegistery Subk ey( root , x ) ) ;
private static string GetProgramP athFromRegistrySubK eys ( RegistryKey root , string subkey )
private static string GetPathFromRegiste rySubk ey ( 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 . Exceptio n( $"|Permission denied when trying to load the program from {path}" , e , MethodBase . GetCurrentMethod ( ) . DeclaringType , path ) ;
ProgramLogger . War n( $"|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 RemoveDuplicates Comparer : IEqualityComparer < Win32Program >
private class Win32ProgramEquality Comparer : IEqualityComparer < Win32Program >
{
public static readonly RemoveDuplicates Comparer Default = new RemoveDuplicates Comparer( ) ;
public static readonly Win32ProgramEquality Comparer Default = new Win32ProgramEquality Comparer( ) ;
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 . N ame . 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 LnkProgr am ( 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 uniqueExeP rograms = p rograms . Where ( x = > ! ( string . IsNullOrEmpty ( x . LnkResolvedPath ) & & ExecutableApplicationExtensions . Contains ( Extension ( x . FullPath ) ) & & x . AppType ! = ApplicationType . RunCommand ) ) ;
return new HashSet < Win32Program > ( uniqueExePrograms , new RemoveDuplicatesComparer ( ) ) . ToArray ( ) ;
var p rogram = GetP rogramFromPath ( 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 = p rograms. Concat ( appPath s) ;
}
( true , ( ) = > CustomProgramPaths ( settings . ProgramSources , settings . ProgramSuffixes ) ) ,
( settings . EnableStartMenuSource , ( ) = > StartMenuP rogramPath s( settings . ProgramSuffixe s) ) ,
( 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 = StartM enu Programs( settings . ProgramSuffixes ) ;
programs = programs . Concat ( startMenu ) ;
}
( settings . EnablePathEnvironmentVariableSource , ( ) = > PathEnvironm ent ProgramPath s( settings . ProgramSuffixes ) ) ,
} ;
if ( settings . En ablePathEnvironmentVariable Source)
{
var appPathEnvironment = PathEnvironmentPrograms ( settings . ProgramSuffixes ) ;
programs = programs . Concat ( appPathEnvironment ) ;
}
var disabledProgramsList = settings . Dis abledProgram Sources ;
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 )
{