diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/ContextMenuLoader.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/ContextMenuLoader.cs index a1465db07b..cf1a3d63b7 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/ContextMenuLoader.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/ContextMenuLoader.cs @@ -6,8 +6,9 @@ using System.Windows; using Wox.Infrastructure.Logger; using Wox.Plugin; using Microsoft.Plugin.Indexer.SearchHelper; -using System.Windows.Input; -using System.Reflection; +using System.Windows.Input; +using System.Reflection; +using System.Threading.Tasks; using Wox.Infrastructure; namespace Microsoft.Plugin.Indexer @@ -22,6 +23,9 @@ namespace Microsoft.Plugin.Indexer File } + // Extensions for adding run as admin context menu item for applications + private readonly string[] appExtensions = { ".exe", ".bat", ".appref-ms", ".lnk" }; + public ContextMenuLoader(PluginInitContext context) { _context = context; @@ -39,6 +43,12 @@ namespace Microsoft.Plugin.Indexer contextMenus.Add(CreateOpenContainingFolderResult(record)); } + // Test to check if File can be Run as admin, if yes, we add a 'run as admin' context menu item + if(CanFileBeRunAsAdmin(record.Path)) + { + contextMenus.Add(CreateRunAsAdminContextMenu(record)); + } + contextMenus.Add(new ContextMenuResult { PluginName = Assembly.GetExecutingAssembly().GetName().Name, @@ -98,8 +108,51 @@ namespace Microsoft.Plugin.Indexer } return contextMenus; - } - + } + + // Function to add the context menu item to run as admin + private ContextMenuResult CreateRunAsAdminContextMenu(SearchResult record) + { + return new ContextMenuResult + { + PluginName = Assembly.GetExecutingAssembly().GetName().Name, + Title = _context.API.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"), + Glyph = "\xE7EF", + FontFamily = "Segoe MDL2 Assets", + AcceleratorKey = Key.Enter, + AcceleratorModifiers = (ModifierKeys.Control | ModifierKeys.Shift), + Action = _ => + { + try + { + Task.Run(() => Helper.RunAsAdmin(record.Path)); + return true; + } + catch(Exception e) + { + Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenu| Failed to run {record.Path} as admin, {e.Message}", e); + return false; + } + + } + }; + } + + // Function to test if the file can be run as admin + private bool CanFileBeRunAsAdmin(string path) + { + string fileExtension = Path.GetExtension(path); + foreach(string extension in appExtensions) + { + if(extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + return false; + } + + private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record) { return new ContextMenuResult diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/de.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/de.xaml index f00dd4e5b2..973a26d871 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/de.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/de.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/en.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/en.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/en.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/en.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/ja.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/ja.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/ja.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/ja.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/pl.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/pl.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/pl.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/pl.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/tr.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/tr.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/tr.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/tr.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-cn.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-cn.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-cn.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-tw.xaml index 9aa0049b2e..6865b58564 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-tw.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Languages/zh-tw.xaml @@ -4,6 +4,7 @@ Copy path (Ctrl+C) Open containing folder (Ctrl+Shift+E) + Run As Administrator (Ctrl+Shift+Enter) Open path in console (Ctrl+Shift+C) Name Path diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj index c41c6568bb..d2709f8ab0 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj @@ -57,12 +57,6 @@ PreserveNewest - - - - <_Parameter1>Wox.Test - - @@ -101,4 +95,11 @@ PreserveNewest + + + + <_Parameter1>Wox.Test + + + diff --git a/src/modules/launcher/Wox.Infrastructure/Helper.cs b/src/modules/launcher/Wox.Infrastructure/Helper.cs index 37554e42ef..16feaeb5b8 100644 --- a/src/modules/launcher/Wox.Infrastructure/Helper.cs +++ b/src/modules/launcher/Wox.Infrastructure/Helper.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Wox.Infrastructure.Logger; namespace Wox.Infrastructure { @@ -76,6 +77,26 @@ namespace Wox.Infrastructure return formatted; } + // Function to run as admin for context menu items + public static void RunAsAdmin(string path) + { + var info = new ProcessStartInfo + { + FileName = path, + WorkingDirectory = Path.GetDirectoryName(path), + Verb = "runas", + UseShellExecute = true + }; + + try + { + Process.Start(info); + } + catch(System.Exception ex) + { + Log.Exception($"Wox.Infrastructure.Helper| Unable to Run {path} as admin : {ex.Message}", ex); + } + } public static Process OpenInConsole(string path) { var processStartInfo = new ProcessStartInfo diff --git a/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs b/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs index b90e46144a..39ca0c6d42 100644 --- a/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs +++ b/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs @@ -1,14 +1,13 @@ using NUnit.Framework; using System; using System.Collections.Generic; -using System.Data.OleDb; using Microsoft.Search.Interop; using Microsoft.Plugin.Indexer.SearchHelper; using Microsoft.Plugin.Indexer.Interface; -using Moq; -using System.Linq; using Microsoft.Plugin.Indexer; +using Moq; using Wox.Plugin; +using System.Linq; namespace Wox.Test.Plugins { @@ -217,45 +216,86 @@ namespace Wox.Test.Plugins Assert.IsTrue(windowsSearchAPIResults.Any(x => x.Title == "file2.txt")); } - [Test] - public void ContextMenuLoader_ReturnContextMenuForFolderWithOpenInConsole_WhenLoadContextMenusIsCalled() + [TestCase("item.exe")] + [TestCase("item.bat")] + [TestCase("item.appref-ms")] + [TestCase("item.lnk")] + public void LoadContextMenus_MustLoadAllItems_WhenFileIsAnApp(string path) { - // Arrange - var mock = new Mock(); - mock.Setup(api => api.GetTranslation(It.IsAny())).Returns(It.IsAny()); - var pluginInitContext = new PluginInitContext() { API = mock.Object }; - var contextMenuLoader = new ContextMenuLoader(pluginInitContext); - var searchResult = new SearchResult() { Path = "C:/DummyFolder", Title = "DummyFolder" }; - var result = new Result() { ContextData = searchResult }; + // Arrange + var mockapi = new Mock(); + var pluginInitContext = new PluginInitContext() { API = mockapi.Object }; + + ContextMenuLoader _contextMenuLoader = new ContextMenuLoader(pluginInitContext); // Act - List contextMenuResults = contextMenuLoader.LoadContextMenus(result); + Result result = new Result + { + ContextData = new SearchResult { Path = path } + }; + + List contextMenuItems = _contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuResults.Count, 2); - mock.Verify(x => x.GetTranslation("Microsoft_plugin_indexer_copy_path"), Times.Once()); - mock.Verify(x => x.GetTranslation("Microsoft_plugin_indexer_open_in_console"), Times.Once()); + Assert.AreEqual(contextMenuItems.Count, 4); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_copy_path"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_in_console"), Times.Once()); } - [Test] - public void ContextMenuLoader_ReturnContextMenuForFileWithOpenInConsole_WhenLoadContextMenusIsCalled() + [TestCase("item.pdf")] + [TestCase("item.xls")] + [TestCase("item.ppt")] + [TestCase("C:/DummyFile.cs")] + public void LoadContextMenus_MustNotLoadRunAsAdmin_WhenFileIsAnNotApp(string path) { - // Arrange - var mock = new Mock(); - mock.Setup(api => api.GetTranslation(It.IsAny())).Returns(It.IsAny()); - var pluginInitContext = new PluginInitContext() { API = mock.Object }; - var contextMenuLoader = new ContextMenuLoader(pluginInitContext); - var searchResult = new SearchResult() { Path = "C:/DummyFile.cs", Title = "DummyFile.cs" }; - var result = new Result() { ContextData = searchResult }; + // Arrange + var mockapi = new Mock(); + var pluginInitContext = new PluginInitContext() { API = mockapi.Object }; + + ContextMenuLoader _contextMenuLoader = new ContextMenuLoader(pluginInitContext); // Act - List contextMenuResults = contextMenuLoader.LoadContextMenus(result); + Result result = new Result + { + ContextData = new SearchResult { Path = path } + }; + + List contextMenuItems = _contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuResults.Count, 3); - mock.Verify(x => x.GetTranslation("Microsoft_plugin_indexer_copy_path"), Times.Once()); - mock.Verify(x => x.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"), Times.Once()); - mock.Verify(x => x.GetTranslation("Microsoft_plugin_indexer_open_in_console"), Times.Once()); + Assert.AreEqual(contextMenuItems.Count, 3); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_copy_path"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"), Times.Never()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_in_console"), Times.Once()); + } + + [TestCase("C:/DummyFolder")] + [TestCase("TestFolder")] + public void LoadContextMenus_MustNotLoadRunAsAdminAndOpenContainingFolder_ForFolder(string path) + { + // Arrange + var mockapi = new Mock(); + var pluginInitContext = new PluginInitContext() { API = mockapi.Object }; + + ContextMenuLoader _contextMenuLoader = new ContextMenuLoader(pluginInitContext); + + // Act + Result result = new Result + { + ContextData = new SearchResult { Path = path } + }; + + List contextMenuItems = _contextMenuLoader.LoadContextMenus(result); + + // Assert + Assert.AreEqual(contextMenuItems.Count, 2); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_copy_path"), Times.Once()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"), Times.Never()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"), Times.Never()); + mockapi.Verify(m => m.GetTranslation("Microsoft_plugin_indexer_open_in_console"), Times.Once()); } } }