Functionality to detect Win32 apps which are installed, deleted or renamed while PowerToys is running (#4960)

* Added file system wrapper and interface

* added win32program repository which would load store app and also handle new apps being added/deleted

* Added event handlers to win32 program repo

* added paths to monitor and setting FSWs for each location

* Events firing as expected

* filter extensions added, events fire as expected

* override gethashcode so that duplicates don't show up. OnCreated and OnDeleted events trigger as expected

* implemented setter for filters in FileSystemWatcher

* Rename adds item but does not seem to delete the previous app

* catching an exception when a duplicate item is inserted

* Removed notify filter for directory because we only need to monitor files

* Added exe programs to be indexed in the desktop and startmenu

* created a new class to init FileSystemHelpers instead of main

* Added fix for shortcut applications to work as expected while renaming and deleting them

* Added wrappers for file system operations

* Added some tests

* Added all tests for appref-ms and added a condition to search in sub directories

* Added tests for Exe applications

* Added lnk app tests

* Added tests for shortcut applications

* removed unnecessary wrappers

* override Equals for win32

* removed debug statements

* Fixed exe issue

* Fixed internet shortcut exception

* fixed renaming shortcut apps

* Added a retry block for ReadAllLines

* capitalized method name - RetrieveTargetPath

* made naming consistent, helper variables start with underscore

* Added the exception condition back

* renamed Win32ProgramRepositoryHelper to Win32ProgramFileSystemWatchers

* made win32Program repository variable static

* changed list to ilist

* disposed file system watchers

* make retrieveTargetPath upper case in tests
This commit is contained in:
Alekhya
2020-07-17 22:32:21 -07:00
committed by GitHub
parent 034079b441
commit d09253e532
17 changed files with 894 additions and 58 deletions

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Plugin.Program.Programs
{
public interface IShellLinkHelper
{
string RetrieveTargetPath(string path);
string description { get; set; }
string Arguments { get; set; }
bool hasArguments { get; set; }
}
}

View File

@@ -6,10 +6,11 @@ using System.IO;
using Accessibility;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Policy;
using Microsoft.Plugin.Program.Logger;
namespace Microsoft.Plugin.Program.Programs
{
class ShellLinkHelper
public class ShellLinkHelper : IShellLinkHelper
{
[Flags()]
public enum SLGP_FLAGS
@@ -99,19 +100,29 @@ namespace Microsoft.Plugin.Program.Programs
{
}
// To initialize the app description
public String description = String.Empty;
// Contains the description of the app
public string description { get; set; } = String.Empty;
// Sets to true if the program takes in arguments
public String Arguments = String.Empty;
public bool hasArguments = false;
// Contains the arguments to the app
public string Arguments { get; set; } = String.Empty;
public bool hasArguments { get; set; } = false;
// Retrieve the target path using Shell Link
public string retrieveTargetPath(string path)
public string RetrieveTargetPath(string path)
{
var link = new ShellLink();
const int STGM_READ = 0;
((IPersistFile)link).Load(path, STGM_READ);
try
{
((IPersistFile)link).Load(path, STGM_READ);
}
catch(System.IO.FileNotFoundException ex)
{
ProgramLogger.LogException($"|Win32| ShellLinkHelper.retrieveTargetPath | {path} | Path could not be retrieved", ex);
return String.Empty;
}
var hwnd = new _RemotableHandle();
((IShellLinkW)link).Resolve(ref hwnd, 0);

View File

@@ -15,6 +15,7 @@ using System.Windows.Input;
using System.Reflection;
using System.Text.RegularExpressions;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.FileSystemHelper;
namespace Microsoft.Plugin.Program.Programs
{
@@ -36,6 +37,10 @@ namespace Microsoft.Plugin.Program.Programs
public string Arguments { get; set; } = String.Empty;
public string Location => ParentDirectory;
public uint AppType { get; set; }
// Wrappers for File Operations
public static IFileVersionInfoWrapper _fileVersionInfoWrapper = new FileVersionInfoWrapper();
public static IFileWrapper _fileWrapper = new FileWrapper();
public static IShellLinkHelper _helper = new ShellLinkHelper();
private const string ShortcutExtension = "lnk";
private const string ApplicationReferenceExtension = "appref-ms";
@@ -313,7 +318,7 @@ namespace Microsoft.Plugin.Program.Programs
// This function filters Internet Shortcut programs
private static Win32 InternetShortcutProgram(string path)
{
string[] lines = System.IO.File.ReadAllLines(path);
string[] lines = _fileWrapper.ReadAllLines(path);
string appName = string.Empty;
string iconPath = string.Empty;
string urlPath = string.Empty;
@@ -382,8 +387,8 @@ namespace Microsoft.Plugin.Program.Programs
{
const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH);
ShellLinkHelper _helper = new ShellLinkHelper();
string target = _helper.retrieveTargetPath(path);
string target = _helper.RetrieveTargetPath(path);
if (!string.IsNullOrEmpty(target))
{
@@ -403,8 +408,8 @@ namespace Microsoft.Plugin.Program.Programs
}
else
{
var info = FileVersionInfo.GetVersionInfo(target);
if (!string.IsNullOrEmpty(info.FileDescription))
var info = _fileVersionInfoWrapper.GetVersionInfo(target);
if (!string.IsNullOrEmpty(info?.FileDescription))
{
program.Description = info.FileDescription;
}
@@ -439,9 +444,9 @@ namespace Microsoft.Plugin.Program.Programs
try
{
var program = Win32Program(path);
var info = FileVersionInfo.GetVersionInfo(path);
var info = _fileVersionInfoWrapper.GetVersionInfo(path);
if (!string.IsNullOrEmpty(info.FileDescription))
if (!string.IsNullOrEmpty(info?.FileDescription))
{
program.Description = info.FileDescription;
}
@@ -457,6 +462,46 @@ namespace Microsoft.Plugin.Program.Programs
}
}
// Function to get the Win32 application, given the path to the application
public static Win32 GetAppFromPath(string path)
{
Win32 app = null;
const string exeExtension = ".exe";
const string lnkExtension = ".lnk";
const string urlExtenion = ".url";
const string apprefExtension = ".appref-ms";
string extension = Path.GetExtension(path);
if(extension.Equals(exeExtension, StringComparison.OrdinalIgnoreCase))
{
app = ExeProgram(path);
}
else if(extension.Equals(lnkExtension, StringComparison.OrdinalIgnoreCase))
{
app = LnkProgram(path);
}
else if(extension.Equals(apprefExtension, StringComparison.OrdinalIgnoreCase))
{
app = Win32Program(path);
}
else if(extension.Equals(urlExtenion, StringComparison.OrdinalIgnoreCase))
{
app = InternetShortcutProgram(path);
}
// if the app is valid, only then return the application, else return null
if(app?.Valid ?? false)
{
return app;
}
else
{
return null;
}
}
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes, bool recursiveSearch = true)
{
if (!Directory.Exists(directory))
@@ -609,8 +654,11 @@ namespace Microsoft.Plugin.Program.Programs
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);
var programs4 = paths.AsParallel().Where(p => Extension(p).Equals(ExeExtension, StringComparison.OrdinalIgnoreCase)).Select(ExeProgram);
return programs1.Concat(programs2).Where(p => p.Valid).Concat(programs3).Where(p => p.Valid);
return programs1.Concat(programs2).Where(p => p.Valid)
.Concat(programs3).Where(p => p.Valid)
.Concat(programs4).Where(p => p.Valid);
}
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes)
@@ -712,6 +760,19 @@ namespace Microsoft.Plugin.Program.Programs
return entry;
}
// Overriding the object.GetHashCode() function to aid in removing duplicates while adding and removing apps from the concurrent dictionary storage
public override int GetHashCode()
{
removeDuplicatesComparer _removeDuplicatesHelper = new removeDuplicatesComparer();
return _removeDuplicatesHelper.GetHashCode(this);
}
public override bool Equals(object obj)
{
removeDuplicatesComparer _removeDuplicatesHelper = new removeDuplicatesComparer();
return obj is Win32 win && _removeDuplicatesHelper.Equals(this, win);
}
public class removeDuplicatesComparer : IEqualityComparer<Win32>
{
public bool Equals(Win32 app1, Win32 app2)
@@ -802,6 +863,6 @@ namespace Microsoft.Plugin.Program.Programs
return new Win32[0];
}
#endif
}
}
}
}