From 7549bba2930607bbaea90fab795ce2bd8a8f48f5 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 18:43:41 +0800
Subject: [PATCH 01/51] Add administrator mode check
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 783ade14ebe..346a86ab624 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -5,6 +5,7 @@
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -753,5 +754,16 @@ private static bool TryGetNotoFont(string langKey, out string notoFont)
}
#endregion
+
+ #region Administrator Mode
+
+ public static bool IsAdministrator()
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ #endregion
}
}
From 369ed86d1d8e68746b28634d75f12a2d4bead828 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 18:44:01 +0800
Subject: [PATCH 02/51] Add administrator text in tray icon
---
Flow.Launcher/Languages/en.xaml | 1 +
Flow.Launcher/MainWindow.xaml.cs | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index f233a30d56f..2abeb28f505 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -46,6 +46,7 @@
Position Reset
Reset search window position
Type here to search
+ (Admin)
Settings
diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs
index bb29d78e5e8..470b25f93e4 100644
--- a/Flow.Launcher/MainWindow.xaml.cs
+++ b/Flow.Launcher/MainWindow.xaml.cs
@@ -595,9 +595,13 @@ private void SoundPlay()
private void InitializeNotifyIcon()
{
+ var text = Win32Helper.IsAdministrator() ?
+ Constant.FlowLauncherFullName + " " + App.API.GetTranslation("admin") :
+ Constant.FlowLauncherFullName;
+
_notifyIcon = new NotifyIcon
{
- Text = Constant.FlowLauncherFullName,
+ Text = text,
Icon = Constant.Version == "1.0.0" ? Properties.Resources.dev : Properties.Resources.app,
Visible = !_settings.HideNotifyIcon
};
From 10d379f350a6f94a37b177368301cdc1e2415395 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 19:54:42 +0800
Subject: [PATCH 03/51] Support running application under non-admin mode & Show
UAC dialogs when running admin mode when application is under admin mode
---
.../Languages/en.xaml | 2 +
Plugins/Flow.Launcher.Plugin.Program/Main.cs | 10 +++
.../Programs/UWPPackage.cs | 30 +++++---
.../Programs/Win32.cs | 73 ++++++++++++-------
4 files changed, 79 insertions(+), 36 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
index e551a7dcb4e..cd1cc051c4f 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
@@ -97,5 +97,7 @@
Successfully disabled this program from displaying in your query
This app is not intended to be run as administrator
Unable to run {0}
+ User Account Control
+ Do you want to allow this app to make changes to your device?
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index d2884599467..f220190e253 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
@@ -32,6 +33,8 @@ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, I
internal static PluginInitContext Context { get; private set; }
+ internal static bool IsAdmin = IsAdministrator();
+
private static readonly List emptyResults = new();
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
@@ -459,5 +462,12 @@ public void Dispose()
{
Win32.Dispose();
}
+
+ private static bool IsAdministrator()
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
index cb33250e15e..182f3fed537 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs
@@ -4,17 +4,17 @@
using System.IO;
using System.Linq;
using System.Security.Principal;
+using System.Threading.Channels;
using System.Threading.Tasks;
+using System.Windows.Input;
using System.Windows.Media.Imaging;
-using Windows.ApplicationModel;
-using Windows.Management.Deployment;
+using System.Xml;
using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedModels;
-using System.Threading.Channels;
-using System.Xml;
-using Windows.ApplicationModel.Core;
-using System.Windows.Input;
using MemoryPack;
+using Windows.ApplicationModel;
+using Windows.ApplicationModel.Core;
+using Windows.Management.Deployment;
namespace Flow.Launcher.Plugin.Program.Programs
{
@@ -454,7 +454,9 @@ public Result Result(string query, IPublicAPI api)
bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
bool shouldRunElevated = elevated && CanRunElevated;
- _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false);
+
+ Launch(shouldRunElevated);
+
if (elevated && !shouldRunElevated)
{
var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
@@ -497,7 +499,8 @@ public List ContextMenus(IPublicAPI api)
Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
Action = c =>
{
- _ = Task.Run(() => Launch(true)).ConfigureAwait(false);
+ Launch(true);
+
return true;
},
IcoPath = "Images/cmd.png",
@@ -510,12 +513,17 @@ public List ContextMenus(IPublicAPI api)
private void Launch(bool elevated = false)
{
- string command = "shell:AppsFolder\\" + UserModelId;
+ var command = "shell:AppsFolder\\" + UserModelId;
command = Environment.ExpandEnvironmentVariables(command.Trim());
- var info = new ProcessStartInfo(command) { UseShellExecute = true, Verb = elevated ? "runas" : "" };
+ var info = new ProcessStartInfo()
+ {
+ FileName = command,
+ UseShellExecute = true,
+ Verb = elevated ? "runas" : ""
+ };
- Main.StartProcess(Process.Start, info);
+ _ = Task.Run(() => Main.StartProcess(Process.Start, info)).ConfigureAwait(false);
}
internal static bool IfAppCanRunElevated(XmlNode appNode)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index a87b002d414..3dd7ec0128c 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -1,21 +1,22 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
+using System.Threading.Channels;
using System.Threading.Tasks;
-using Microsoft.Win32;
+using System.Windows;
+using System.Windows.Input;
using Flow.Launcher.Plugin.Program.Logger;
+using Flow.Launcher.Plugin.Program.Views.Models;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading.Channels;
-using Flow.Launcher.Plugin.Program.Views.Models;
using IniParser;
-using System.Windows.Input;
using MemoryPack;
+using Microsoft.Win32;
namespace Flow.Launcher.Plugin.Program.Programs
{
@@ -196,15 +197,7 @@ public Result Result(string query, IPublicAPI api)
// Ctrl + Shift + Enter to run as admin
bool runAsAdmin = c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
- var info = new ProcessStartInfo
- {
- FileName = FullPath,
- WorkingDirectory = ParentDirectory,
- UseShellExecute = true,
- Verb = runAsAdmin ? "runas" : "",
- };
-
- _ = Task.Run(() => Main.StartProcess(Process.Start, info));
+ Launch(runAsAdmin);
return true;
}
@@ -213,6 +206,44 @@ public Result Result(string query, IPublicAPI api)
return result;
}
+ private void Launch(bool elevated = false)
+ {
+ var info = new ProcessStartInfo
+ {
+ FileName = FullPath,
+ WorkingDirectory = ParentDirectory,
+ UseShellExecute = true,
+ Verb = elevated ? "runas" : "",
+ };
+
+ if (Main.IsAdmin)
+ {
+ if (elevated)
+ {
+ // Since we are already elevated, we need to create UAC dialog manually
+ if (Main.Context.API.ShowMsgBox(
+ Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_subtitle"),
+ Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_title"),
+ MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // Use explorer.exe as workaround to start process as standard user
+ info = new ProcessStartInfo
+ {
+ FileName = "explorer.exe",
+ Arguments = $"\"{FullPath}\"",
+ WorkingDirectory = ParentDirectory,
+ UseShellExecute = true,
+ };
+ }
+ }
+
+ _ = Task.Run(() => Main.StartProcess(Process.Start, info)).ConfigureAwait(false); ;
+ }
public List ContextMenus(IPublicAPI api)
{
@@ -228,7 +259,7 @@ public List ContextMenus(IPublicAPI api)
FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true
};
- _ = Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
+ _ = Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)).ConfigureAwait(false);;
return true;
},
@@ -240,15 +271,7 @@ public List ContextMenus(IPublicAPI api)
Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
Action = c =>
{
- var info = new ProcessStartInfo
- {
- FileName = FullPath,
- WorkingDirectory = ParentDirectory,
- Verb = "runas",
- UseShellExecute = true
- };
-
- _ = Task.Run(() => Main.StartProcess(Process.Start, info));
+ Launch(true);
return true;
},
From 484f91c52d2de6100661c6dcf40cefc0729a5dd2 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 20:26:41 +0800
Subject: [PATCH 04/51] Improve UAC dialog
---
.../Languages/en.xaml | 1 +
.../Programs/Win32.cs | 5 +-
.../UACDialog.xaml | 154 ++++++++++++++++++
.../UACDialog.xaml.cs | 87 ++++++++++
4 files changed, 243 insertions(+), 4 deletions(-)
create mode 100644 Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml
create mode 100644 Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
index cd1cc051c4f..e3eeb4e91a5 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
@@ -99,5 +99,6 @@
Unable to run {0}
User Account Control
Do you want to allow this app to make changes to your device?
+ Program location: {0}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 3dd7ec0128c..8cf1f77dad5 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -221,10 +221,7 @@ private void Launch(bool elevated = false)
if (elevated)
{
// Since we are already elevated, we need to create UAC dialog manually
- if (Main.Context.API.ShowMsgBox(
- Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_subtitle"),
- Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_title"),
- MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+ if (UACDialog.Show(IcoPath, Name, FullPath) != MessageBoxResult.Yes)
{
return;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml b/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml
new file mode 100644
index 00000000000..d619f47654d
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs b/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs
new file mode 100644
index 00000000000..3ab087b489f
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+
+namespace Flow.Launcher.Plugin.Program
+{
+ public partial class UACDialog : Window
+ {
+ private static readonly string ClassName = nameof(UACDialog);
+
+ private static UACDialog msgBox;
+ private static MessageBoxResult _result = MessageBoxResult.None;
+
+ private UACDialog()
+ {
+ InitializeComponent();
+ }
+
+ public static MessageBoxResult Show(string iconPath, string appName, string fullPath)
+ {
+ if (!Application.Current.Dispatcher.CheckAccess())
+ {
+ return Application.Current.Dispatcher.Invoke(() => Show(iconPath, appName, fullPath));
+ }
+
+ try
+ {
+ msgBox = new UACDialog
+ {
+ Title = Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_title")
+ };
+
+ // Set icon & app name & program location
+ _ = msgBox.SetImageAsync(iconPath);
+ msgBox.AppName.Text = appName;
+ msgBox.ProgramLocation.Text = string.Format(
+ Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_program_location"),
+ fullPath);
+
+ // Focus No by default
+ msgBox.btnNo.Focus();
+ _result = MessageBoxResult.No;
+
+ msgBox.ShowDialog();
+ return _result;
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogError(ClassName, $"An error occurred: {e.Message}");
+ msgBox = null;
+ return MessageBoxResult.None;
+ }
+ }
+
+ private async Task SetImageAsync(string imagePath)
+ {
+ var imageSource = await Main.Context.API.LoadImageAsync(imagePath);
+ Img.Source = imageSource;
+ }
+
+ private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
+ {
+ DialogResult = false;
+ Close();
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender == btnYes)
+ _result = MessageBoxResult.Yes;
+ else if (sender == btnNo)
+ _result = MessageBoxResult.No;
+ else
+ _result = MessageBoxResult.None;
+ msgBox.Close();
+ msgBox = null;
+ }
+
+ private void Button_Cancel(object sender, RoutedEventArgs e)
+ {
+ _result = MessageBoxResult.Cancel;
+ msgBox.Close();
+ msgBox = null;
+ }
+ }
+}
From 7611b002873d33e2054f3f4122ddfb8c875d471b Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 21:46:27 +0800
Subject: [PATCH 05/51] Add new restart api
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 6 ++++++
Flow.Launcher/PublicAPIInstance.cs | 7 ++++---
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index cb60251ed95..b304542773a 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -33,6 +33,12 @@ public interface IPublicAPI
///
void RestartApp();
+ ///
+ /// Restart Flow Launcher with arguments
+ ///
+ /// Application start arguments
+ void RestartApp(string arguments);
+
///
/// Run a shell command
///
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 66e11f88130..751e38c4297 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -11,7 +11,6 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-using System.Windows.Input;
using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
@@ -73,8 +72,10 @@ public void ChangeQuery(string query, bool requery = false)
_mainVM.ChangeQueryText(query, requery);
}
+ public void RestartApp() => RestartApp(null);
+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
- public async void RestartApp()
+ public async void RestartApp(string arguments)
{
_mainVM.Hide();
@@ -89,7 +90,7 @@ public async void RestartApp()
// Restart requires Squirrel's Update.exe to be present in the parent folder,
// it is only published from the project's release pipeline. When debugging without it,
// the project may not restart or just terminates. This is expected.
- UpdateManager.RestartApp(Constant.ApplicationFileName);
+ UpdateManager.RestartApp(Constant.ApplicationFileName, arguments);
}
public void ShowMainWindow() => _mainVM.Show();
From d29a5d4b1fc423bf02c0fbb44feb6b727f84a41d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:03:25 +0800
Subject: [PATCH 06/51] Add setting model
---
Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index f00f42ac095..383efdaf119 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -372,6 +372,8 @@ public bool HideNotifyIcon
public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactivated { get; set; } = true;
+ public bool AlwaysRunAsAdministrator { get; set; } = false;
+
public bool SearchQueryResultsWithDelay { get; set; }
public int SearchDelayTime { get; set; } = 150;
From 40cf41389845f9d6a223b8fae679b9e2e26d6c5c Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:04:32 +0800
Subject: [PATCH 07/51] Add admin support for auto startup
---
Flow.Launcher/App.xaml.cs | 2 +-
Flow.Launcher/Helper/AutoStartup.cs | 35 ++++++++-----------
.../Resources/Pages/WelcomePage5.xaml.cs | 2 +-
.../SettingsPaneGeneralViewModel.cs | 4 +--
4 files changed, 19 insertions(+), 24 deletions(-)
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 969bb75bbf9..8c5915cd45e 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -236,7 +236,7 @@ private void AutoStartup()
{
try
{
- Helper.AutoStartup.CheckIsEnabled(_settings.UseLogonTaskForStartup);
+ Helper.AutoStartup.CheckIsEnabled(_settings.UseLogonTaskForStartup, _settings.AlwaysRunAsAdministrator);
}
catch (Exception e)
{
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 34700c61015..e6fd1ba1e78 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -17,18 +17,18 @@ public class AutoStartup
private const string LogonTaskName = $"{Constant.FlowLauncher} Startup";
private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup";
- public static void CheckIsEnabled(bool useLogonTaskForStartup)
+ public static void CheckIsEnabled(bool useLogonTaskForStartup, bool alwaysRunAsAdministrator)
{
// We need to check both because if both of them are enabled,
// Hide Flow Launcher on startup will not work since the later one will trigger main window show event
- var logonTaskEnabled = CheckLogonTask();
+ var logonTaskEnabled = CheckLogonTask(alwaysRunAsAdministrator);
var registryEnabled = CheckRegistry();
if (useLogonTaskForStartup)
{
// Enable logon task
if (!logonTaskEnabled)
{
- Enable(true);
+ Enable(true, alwaysRunAsAdministrator);
}
// Disable registry
if (registryEnabled)
@@ -41,7 +41,7 @@ public static void CheckIsEnabled(bool useLogonTaskForStartup)
// Enable registry
if (!registryEnabled)
{
- Enable(false);
+ Enable(false, alwaysRunAsAdministrator);
}
// Disable logon task
if (logonTaskEnabled)
@@ -51,7 +51,7 @@ public static void CheckIsEnabled(bool useLogonTaskForStartup)
}
}
- private static bool CheckLogonTask()
+ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
{
using var taskService = new TaskService();
var task = taskService.RootFolder.AllTasks.FirstOrDefault(t => t.Name == LogonTaskName);
@@ -67,7 +67,7 @@ private static bool CheckLogonTask()
if (!action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase))
{
UnscheduleLogonTask();
- ScheduleLogonTask();
+ ScheduleLogonTask(alwaysRunAsAdministrator);
}
}
@@ -117,16 +117,17 @@ public static void DisableViaLogonTaskAndRegistry()
Disable(false);
}
- public static void ChangeToViaLogonTask()
+ public static void ChangeToViaLogonTask(bool alwaysRunAsAdministrator)
{
Disable(false);
- Enable(true);
+ Enable(true, alwaysRunAsAdministrator);
}
public static void ChangeToViaRegistry()
{
Disable(true);
- Enable(false);
+ // We do not need to use alwaysRunAsAdministrator for registry, so we just set false here
+ Enable(false, false);
}
private static void Disable(bool logonTask)
@@ -149,13 +150,13 @@ private static void Disable(bool logonTask)
}
}
- private static void Enable(bool logonTask)
+ private static void Enable(bool logonTask, bool alwaysRunAsAdministrator)
{
try
{
if (logonTask)
{
- ScheduleLogonTask();
+ ScheduleLogonTask(alwaysRunAsAdministrator);
}
else
{
@@ -169,14 +170,15 @@ private static void Enable(bool logonTask)
}
}
- private static bool ScheduleLogonTask()
+ private static bool ScheduleLogonTask(bool alwaysRunAsAdministrator)
{
using var td = TaskService.Instance.NewTask();
td.RegistrationInfo.Description = LogonTaskDesc;
td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name, Delay = TimeSpan.FromSeconds(2) });
td.Actions.Add(Constant.ExecutablePath);
- if (IsCurrentUserIsAdmin())
+ // Only if the app is running as administrator, we can set the run level to highest
+ if (Win32Helper.IsAdministrator() && alwaysRunAsAdministrator)
{
td.Principal.RunLevel = TaskRunLevel.Highest;
}
@@ -212,13 +214,6 @@ private static bool UnscheduleLogonTask()
}
}
- private static bool IsCurrentUserIsAdmin()
- {
- var identity = WindowsIdentity.GetCurrent();
- var principal = new WindowsPrincipal(identity);
- return principal.IsInRole(WindowsBuiltInRole.Administrator);
- }
-
private static bool UnscheduleRegistry()
{
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs
index 8db0a9f7e08..dc88f74fd58 100644
--- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs
+++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs
@@ -45,7 +45,7 @@ private void ChangeAutoStartup(bool value)
{
if (Settings.UseLogonTaskForStartup)
{
- AutoStartup.ChangeToViaLogonTask();
+ AutoStartup.ChangeToViaLogonTask(Settings.AlwaysRunAsAdministrator);
}
else
{
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index eac40099bfb..eed81fe17f7 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -49,7 +49,7 @@ public bool StartFlowLauncherOnSystemStartup
{
if (UseLogonTaskForStartup)
{
- AutoStartup.ChangeToViaLogonTask();
+ AutoStartup.ChangeToViaLogonTask(AlwaysRunAsAdministrator);
}
else
{
@@ -81,7 +81,7 @@ public bool UseLogonTaskForStartup
{
if (value)
{
- AutoStartup.ChangeToViaLogonTask();
+ AutoStartup.ChangeToViaLogonTask(AlwaysRunAsAdministrator);
}
else
{
From fe70a72a626a74d0ef88c0d359a2f448642329e1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:04:47 +0800
Subject: [PATCH 08/51] Add admin mode configuration in general page
---
Flow.Launcher/Languages/en.xaml | 4 ++
.../SettingsPaneGeneralViewModel.cs | 59 ++++++++++++++++++-
.../Views/SettingsPaneGeneral.xaml | 31 ++++++----
3 files changed, 83 insertions(+), 11 deletions(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 2abeb28f505..de9ba2f2771 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -132,6 +132,10 @@
Show History Results in Home Page
Maximum History Results Shown in Home Page
This can only be edited if plugin supports Home feature and Home Page is enabled.
+ Always run as administrator
+ Run Flow Launcher as administrator on system startup
+ Administrator Mode Change
+ Do you want to restart to apply administrator mode change?
Search Plugin
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index eed81fe17f7..edb2aacf761 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Windows;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
@@ -22,6 +23,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private readonly Portable _portable;
private readonly Internationalization _translater;
+ private static readonly bool _isAdministrator = Win32Helper.IsAdministrator();
+
public SettingsPaneGeneralViewModel(Settings settings, Updater updater, Portable portable, Internationalization translater)
{
Settings = settings;
@@ -65,6 +68,13 @@ public bool StartFlowLauncherOnSystemStartup
{
App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
}
+
+ // If we have enabled logon task startup, we need to check if we need to restart the app
+ // even if we encounter an error while setting the startup method
+ if (value && UseLogonTaskForStartup)
+ {
+ CheckAdminChangeAndAskForRestart();
+ }
}
}
@@ -92,10 +102,57 @@ public bool UseLogonTaskForStartup
{
App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
}
- }
+ }
+
+ // If we have enabled logon task startup, we need to check if we need to restart the app
+ // even if we encounter an error while setting the startup method
+ if (StartFlowLauncherOnSystemStartup && value)
+ {
+ CheckAdminChangeAndAskForRestart();
+ }
}
}
+ public bool AlwaysRunAsAdministrator
+ {
+ get => Settings.AlwaysRunAsAdministrator;
+ set
+ {
+ Settings.AlwaysRunAsAdministrator = value;
+
+ if (StartFlowLauncherOnSystemStartup && UseLogonTaskForStartup)
+ {
+ try
+ {
+ AutoStartup.ChangeToViaLogonTask(value);
+ }
+ catch (Exception e)
+ {
+ App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
+ }
+
+ // If we have enabled logon task startup, we need to check if we need to restart the app
+ // even if we encounter an error while setting the startup method
+ CheckAdminChangeAndAskForRestart();
+ }
+ }
+ }
+
+ private void CheckAdminChangeAndAskForRestart()
+ {
+ if ((AlwaysRunAsAdministrator && !_isAdministrator) || // Change from non-admin to admin
+ (!AlwaysRunAsAdministrator && _isAdministrator)) // Change from admin to non-admin
+ {
+ if (App.API.ShowMsgBox(
+ App.API.GetTranslation("runAsAdministratorChangeAndRestart"),
+ App.API.GetTranslation("runAsAdministratorChange"),
+ MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ App.API.RestartApp(AlwaysRunAsAdministrator ? "runas" : string.Empty);
+ }
+ }
+ }
+
public List SearchWindowScreens { get; } =
DropdownDataGeneric.GetValues("SearchWindowScreen");
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index c0c5613de04..1871a92ffc5 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -43,15 +43,27 @@
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
-
-
-
+
+
+
+
+
+
+
+
+
-
Date: Thu, 22 May 2025 22:13:46 +0800
Subject: [PATCH 09/51] Remove useless semicolon
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 8cf1f77dad5..c8bde614a34 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -239,7 +239,7 @@ private void Launch(bool elevated = false)
}
}
- _ = Task.Run(() => Main.StartProcess(Process.Start, info)).ConfigureAwait(false); ;
+ _ = Task.Run(() => Main.StartProcess(Process.Start, info)).ConfigureAwait(false);
}
public List ContextMenus(IPublicAPI api)
From 91f7a4fd55bbc5b56c16edd2e117beb4d91b7c14 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:17:10 +0800
Subject: [PATCH 10/51] Remove useless semicolon
---
Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index c8bde614a34..4e1d99454be 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -256,7 +256,7 @@ public List ContextMenus(IPublicAPI api)
FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true
};
- _ = Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)).ConfigureAwait(false);;
+ _ = Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)).ConfigureAwait(false);
return true;
},
From 902c8f44123588e7e9fc7eccbe1b4f5b87da6e96 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:18:25 +0800
Subject: [PATCH 11/51] Check value changed
---
.../SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index edb2aacf761..d7d92d64352 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -44,6 +44,8 @@ public bool StartFlowLauncherOnSystemStartup
get => Settings.StartFlowLauncherOnSystemStartup;
set
{
+ if (Settings.StartFlowLauncherOnSystemStartup == value) return;
+
Settings.StartFlowLauncherOnSystemStartup = value;
try
@@ -83,6 +85,8 @@ public bool UseLogonTaskForStartup
get => Settings.UseLogonTaskForStartup;
set
{
+ if (UseLogonTaskForStartup == value) return;
+
Settings.UseLogonTaskForStartup = value;
if (StartFlowLauncherOnSystemStartup)
@@ -118,6 +122,8 @@ public bool AlwaysRunAsAdministrator
get => Settings.AlwaysRunAsAdministrator;
set
{
+ if (AlwaysRunAsAdministrator == value) return;
+
Settings.AlwaysRunAsAdministrator = value;
if (StartFlowLauncherOnSystemStartup && UseLogonTaskForStartup)
From c5c24acb17eca7de8eae6d3c2fad24dc1148f758 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:55:17 +0800
Subject: [PATCH 12/51] Check run level
---
Flow.Launcher/Helper/AutoStartup.cs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index e6fd1ba1e78..6e1702ccfb7 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -64,7 +64,8 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
if (task.Definition.Actions.FirstOrDefault() is Microsoft.Win32.TaskScheduler.Action taskAction)
{
var action = taskAction.ToString().Trim();
- if (!action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase))
+ if (!action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase) || // Path issue
+ !CheckRunLevel(task.Definition.Principal, alwaysRunAsAdministrator)) // Run level issue
{
UnscheduleLogonTask();
ScheduleLogonTask(alwaysRunAsAdministrator);
@@ -83,6 +84,11 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
return false;
}
+ private static bool CheckRunLevel(TaskPrincipal tp, bool alwaysRunAsAdministrator)
+ {
+ return alwaysRunAsAdministrator ? tp.RunLevel == TaskRunLevel.Highest : tp.RunLevel != TaskRunLevel.Highest;
+ }
+
private static bool CheckRegistry()
{
try
From 1073061bda93cbe9682cc8a63e0910dcf8992498 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 22 May 2025 22:55:29 +0800
Subject: [PATCH 13/51] Remove old before creating new one
---
Flow.Launcher/Helper/AutoStartup.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 6e1702ccfb7..7a1a180b458 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -126,12 +126,14 @@ public static void DisableViaLogonTaskAndRegistry()
public static void ChangeToViaLogonTask(bool alwaysRunAsAdministrator)
{
Disable(false);
+ Disable(true); // Remove old logon task so that we can create a new one
Enable(true, alwaysRunAsAdministrator);
}
public static void ChangeToViaRegistry()
{
Disable(true);
+ Disable(false); // Remove old registry so that we can create a new one
// We do not need to use alwaysRunAsAdministrator for registry, so we just set false here
Enable(false, false);
}
From e9c40e45868111b32ad8dbaebc3c90813d9e0c67 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 11:01:32 +0800
Subject: [PATCH 14/51] Revert "Add new restart api"
This reverts commit 7611b002873d33e2054f3f4122ddfb8c875d471b.
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 6 ------
Flow.Launcher/PublicAPIInstance.cs | 7 +++----
2 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index b304542773a..cb60251ed95 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -33,12 +33,6 @@ public interface IPublicAPI
///
void RestartApp();
- ///
- /// Restart Flow Launcher with arguments
- ///
- /// Application start arguments
- void RestartApp(string arguments);
-
///
/// Run a shell command
///
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 751e38c4297..66e11f88130 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -11,6 +11,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
+using System.Windows.Input;
using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
@@ -72,10 +73,8 @@ public void ChangeQuery(string query, bool requery = false)
_mainVM.ChangeQueryText(query, requery);
}
- public void RestartApp() => RestartApp(null);
-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
- public async void RestartApp(string arguments)
+ public async void RestartApp()
{
_mainVM.Hide();
@@ -90,7 +89,7 @@ public async void RestartApp(string arguments)
// Restart requires Squirrel's Update.exe to be present in the parent folder,
// it is only published from the project's release pipeline. When debugging without it,
// the project may not restart or just terminates. This is expected.
- UpdateManager.RestartApp(Constant.ApplicationFileName, arguments);
+ UpdateManager.RestartApp(Constant.ApplicationFileName);
}
public void ShowMainWindow() => _mainVM.Show();
From 365ba6afa5eb823244ba1b388cee0d50e47a32e0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 11:04:27 +0800
Subject: [PATCH 15/51] Code quality
---
Flow.Launcher/PublicAPIInstance.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 66e11f88130..595674b3eb8 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -11,7 +11,6 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-using System.Windows.Input;
using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
From eae4a9584b9e021ff8ab6981a0560c36f647a727 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 11:05:19 +0800
Subject: [PATCH 16/51] Use readonly bool variable
---
Flow.Launcher/Helper/AutoStartup.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 7a1a180b458..919eb52d201 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -17,6 +17,8 @@ public class AutoStartup
private const string LogonTaskName = $"{Constant.FlowLauncher} Startup";
private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup";
+ private static readonly bool _isAdministrator = Win32Helper.IsAdministrator();
+
public static void CheckIsEnabled(bool useLogonTaskForStartup, bool alwaysRunAsAdministrator)
{
// We need to check both because if both of them are enabled,
@@ -186,7 +188,7 @@ private static bool ScheduleLogonTask(bool alwaysRunAsAdministrator)
td.Actions.Add(Constant.ExecutablePath);
// Only if the app is running as administrator, we can set the run level to highest
- if (Win32Helper.IsAdministrator() && alwaysRunAsAdministrator)
+ if (_isAdministrator && alwaysRunAsAdministrator)
{
td.Principal.RunLevel = TaskRunLevel.Highest;
}
From 52c36ff8d42c9cbbe3edeea85a16a7517b2a1362 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 11:14:15 +0800
Subject: [PATCH 17/51] Only restart from non-admin to admin & Fix restart as
administrator issue
---
Flow.Launcher/Languages/en.xaml | 2 +-
.../SettingsPaneGeneralViewModel.cs | 36 +++++++++++++++----
2 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index de9ba2f2771..82e0091a0c8 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -135,7 +135,7 @@
Always run as administrator
Run Flow Launcher as administrator on system startup
Administrator Mode Change
- Do you want to restart to apply administrator mode change?
+ Do you want to restart as administrator to apply this change? Or you need to run as administrator during next start manually.
Search Plugin
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index d7d92d64352..8a3f1661f8e 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
@@ -9,6 +11,7 @@
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
@@ -75,7 +78,7 @@ public bool StartFlowLauncherOnSystemStartup
// even if we encounter an error while setting the startup method
if (value && UseLogonTaskForStartup)
{
- CheckAdminChangeAndAskForRestart();
+ _ = CheckAdminChangeAndAskForRestartAsync();
}
}
}
@@ -112,7 +115,7 @@ public bool UseLogonTaskForStartup
// even if we encounter an error while setting the startup method
if (StartFlowLauncherOnSystemStartup && value)
{
- CheckAdminChangeAndAskForRestart();
+ _ = CheckAdminChangeAndAskForRestartAsync();
}
}
}
@@ -139,22 +142,41 @@ public bool AlwaysRunAsAdministrator
// If we have enabled logon task startup, we need to check if we need to restart the app
// even if we encounter an error while setting the startup method
- CheckAdminChangeAndAskForRestart();
+ _ = CheckAdminChangeAndAskForRestartAsync();
}
}
}
- private void CheckAdminChangeAndAskForRestart()
+ private async Task CheckAdminChangeAndAskForRestartAsync()
{
- if ((AlwaysRunAsAdministrator && !_isAdministrator) || // Change from non-admin to admin
- (!AlwaysRunAsAdministrator && _isAdministrator)) // Change from admin to non-admin
+ // When we change from non-admin to admin, we need to restart the app as administrator to apply the changes
+ // Under non-administrator, we cannot delete or set the logon task which is run as administrator
+ if (AlwaysRunAsAdministrator && !_isAdministrator)
{
if (App.API.ShowMsgBox(
App.API.GetTranslation("runAsAdministratorChangeAndRestart"),
App.API.GetTranslation("runAsAdministratorChange"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- App.API.RestartApp(AlwaysRunAsAdministrator ? "runas" : string.Empty);
+ App.API.HideMainWindow();
+
+ // We must manually save because of Environment.Exit(0)
+ // which will cause ungraceful exit
+ App.API.SaveAppAllSettings();
+
+ // Wait for all image caches to be saved before restarting
+ await ImageLoader.WaitSaveAsync();
+
+ // Restart the app as administrator
+ var startInfo = new ProcessStartInfo
+ {
+ UseShellExecute = true,
+ WorkingDirectory = Environment.CurrentDirectory,
+ FileName = Constant.ExecutablePath,
+ Verb = "runas"
+ };
+ Process.Start(startInfo);
+ Environment.Exit(0);
}
}
}
From 35f4fd5b187f7c7e7a2b289d674f51d103071ffd Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 12:36:03 +0800
Subject: [PATCH 18/51] Fix restart as administrator issue
---
.../SettingsPaneGeneralViewModel.cs | 45 +++++++++++++++----
1 file changed, 36 insertions(+), 9 deletions(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index 8a3f1661f8e..6fab0cb2e7c 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
+using System.Reflection;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
@@ -168,19 +171,43 @@ private async Task CheckAdminChangeAndAskForRestartAsync()
await ImageLoader.WaitSaveAsync();
// Restart the app as administrator
- var startInfo = new ProcessStartInfo
- {
- UseShellExecute = true,
- WorkingDirectory = Environment.CurrentDirectory,
- FileName = Constant.ExecutablePath,
- Verb = "runas"
- };
- Process.Start(startInfo);
- Environment.Exit(0);
+ RestartAppAsAdministrator(Constant.ExecutablePath);
}
}
}
+ private static void RestartAppAsAdministrator(string exeToStart)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = getUpdateExe(),
+ Arguments = $"--processStartAndWait {exeToStart}",
+ UseShellExecute = true,
+ Verb = "runas",
+ };
+ Process.Start(startInfo);
+ Thread.Sleep(500);
+ Environment.Exit(0);
+ }
+
+ private static string getUpdateExe()
+ {
+ Assembly entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly != null && Path.GetFileName(entryAssembly.Location).Equals("update.exe", StringComparison.OrdinalIgnoreCase) && entryAssembly.Location.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 && entryAssembly.Location.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ return Path.GetFullPath(entryAssembly.Location);
+ }
+
+ entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
+ FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(entryAssembly.Location), "..\\Update.exe"));
+ if (!fileInfo.Exists)
+ {
+ throw new Exception("Update.exe not found, not a Squirrel-installed app?");
+ }
+
+ return fileInfo.FullName;
+ }
+
public List SearchWindowScreens { get; } =
DropdownDataGeneric.GetValues("SearchWindowScreen");
From eacccf9823210ead6abab8f12ebcd4f298068060 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 12:56:23 +0800
Subject: [PATCH 19/51] Add code comments & Use local function
---
.../SettingsPaneGeneralViewModel.cs | 31 ++++++++++---------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index 6fab0cb2e7c..89a89b090a8 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -176,6 +176,8 @@ private async Task CheckAdminChangeAndAskForRestartAsync()
}
}
+ // Since Squirrel does not provide a way to restart the app as administrator,
+ // we need to do it manually by starting the update.exe with the runas verb
private static void RestartAppAsAdministrator(string exeToStart)
{
var startInfo = new ProcessStartInfo
@@ -188,24 +190,25 @@ private static void RestartAppAsAdministrator(string exeToStart)
Process.Start(startInfo);
Thread.Sleep(500);
Environment.Exit(0);
- }
- private static string getUpdateExe()
- {
- Assembly entryAssembly = Assembly.GetEntryAssembly();
- if (entryAssembly != null && Path.GetFileName(entryAssembly.Location).Equals("update.exe", StringComparison.OrdinalIgnoreCase) && entryAssembly.Location.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 && entryAssembly.Location.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1)
+ // Local function
+ static string getUpdateExe()
{
- return Path.GetFullPath(entryAssembly.Location);
- }
+ Assembly entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly != null && Path.GetFileName(entryAssembly.Location).Equals("update.exe", StringComparison.OrdinalIgnoreCase) && entryAssembly.Location.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 && entryAssembly.Location.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ return Path.GetFullPath(entryAssembly.Location);
+ }
- entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
- FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(entryAssembly.Location), "..\\Update.exe"));
- if (!fileInfo.Exists)
- {
- throw new Exception("Update.exe not found, not a Squirrel-installed app?");
- }
+ entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
+ FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(entryAssembly.Location), "..\\Update.exe"));
+ if (!fileInfo.Exists)
+ {
+ throw new Exception("Update.exe not found, not a Squirrel-installed app?");
+ }
- return fileInfo.FullName;
+ return fileInfo.FullName;
+ }
}
public List SearchWindowScreens { get; } =
From d6462f4fdc2c28f8b14c8bbd879a3d8c15176dc4 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 13:18:19 +0800
Subject: [PATCH 20/51] Move restart function to app class
---
Flow.Launcher/App.xaml.cs | 41 +++++++++++++++++++
.../SettingsPaneGeneralViewModel.cs | 41 +------------------
2 files changed, 42 insertions(+), 40 deletions(-)
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 8c5915cd45e..7a33c995f25 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
+using System.IO;
+using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -319,6 +321,45 @@ private static void RegisterTaskSchedulerUnhandledException()
#endregion
+ #region Restart
+
+ // Since Squirrel does not provide a way to restart the app as administrator,
+ // we need to do it manually by starting the update.exe with the runas verb
+ public static void RestartAppAsAdministrator()
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = getUpdateExe(),
+ Arguments = $"--processStartAndWait {Constant.ExecutablePath}",
+ UseShellExecute = true,
+ Verb = "runas",
+ };
+ Process.Start(startInfo);
+ Thread.Sleep(500);
+ Environment.Exit(0);
+
+ // Local function
+ static string getUpdateExe()
+ {
+ Assembly entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly != null && Path.GetFileName(entryAssembly.Location).Equals("update.exe", StringComparison.OrdinalIgnoreCase) && entryAssembly.Location.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 && entryAssembly.Location.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ return Path.GetFullPath(entryAssembly.Location);
+ }
+
+ entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
+ FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(entryAssembly.Location), "..\\Update.exe"));
+ if (!fileInfo.Exists)
+ {
+ throw new Exception("Update.exe not found, not a Squirrel-installed app?");
+ }
+
+ return fileInfo.FullName;
+ }
+ }
+
+ #endregion
+
#region IDisposable
protected virtual void Dispose(bool disposing)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index 89a89b090a8..e7c7333c27e 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
using System.Linq;
-using System.Reflection;
-using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
@@ -171,46 +167,11 @@ private async Task CheckAdminChangeAndAskForRestartAsync()
await ImageLoader.WaitSaveAsync();
// Restart the app as administrator
- RestartAppAsAdministrator(Constant.ExecutablePath);
+ App.RestartAppAsAdministrator();
}
}
}
- // Since Squirrel does not provide a way to restart the app as administrator,
- // we need to do it manually by starting the update.exe with the runas verb
- private static void RestartAppAsAdministrator(string exeToStart)
- {
- var startInfo = new ProcessStartInfo
- {
- FileName = getUpdateExe(),
- Arguments = $"--processStartAndWait {exeToStart}",
- UseShellExecute = true,
- Verb = "runas",
- };
- Process.Start(startInfo);
- Thread.Sleep(500);
- Environment.Exit(0);
-
- // Local function
- static string getUpdateExe()
- {
- Assembly entryAssembly = Assembly.GetEntryAssembly();
- if (entryAssembly != null && Path.GetFileName(entryAssembly.Location).Equals("update.exe", StringComparison.OrdinalIgnoreCase) && entryAssembly.Location.IndexOf("app-", StringComparison.OrdinalIgnoreCase) == -1 && entryAssembly.Location.IndexOf("SquirrelTemp", StringComparison.OrdinalIgnoreCase) == -1)
- {
- return Path.GetFullPath(entryAssembly.Location);
- }
-
- entryAssembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
- FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(entryAssembly.Location), "..\\Update.exe"));
- if (!fileInfo.Exists)
- {
- throw new Exception("Update.exe not found, not a Squirrel-installed app?");
- }
-
- return fileInfo.FullName;
- }
- }
-
public List SearchWindowScreens { get; } =
DropdownDataGeneric.GetValues("SearchWindowScreen");
From 479b49db94b8f4adf69c6d8d76813e95ffd55b63 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 13:20:36 +0800
Subject: [PATCH 21/51] Throw UnauthorizedAccessException when encountering
admin issue
---
Flow.Launcher/Helper/AutoStartup.cs | 40 +++++++++++++++++++++++------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 919eb52d201..371ab46effb 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -61,21 +61,45 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
{
try
{
- // Check if the action is the same as the current executable path
- // If not, we need to unschedule and reschedule the task
if (task.Definition.Actions.FirstOrDefault() is Microsoft.Win32.TaskScheduler.Action taskAction)
{
var action = taskAction.ToString().Trim();
- if (!action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase) || // Path issue
- !CheckRunLevel(task.Definition.Principal, alwaysRunAsAdministrator)) // Run level issue
+ var pathCorrect = action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase);
+ var runLevelCorrect = CheckRunLevel(task.Definition.Principal.RunLevel, alwaysRunAsAdministrator);
+
+ if (_isAdministrator)
+ {
+ // If path or run level is not correct, we need to unschedule and reschedule the task
+ if (!pathCorrect || !runLevelCorrect)
+ {
+ UnscheduleLogonTask();
+ ScheduleLogonTask(alwaysRunAsAdministrator);
+ }
+ }
+ else
{
- UnscheduleLogonTask();
- ScheduleLogonTask(alwaysRunAsAdministrator);
+ // If run level is not correct, we cannot edit it because we are not administrator
+ if (!runLevelCorrect)
+ {
+ throw new UnauthorizedAccessException("Cannot edit task run level because the app is not running as administrator.");
+ }
+
+ // If run level is correct and path is not correct, we need to unschedule and reschedule the task
+ if (!pathCorrect)
+ {
+ UnscheduleLogonTask();
+ ScheduleLogonTask(alwaysRunAsAdministrator);
+ }
}
}
return true;
}
+ catch (UnauthorizedAccessException e)
+ {
+ App.API.LogError(ClassName, $"Failed to check logon task: {e}");
+ throw; // Throw exception so that App.AutoStartup can show error message
+ }
catch (Exception e)
{
App.API.LogError(ClassName, $"Failed to check logon task: {e}");
@@ -86,9 +110,9 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
return false;
}
- private static bool CheckRunLevel(TaskPrincipal tp, bool alwaysRunAsAdministrator)
+ private static bool CheckRunLevel(TaskRunLevel rl, bool alwaysRunAsAdministrator)
{
- return alwaysRunAsAdministrator ? tp.RunLevel == TaskRunLevel.Highest : tp.RunLevel != TaskRunLevel.Highest;
+ return alwaysRunAsAdministrator ? rl == TaskRunLevel.Highest : rl != TaskRunLevel.Highest;
}
private static bool CheckRegistry()
From c3ec002b72f7ced8eba5248f256b2149c3dbac85 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 13:21:34 +0800
Subject: [PATCH 22/51] Show message box to ask users to restart as
administrator when encountering issue checking startup
---
Flow.Launcher/App.xaml.cs | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 7a33c995f25..cc573e00585 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -240,10 +240,21 @@ private void AutoStartup()
{
Helper.AutoStartup.CheckIsEnabled(_settings.UseLogonTaskForStartup, _settings.AlwaysRunAsAdministrator);
}
+ catch (UnauthorizedAccessException)
+ {
+ // If it fails for permission, we need to ask the user to restart as administrator
+ if (API.ShowMsgBox(
+ API.GetTranslation("runAsAdministratorChangeAndRestart"),
+ API.GetTranslation("runAsAdministratorChange"),
+ MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ {
+ RestartAppAsAdministrator();
+ }
+ }
catch (Exception e)
{
- // but if it fails (permissions, etc) then don't keep retrying
- // this also gives the user a visual indication in the Settings widget
+ // But if it fails for other reasons then do not keep retrying,
+ // set startup to false to give users a visual indication in the general page
_settings.StartFlowLauncherOnSystemStartup = false;
API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message);
}
From 80c0288235fbeb5fd5c71d88d3d10229f713199a Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 13:24:32 +0800
Subject: [PATCH 23/51] Throw exception when editing highest run level task
---
Flow.Launcher/Helper/AutoStartup.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 371ab46effb..96578b2fa4e 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -78,8 +78,11 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
}
else
{
- // If run level is not correct, we cannot edit it because we are not administrator
- if (!runLevelCorrect)
+ // If run level is not correct or we need to change a highest run level task,
+ // we cannot edit it because we are not administrator
+ // So we just throw an exception to let the user know
+ if (!runLevelCorrect || // run level is not correct
+ alwaysRunAsAdministrator) // we need to change a highest run level task
{
throw new UnauthorizedAccessException("Cannot edit task run level because the app is not running as administrator.");
}
From 1b80290995c5446851298ec2c6f236c8d828d190 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Fri, 23 May 2025 16:11:29 +0800
Subject: [PATCH 24/51] Do not show noticification when run level is correct
---
Flow.Launcher/Helper/AutoStartup.cs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs
index 96578b2fa4e..2601645dbba 100644
--- a/Flow.Launcher/Helper/AutoStartup.cs
+++ b/Flow.Launcher/Helper/AutoStartup.cs
@@ -78,11 +78,9 @@ private static bool CheckLogonTask(bool alwaysRunAsAdministrator)
}
else
{
- // If run level is not correct or we need to change a highest run level task,
- // we cannot edit it because we are not administrator
+ // If run level is not correct, we cannot edit it because we are not administrator
// So we just throw an exception to let the user know
- if (!runLevelCorrect || // run level is not correct
- alwaysRunAsAdministrator) // we need to change a highest run level task
+ if (!runLevelCorrect)
{
throw new UnauthorizedAccessException("Cannot edit task run level because the app is not running as administrator.");
}
From ba1ada7bf1045d3b4cbc5015bf53ae72659b93e3 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 3 Jun 2025 14:45:12 +0800
Subject: [PATCH 25/51] Continue to restart app as administrator if app is run
as administrator already
---
Flow.Launcher/PublicAPIInstance.cs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index c06c5603991..5f427f90243 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -85,10 +85,18 @@ public async void RestartApp()
// Wait for all image caches to be saved before restarting
await ImageLoader.WaitSaveAsync();
- // Restart requires Squirrel's Update.exe to be present in the parent folder,
- // it is only published from the project's release pipeline. When debugging without it,
- // the project may not restart or just terminates. This is expected.
- UpdateManager.RestartApp(Constant.ApplicationFileName);
+ // If app is run as administrator already, we continue to restart app as administrator
+ if (Win32Helper.IsAdministrator())
+ {
+ App.RestartAppAsAdministrator();
+ }
+ else
+ {
+ // Restart requires Squirrel's Update.exe to be present in the parent folder,
+ // it is only published from the project's release pipeline. When debugging without it,
+ // the project may not restart or just terminates. This is expected.
+ UpdateManager.RestartApp(Constant.ApplicationFileName);
+ }
}
public void ShowMainWindow() => _mainVM.Show();
From b2edea42019b50cb02f085b8bec4010e52cce605 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 3 Jun 2025 17:21:48 +0800
Subject: [PATCH 26/51] Improve code quality
---
Flow.Launcher/App.xaml.cs | 21 ++++++++++++++++++++-
Flow.Launcher/PublicAPIInstance.cs | 17 ++++-------------
2 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index c3dc03ed7f9..69e57f4435a 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -26,6 +26,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.Threading;
+using Squirrel;
namespace Flow.Launcher
{
@@ -334,9 +335,27 @@ private static void RegisterTaskSchedulerUnhandledException()
#region Restart
+ ///
+ /// Restart the application without changing the user privileges.
+ ///
+ public static void RestartApp(bool forceAdmin = false)
+ {
+ if (Win32Helper.IsAdministrator() || forceAdmin)
+ {
+ RestartAppAsAdministrator();
+ }
+ else
+ {
+ // Restart requires Squirrel's Update.exe to be present in the parent folder,
+ // it is only published from the project's release pipeline. When debugging without it,
+ // the project may not restart or just terminates. This is expected.
+ UpdateManager.RestartApp(Constant.ApplicationFileName);
+ }
+ }
+
// Since Squirrel does not provide a way to restart the app as administrator,
// we need to do it manually by starting the update.exe with the runas verb
- public static void RestartAppAsAdministrator()
+ private static void RestartAppAsAdministrator()
{
var startInfo = new ProcessStartInfo
{
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 5f427f90243..a470b47397a 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -31,7 +31,6 @@
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
-using Squirrel;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher
@@ -85,18 +84,10 @@ public async void RestartApp()
// Wait for all image caches to be saved before restarting
await ImageLoader.WaitSaveAsync();
- // If app is run as administrator already, we continue to restart app as administrator
- if (Win32Helper.IsAdministrator())
- {
- App.RestartAppAsAdministrator();
- }
- else
- {
- // Restart requires Squirrel's Update.exe to be present in the parent folder,
- // it is only published from the project's release pipeline. When debugging without it,
- // the project may not restart or just terminates. This is expected.
- UpdateManager.RestartApp(Constant.ApplicationFileName);
- }
+ // Restart requires Squirrel's Update.exe to be present in the parent folder,
+ // it is only published from the project's release pipeline. When debugging without it,
+ // the project may not restart or just terminates. This is expected.
+ App.RestartApp();
}
public void ShowMainWindow() => _mainVM.Show();
From de26abe7b8be99ec920fc3d8b18ba76f71586adf Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 3 Jun 2025 17:22:05 +0800
Subject: [PATCH 27/51] Use api functions instead
---
Flow.Launcher.Core/Configuration/Portable.cs | 4 ++--
Flow.Launcher.Core/Updater.cs | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs
index 7f02cef0990..00662d1e5da 100644
--- a/Flow.Launcher.Core/Configuration/Portable.cs
+++ b/Flow.Launcher.Core/Configuration/Portable.cs
@@ -48,7 +48,7 @@ public void DisablePortableMode()
API.ShowMsgBox("Flow Launcher needs to restart to finish disabling portable mode, " +
"after the restart your portable data profile will be deleted and roaming data profile kept");
- UpdateManager.RestartApp(Constant.ApplicationFileName);
+ API.RestartApp();
}
catch (Exception e)
{
@@ -72,7 +72,7 @@ public void EnablePortableMode()
API.ShowMsgBox("Flow Launcher needs to restart to finish enabling portable mode, " +
"after the restart your roaming data profile will be deleted and portable data profile kept");
- UpdateManager.RestartApp(Constant.ApplicationFileName);
+ API.RestartApp();
}
catch (Exception e)
{
diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs
index bc3655f69e7..f0a573f9550 100644
--- a/Flow.Launcher.Core/Updater.cs
+++ b/Flow.Launcher.Core/Updater.cs
@@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
-using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
using JetBrains.Annotations;
using Squirrel;
@@ -89,7 +89,7 @@ public async Task UpdateAppAsync(bool silentUpdate = true)
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- UpdateManager.RestartApp(Constant.ApplicationFileName);
+ _api.RestartApp();
}
}
catch (Exception e)
From 23f24896801eeae5072f592db59136a1c57183be Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 3 Jun 2025 17:22:26 +0800
Subject: [PATCH 28/51] Force admin restart and fix build issue
---
.../SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index e7c7333c27e..05cb20b304b 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -167,7 +167,7 @@ private async Task CheckAdminChangeAndAskForRestartAsync()
await ImageLoader.WaitSaveAsync();
// Restart the app as administrator
- App.RestartAppAsAdministrator();
+ App.RestartApp(true);
}
}
}
From 1a8334f1d3426775079c281b4319f8eeb2720142 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 15:18:14 +0800
Subject: [PATCH 29/51] Add RunAsDesktopUser helper method
---
.../NativeMethods.txt | 15 ++
Flow.Launcher.Infrastructure/Win32Helper.cs | 136 ++++++++++++++++++
2 files changed, 151 insertions(+)
diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
index edc71feef24..403e1550483 100644
--- a/Flow.Launcher.Infrastructure/NativeMethods.txt
+++ b/Flow.Launcher.Infrastructure/NativeMethods.txt
@@ -66,3 +66,18 @@ LOCALE_TRANSIENT_KEYBOARD4
SHParseDisplayName
SHOpenFolderAndSelectItems
CoTaskMemFree
+
+OpenProcessToken
+GetCurrentProcess
+LookupPrivilegeValue
+SE_INCREASE_QUOTA_NAME
+CloseHandle
+TOKEN_PRIVILEGES
+AdjustTokenPrivileges
+GetShellWindow
+GetWindowThreadProcessId
+OpenProcess
+GetProcessId
+DuplicateTokenEx
+CreateProcessWithTokenW
+STARTUPINFO
\ No newline at end of file
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index e6b59dc6199..8c8809e4375 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -18,6 +18,8 @@
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
+using Windows.Win32.Security;
+using Windows.Win32.System.Threading;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.Shell.Common;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -802,6 +804,140 @@ public static bool IsAdministrator()
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
+ ///
+ /// Inspired by
+ /// Document:
+ ///
+ public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string currentDir, out string errorInfo)
+ {
+ STARTUPINFOW si = new();
+ PROCESS_INFORMATION pi = new();
+ errorInfo = string.Empty;
+ HANDLE hShellProcess = HANDLE.Null, hShellProcessToken = HANDLE.Null, hPrimaryToken = HANDLE.Null;
+ HWND hwnd;
+ uint dwPID;
+
+ // 1. Enable the SeIncreaseQuotaPrivilege in your current token
+ if (!PInvoke.OpenProcessToken(PInvoke.GetCurrentProcess_SafeHandle(), TOKEN_ACCESS_MASK.TOKEN_ADJUST_PRIVILEGES, out var hProcessToken))
+ {
+ errorInfo = $"OpenProcessToken failed: {Marshal.GetLastWin32Error()}";
+ return false;
+ }
+
+ if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_INCREASE_QUOTA_NAME, out var luid))
+ {
+ errorInfo = $"LookupPrivilegeValue failed: {Marshal.GetLastWin32Error()}";
+ hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken);
+ return false;
+ }
+
+ var tp = new TOKEN_PRIVILEGES
+ {
+ PrivilegeCount = 1,
+ Privileges = new()
+ {
+ e0 = new LUID_AND_ATTRIBUTES
+ {
+ Luid = luid,
+ Attributes = TOKEN_PRIVILEGES_ATTRIBUTES.SE_PRIVILEGE_ENABLED
+ }
+ }
+ };
+
+ PInvoke.AdjustTokenPrivileges(hProcessToken, false, &tp, 0, null, null);
+ var lastError = Marshal.GetLastWin32Error();
+ hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken);
+
+ if (lastError != 0)
+ {
+ errorInfo = $"AdjustTokenPrivileges failed: {lastError}";
+ return false;
+ }
+
+retry:
+ // 2. Get an HWND representing the desktop shell
+ hwnd = PInvoke.GetShellWindow();
+ if (hwnd == HWND.Null)
+ {
+ errorInfo = "No desktop shell is present.";
+ return false;
+ }
+
+ // 3. Get the Process ID (PID) of the process associated with that window
+ _ = PInvoke.GetWindowThreadProcessId(hwnd, &dwPID);
+ if (dwPID == 0)
+ {
+ errorInfo = "Unable to get PID of desktop shell.";
+ return false;
+ }
+
+ // 4. Open that process
+ hShellProcess = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION, false, dwPID);
+ if (hShellProcess == IntPtr.Zero)
+ {
+ errorInfo = $"Can't open desktop shell process: {Marshal.GetLastWin32Error()}";
+ return false;
+ }
+
+ if (hwnd != PInvoke.GetShellWindow())
+ {
+ PInvoke.CloseHandle(hShellProcess);
+ goto retry;
+ }
+
+ _ = PInvoke.GetWindowThreadProcessId(hwnd, &dwPID);
+ if (dwPID != PInvoke.GetProcessId(hShellProcess))
+ {
+ PInvoke.CloseHandle(hShellProcess);
+ goto retry;
+ }
+
+ // 5. Get the access token from that process
+ if (!PInvoke.OpenProcessToken(hShellProcess, TOKEN_ACCESS_MASK.TOKEN_DUPLICATE, &hShellProcessToken))
+ {
+ errorInfo = $"Can't get process token of desktop shell: {Marshal.GetLastWin32Error()}";
+ goto cleanup;
+ }
+
+ // 6. Make a primary token with that token
+ var tokenRights = TOKEN_ACCESS_MASK.TOKEN_QUERY | TOKEN_ACCESS_MASK.TOKEN_ASSIGN_PRIMARY |
+ TOKEN_ACCESS_MASK.TOKEN_DUPLICATE | TOKEN_ACCESS_MASK.TOKEN_ADJUST_DEFAULT |
+ TOKEN_ACCESS_MASK.TOKEN_ADJUST_SESSIONID;
+ if (!PInvoke.DuplicateTokenEx(hShellProcessToken, tokenRights, null, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, &hPrimaryToken))
+ {
+ errorInfo = $"Can't get primary token: {Marshal.GetLastWin32Error()}";
+ goto cleanup;
+ }
+
+ // 7. Start the new process with that primary token
+ fixed (char* appPtr = app)
+ fixed (char* cmdLinePtr = cmdLine)
+ fixed (char* currentDirPtr = currentDir)
+ {
+ if (!PInvoke.CreateProcessWithToken(hPrimaryToken,
+ 0 /*CREATE_PROCESS_LOGON_FLAGS.LOGON_WITH_PROFILE*/,
+ appPtr,
+ cmdLinePtr,
+ 0/*PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE*/,
+ null,
+ currentDirPtr,
+ &si,
+ &pi))
+ {
+ errorInfo = $"CreateProcessWithTokenW failed: {Marshal.GetLastWin32Error()}";
+ goto cleanup;
+ }
+ }
+
+ return true;
+
+cleanup:
+ if (hShellProcessToken != HANDLE.Null) PInvoke.CloseHandle(hShellProcessToken);
+ if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken);
+ if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess);
+ return false;
+ }
+
#endregion
}
}
From e391c984748ac073e14c67a77395bb89e4ec0385 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 15:35:17 +0800
Subject: [PATCH 30/51] Code quality
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 8c8809e4375..45b26b58e69 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -827,7 +827,7 @@ public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string cu
if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_INCREASE_QUOTA_NAME, out var luid))
{
errorInfo = $"LookupPrivilegeValue failed: {Marshal.GetLastWin32Error()}";
- hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken);
+ hProcessToken.Dispose();
return false;
}
@@ -846,7 +846,7 @@ public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string cu
PInvoke.AdjustTokenPrivileges(hProcessToken, false, &tp, 0, null, null);
var lastError = Marshal.GetLastWin32Error();
- hProcessToken.Dispose();//PInvoke.CloseHandle(hProcessToken);
+ hProcessToken.Dispose();
if (lastError != 0)
{
@@ -915,10 +915,10 @@ public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string cu
fixed (char* currentDirPtr = currentDir)
{
if (!PInvoke.CreateProcessWithToken(hPrimaryToken,
- 0 /*CREATE_PROCESS_LOGON_FLAGS.LOGON_WITH_PROFILE*/,
+ 0,
appPtr,
cmdLinePtr,
- 0/*PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE*/,
+ 0,
null,
currentDirPtr,
&si,
From 100c21a83e33c7bab87745b184863f7c7ba639b6 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 16:02:24 +0800
Subject: [PATCH 31/51] Add new api to run process as desktop user
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 12 ++++++++
Flow.Launcher/PublicAPIInstance.cs | 29 +++++++++++++++++--
2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 76c7a4911bf..13564626b27 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -580,5 +580,17 @@ public interface IPublicAPI
///
/// The time taken to execute the method in milliseconds
public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "");
+
+ ///
+ /// Start a process with the given file path and arguments
+ ///
+ ///
+ /// It can help to start a deelevated process when Flow is running as administrator.
+ ///
+ /// File path of the process to start. It can be an executable file or a script file
+ /// Working directory of the process. If not specified, the current directory will be used
+ /// Optional arguments to pass to the process. If not specified, no arguments will be passed
+ /// Whether to run the process as administrator
+ public void StartProcess(string filePath, string workingDirectory, string arguments = "", bool runAsAdmin = false);
}
}
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 694b13cdd76..3f003e6606f 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -14,21 +14,21 @@
using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core;
+using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
-using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Storage;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Hotkey;
+using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
-using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Plugin.SharedCommands;
+using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
using JetBrains.Annotations;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
@@ -577,6 +577,29 @@ public long StopwatchLogInfo(string className, string message, Action action, [C
public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "") =>
Stopwatch.InfoAsync(className, message, action, methodName);
+ public void StartProcess(string filePath, string workingDirectory, string arguments = "", bool runAsAdmin = false)
+ {
+ // Deelevate process if it is running as administrator
+ if (Win32Helper.IsAdministrator() && !runAsAdmin)
+ {
+ Win32Helper.RunAsDesktopUser(filePath, workingDirectory, arguments, out var errorInfo);
+ if (!string.IsNullOrEmpty(errorInfo))
+ {
+ LogError(ClassName, $"Failed to start process {filePath} with error: {errorInfo}");
+ }
+ }
+
+ var info = new ProcessStartInfo
+ {
+ FileName = filePath,
+ WorkingDirectory = workingDirectory,
+ Arguments = arguments,
+ UseShellExecute = true,
+ Verb = runAsAdmin ? "runas" : "",
+ };
+ Process.Start(info)?.Dispose();
+ }
+
#endregion
#region Private Methods
From fc53efd6a6254778320b8bd8db54dd306008315d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 16:04:17 +0800
Subject: [PATCH 32/51] Use new api function to open application
---
.../Programs/Win32.cs | 32 +++----------------
1 file changed, 5 insertions(+), 27 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 4e1d99454be..e3dec1c6065 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -208,38 +208,16 @@ public Result Result(string query, IPublicAPI api)
private void Launch(bool elevated = false)
{
- var info = new ProcessStartInfo
+ if (Main.IsAdmin && elevated)
{
- FileName = FullPath,
- WorkingDirectory = ParentDirectory,
- UseShellExecute = true,
- Verb = elevated ? "runas" : "",
- };
-
- if (Main.IsAdmin)
- {
- if (elevated)
+ // Since we are already elevated, we need to create UAC dialog manually
+ if (UACDialog.Show(IcoPath, Name, FullPath) != MessageBoxResult.Yes)
{
- // Since we are already elevated, we need to create UAC dialog manually
- if (UACDialog.Show(IcoPath, Name, FullPath) != MessageBoxResult.Yes)
- {
- return;
- }
- }
- else
- {
- // Use explorer.exe as workaround to start process as standard user
- info = new ProcessStartInfo
- {
- FileName = "explorer.exe",
- Arguments = $"\"{FullPath}\"",
- WorkingDirectory = ParentDirectory,
- UseShellExecute = true,
- };
+ return;
}
}
- _ = Task.Run(() => Main.StartProcess(Process.Start, info)).ConfigureAwait(false);
+ _ = Task.Run(() => Main.Context.API.StartProcess(FullPath, ParentDirectory, "", elevated)).ConfigureAwait(false);
}
public List ContextMenus(IPublicAPI api)
From d77993e215ba878aa754f8e94c46dc91f2c99afe Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 16:22:24 +0800
Subject: [PATCH 33/51] Use new api function to start shell process
---
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index d0add9f3155..b03f9c0a01c 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -17,7 +17,7 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
{
private static readonly string ClassName = nameof(Main);
- internal PluginInitContext Context { get; private set; }
+ internal static PluginInitContext Context { get; private set; }
private const string Image = "Images/shell.png";
private bool _winRStroked;
@@ -80,7 +80,7 @@ public List Query(Query query)
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
- Execute(Process.Start, PrepareProcessStartInfo(m, runAsAdministrator));
+ Execute(StartProcess, PrepareProcessStartInfo(m, runAsAdministrator));
return true;
},
CopyText = m
@@ -120,7 +120,7 @@ private List GetHistoryCmds(string cmd, Result result)
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
- Execute(Process.Start, PrepareProcessStartInfo(m.Key, runAsAdministrator));
+ Execute(StartProcess, PrepareProcessStartInfo(m.Key, runAsAdministrator));
return true;
},
CopyText = m.Key
@@ -150,7 +150,7 @@ private Result GetCurrentCmd(string cmd)
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
- Execute(Process.Start, PrepareProcessStartInfo(cmd, runAsAdministrator));
+ Execute(StartProcess, PrepareProcessStartInfo(cmd, runAsAdministrator));
return true;
},
CopyText = cmd
@@ -175,7 +175,7 @@ private List ResultsFromHistory()
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
- Execute(Process.Start, PrepareProcessStartInfo(m.Key, runAsAdministrator));
+ Execute(StartProcess, PrepareProcessStartInfo(m.Key, runAsAdministrator));
return true;
},
CopyText = m.Key
@@ -307,7 +307,13 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin
return info;
}
- private void Execute(Func startProcess, ProcessStartInfo info)
+ private static Process StartProcess(ProcessStartInfo info)
+ {
+ Context.API.StartProcess(info.FileName, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
+ return null;
+ }
+
+ private static void Execute(Func startProcess, ProcessStartInfo info)
{
try
{
@@ -434,7 +440,7 @@ public List LoadContextMenus(Result selectedResult)
Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"),
Action = c =>
{
- Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
+ Execute(StartProcess, PrepareProcessStartInfo(selectedResult.Title, true));
return true;
},
IcoPath = "Images/admin.png",
From 4cf66362afd56a40f4f7093be46e3deefca9cb90 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 16:52:19 +0800
Subject: [PATCH 34/51] Fix handle close issue
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 45b26b58e69..cfc73ad90c0 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -929,6 +929,11 @@ public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string cu
}
}
+ if (pi.hProcess != HANDLE.Null) PInvoke.CloseHandle(pi.hProcess);
+ if (pi.hThread != HANDLE.Null) PInvoke.CloseHandle(pi.hThread);
+ if (hShellProcessToken != HANDLE.Null) PInvoke.CloseHandle(hShellProcessToken);
+ if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken);
+ if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess);
return true;
cleanup:
From f1f7309762dfe564f8b0a8d6ec3fa39124e747cf Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 16:55:38 +0800
Subject: [PATCH 35/51] Add working directory check & Add try-catch
---
Flow.Launcher/PublicAPIInstance.cs | 35 ++++++++++++++++++------------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 3f003e6606f..9b8b4f07e3b 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -579,25 +579,32 @@ public Task StopwatchLogInfoAsync(string className, string message, Func
Date: Wed, 11 Jun 2025 20:00:02 +0800
Subject: [PATCH 36/51] Update documents
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 13564626b27..7b0e1dc8c56 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -587,8 +587,8 @@ public interface IPublicAPI
///
/// It can help to start a deelevated process when Flow is running as administrator.
///
- /// File path of the process to start. It can be an executable file or a script file
- /// Working directory of the process. If not specified, the current directory will be used
+ /// Absolute file path. It can be an executable file or a script file
+ /// Working directory. If not specified, the current directory will be used
/// Optional arguments to pass to the process. If not specified, no arguments will be passed
/// Whether to run the process as administrator
public void StartProcess(string filePath, string workingDirectory, string arguments = "", bool runAsAdmin = false);
From ea9ede2e0e31e0c63bcad67508a33b1b6ca97f3d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 20:00:25 +0800
Subject: [PATCH 37/51] Fix deelevate process running issue
---
Flow.Launcher/PublicAPIInstance.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 9b8b4f07e3b..f82ebdc37f1 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -589,6 +589,7 @@ public void StartProcess(string filePath, string workingDirectory, string argume
{
LogError(ClassName, $"Failed to start process {filePath} with error: {errorInfo}");
}
+ return;
}
var info = new ProcessStartInfo
From d13901bb9deebb52c6fa7357a8987c9c0d838c8e Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 20:00:40 +0800
Subject: [PATCH 38/51] Update work directory correctly
---
Flow.Launcher/PublicAPIInstance.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index f82ebdc37f1..2f29e098eb8 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -581,6 +581,8 @@ public void StartProcess(string filePath, string workingDirectory, string argume
{
try
{
+ workingDirectory = string.IsNullOrEmpty(workingDirectory) ? Environment.CurrentDirectory : workingDirectory;
+
// Deelevate process if it is running as administrator
if (Win32Helper.IsAdministrator() && !runAsAdmin)
{
@@ -595,7 +597,7 @@ public void StartProcess(string filePath, string workingDirectory, string argume
var info = new ProcessStartInfo
{
FileName = filePath,
- WorkingDirectory = string.IsNullOrEmpty(workingDirectory) ? Environment.CurrentDirectory : workingDirectory,
+ WorkingDirectory = workingDirectory,
Arguments = arguments,
UseShellExecute = true,
Verb = runAsAdmin ? "runas" : "",
From f9b2be1fc7119055a02fcc5444fe7115567c485b Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 20:01:37 +0800
Subject: [PATCH 39/51] Let work directory can be empty
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +-
Flow.Launcher/PublicAPIInstance.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 7b0e1dc8c56..a413fe98ef2 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -591,6 +591,6 @@ public interface IPublicAPI
/// Working directory. If not specified, the current directory will be used
/// Optional arguments to pass to the process. If not specified, no arguments will be passed
/// Whether to run the process as administrator
- public void StartProcess(string filePath, string workingDirectory, string arguments = "", bool runAsAdmin = false);
+ public void StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false);
}
}
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 2f29e098eb8..e76ea1a5a6a 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -577,7 +577,7 @@ public long StopwatchLogInfo(string className, string message, Action action, [C
public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "") =>
Stopwatch.InfoAsync(className, message, action, methodName);
- public void StartProcess(string filePath, string workingDirectory, string arguments = "", bool runAsAdmin = false)
+ public void StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false)
{
try
{
From 30deb6da99e3c7f5e96dd7e4dd7ec2d67821cbd2 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 20:02:32 +0800
Subject: [PATCH 40/51] Fix argument sequence issue
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index cfc73ad90c0..9336ce44c61 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -808,7 +808,7 @@ public static bool IsAdministrator()
/// Inspired by
/// Document:
///
- public static unsafe bool RunAsDesktopUser(string app, string cmdLine, string currentDir, out string errorInfo)
+ public static unsafe bool RunAsDesktopUser(string app, string currentDir, string cmdLine, out string errorInfo)
{
STARTUPINFOW si = new();
PROCESS_INFORMATION pi = new();
From fb4735fbc081b0e0b19bc335a680cbe75fcb0e7f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 20:02:54 +0800
Subject: [PATCH 41/51] Fix program deelevate open issue
---
Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index e3dec1c6065..4c141238faa 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -217,7 +217,7 @@ private void Launch(bool elevated = false)
}
}
- _ = Task.Run(() => Main.Context.API.StartProcess(FullPath, ParentDirectory, "", elevated)).ConfigureAwait(false);
+ _ = Task.Run(() => Main.Context.API.StartProcess(LnkResolvedPath, runAsAdmin:elevated)).ConfigureAwait(false);
}
public List ContextMenus(IPublicAPI api)
From b15cece4b8edd6ec9ad8a3ffd633d6bad842a2da Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 21:13:09 +0800
Subject: [PATCH 42/51] Fix absolute exe file path issue
---
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index b03f9c0a01c..2728628d537 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -23,6 +23,13 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
private bool _winRStroked;
private readonly KeyboardSimulator _keyboardSimulator = new(new InputSimulator());
+ private static readonly string[] possiblePwshPaths = new[]
+ {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"PowerShell\7\pwsh.exe"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\pwsh.exe"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @"scoop\apps\pwsh\current\pwsh.exe") // if using Scoop
+ };
+
private Settings _settings;
public List Query(Query query)
@@ -309,7 +316,15 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin
private static Process StartProcess(ProcessStartInfo info)
{
- Context.API.StartProcess(info.FileName, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
+ var absoluteFileName = info.FileName switch
+ {
+ "cmd.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"),
+ "powershell.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"WindowsPowerShell\v1.0\powershell.exe"),
+ "pwsh.exe" => possiblePwshPaths.FirstOrDefault(File.Exists),
+ "wt.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\wt.exe"),
+ _ => info.FileName,
+ };
+ Context.API.StartProcess(absoluteFileName, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
return null;
}
From 255ca5ac01a7ed3db68cf97350ed8a2d1ecbe5c1 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 11 Jun 2025 21:34:33 +0800
Subject: [PATCH 43/51] Improve code quality
---
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 30 +++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index 2728628d537..bcc5c470e7a 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -23,6 +23,18 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
private bool _winRStroked;
private readonly KeyboardSimulator _keyboardSimulator = new(new InputSimulator());
+ private static readonly string[] possibleCmdPaths = new[]
+ {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"System32\cmd.exe")
+ };
+
+ private static readonly string[] possiblePowershellPaths = new[]
+ {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"WindowsPowerShell\v1.0\powershell.exe"),
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"System32\WindowsPowerShell\v1.0\powershell.exe")
+ };
+
private static readonly string[] possiblePwshPaths = new[]
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"PowerShell\7\pwsh.exe"),
@@ -30,6 +42,13 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @"scoop\apps\pwsh\current\pwsh.exe") // if using Scoop
};
+ private static readonly string[] possibleWTPowerShellPaths = new[]
+ {
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\wt.exe"),
+ // if using Windows Terminal from Microsoft Store
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"WindowsApps\Microsoft.WindowsTerminal_*\wt.exe")
+ };
+
private Settings _settings;
public List Query(Query query)
@@ -318,12 +337,17 @@ private static Process StartProcess(ProcessStartInfo info)
{
var absoluteFileName = info.FileName switch
{
- "cmd.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"),
- "powershell.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"WindowsPowerShell\v1.0\powershell.exe"),
+ "cmd.exe" => possibleCmdPaths.FirstOrDefault(File.Exists),
+ "powershell.exe" => possiblePowershellPaths.FirstOrDefault(File.Exists),
"pwsh.exe" => possiblePwshPaths.FirstOrDefault(File.Exists),
- "wt.exe" => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\wt.exe"),
+ "wt.exe" => possibleWTPowerShellPaths.FirstOrDefault(File.Exists),
_ => info.FileName,
};
+ if (string.IsNullOrEmpty(absoluteFileName))
+ {
+ Context.API.LogError(ClassName, $"The command '{info.FileName}' could not be found in the system PATH or the specified directories.");
+ return null;
+ }
Context.API.StartProcess(absoluteFileName, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
return null;
}
From 433e0fd0e198aae877c7a04b9be42648c7b04387 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 15:18:14 +0800
Subject: [PATCH 44/51] Improve uac dialog path
---
Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 4c141238faa..7e78a7fb640 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -211,7 +211,7 @@ private void Launch(bool elevated = false)
if (Main.IsAdmin && elevated)
{
// Since we are already elevated, we need to create UAC dialog manually
- if (UACDialog.Show(IcoPath, Name, FullPath) != MessageBoxResult.Yes)
+ if (UACDialog.Show(IcoPath, Name, LnkResolvedPath) != MessageBoxResult.Yes)
{
return;
}
From 21e8d92aa2d4ba5ad3709febf8066672454d54a0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 15:30:25 +0800
Subject: [PATCH 45/51] Add show uac dialog api function
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 15 +++++++++++++++
Flow.Launcher/Languages/en.xaml | 5 +++++
Flow.Launcher/PublicAPIInstance.cs | 3 +++
.../UACDialog.xaml | 7 ++++---
.../UACDialog.xaml.cs | 13 +++++--------
.../Languages/en.xaml | 3 ---
.../Programs/Win32.cs | 2 +-
7 files changed, 33 insertions(+), 15 deletions(-)
rename {Plugins/Flow.Launcher.Plugin.Program => Flow.Launcher}/UACDialog.xaml (95%)
rename {Plugins/Flow.Launcher.Plugin.Program => Flow.Launcher}/UACDialog.xaml.cs (80%)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index a413fe98ef2..9e3d279ab2a 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -592,5 +592,20 @@ public interface IPublicAPI
/// Optional arguments to pass to the process. If not specified, no arguments will be passed
/// Whether to run the process as administrator
public void StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false);
+
+ ///
+ /// Displays a User Account Control (UAC) dialog to request elevated permissions for the specified application.
+ ///
+ ///
+ /// If Flow is running under administrator mode, executing elevated actions will not trigger UAC dialog.
+ /// So this method is used to manually show the UAC dialog when needed.
+ ///
+ /// The file path to the icon that will be displayed in the dialog. Must be a valid path to an image file.
+ /// The name of the application requesting elevation. This will be displayed in the dialog.
+ /// The full file path of the executable requesting elevation. This is shown to the user for verification.
+ /// A indicating the user's response to the dialog. Possible values include
+ /// if the user grants permission,
+ /// or if the user denies permission.
+ public MessageBoxResult ShowUACDialog(string iconPath, string appName, string fullPath);
}
}
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 97f294b14cb..aea9d4a6056 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -379,6 +379,11 @@
Flow Launcher has been updated to {0}
Click here to view the release notes
+
+ User Account Control
+ Do you want to allow this app to make changes to your device?
+ Program location: {0}
+
Select File Manager
Learn more
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index e76ea1a5a6a..6a4a4493d0c 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -610,6 +610,9 @@ public void StartProcess(string filePath, string workingDirectory = "", string a
}
}
+ public MessageBoxResult ShowUACDialog(string iconPath, string appName, string fullPath) =>
+ UACDialog.Show(iconPath, appName, fullPath);
+
#endregion
#region Private Methods
diff --git a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml b/Flow.Launcher/UACDialog.xaml
similarity index 95%
rename from Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml
rename to Flow.Launcher/UACDialog.xaml
index d619f47654d..9d13a2c7b08 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml
+++ b/Flow.Launcher/UACDialog.xaml
@@ -1,10 +1,11 @@
@@ -86,7 +87,7 @@
Margin="0 0 26 0"
HorizontalAlignment="Stretch"
FontSize="14"
- Text="{DynamicResource flowlauncher_plugin_program_user_account_control_subtitle}"
+ Text="{DynamicResource userAccountControlSubtitle}"
TextAlignment="Left"
TextWrapping="Wrap" />
diff --git a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs b/Flow.Launcher/UACDialog.xaml.cs
similarity index 80%
rename from Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs
rename to Flow.Launcher/UACDialog.xaml.cs
index 3ab087b489f..0d5e8a46d1b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/UACDialog.xaml.cs
+++ b/Flow.Launcher/UACDialog.xaml.cs
@@ -3,7 +3,7 @@
using System.Windows;
using System.Windows.Input;
-namespace Flow.Launcher.Plugin.Program
+namespace Flow.Launcher
{
public partial class UACDialog : Window
{
@@ -26,16 +26,13 @@ public static MessageBoxResult Show(string iconPath, string appName, string full
try
{
- msgBox = new UACDialog
- {
- Title = Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_title")
- };
+ msgBox = new UACDialog();
// Set icon & app name & program location
_ = msgBox.SetImageAsync(iconPath);
msgBox.AppName.Text = appName;
msgBox.ProgramLocation.Text = string.Format(
- Main.Context.API.GetTranslation("flowlauncher_plugin_program_user_account_control_program_location"),
+ App.API.GetTranslation("userAccountControlProgramLocation"),
fullPath);
// Focus No by default
@@ -47,7 +44,7 @@ public static MessageBoxResult Show(string iconPath, string appName, string full
}
catch (Exception e)
{
- Main.Context.API.LogError(ClassName, $"An error occurred: {e.Message}");
+ App.API.LogError(ClassName, $"An error occurred: {e.Message}");
msgBox = null;
return MessageBoxResult.None;
}
@@ -55,7 +52,7 @@ public static MessageBoxResult Show(string iconPath, string appName, string full
private async Task SetImageAsync(string imagePath)
{
- var imageSource = await Main.Context.API.LoadImageAsync(imagePath);
+ var imageSource = await App.API.LoadImageAsync(imagePath);
Img.Source = imageSource;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
index e3eeb4e91a5..e551a7dcb4e 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
@@ -97,8 +97,5 @@
Successfully disabled this program from displaying in your query
This app is not intended to be run as administrator
Unable to run {0}
- User Account Control
- Do you want to allow this app to make changes to your device?
- Program location: {0}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 7e78a7fb640..4e256d2cb47 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -211,7 +211,7 @@ private void Launch(bool elevated = false)
if (Main.IsAdmin && elevated)
{
// Since we are already elevated, we need to create UAC dialog manually
- if (UACDialog.Show(IcoPath, Name, LnkResolvedPath) != MessageBoxResult.Yes)
+ if (Main.Context.API.ShowUACDialog(IcoPath, Name, LnkResolvedPath) != MessageBoxResult.Yes)
{
return;
}
From 0d1358fdc314fe8560eb0bc45eedf6e81c0c5716 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 15:32:34 +0800
Subject: [PATCH 46/51] Add return value for start process api function
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 3 ++-
Flow.Launcher/PublicAPIInstance.cs | 8 ++++++--
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 9e3d279ab2a..8c02151be82 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -591,7 +591,8 @@ public interface IPublicAPI
/// Working directory. If not specified, the current directory will be used
/// Optional arguments to pass to the process. If not specified, no arguments will be passed
/// Whether to run the process as administrator
- public void StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false);
+ /// Whether process is started successfully
+ public bool StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false);
///
/// Displays a User Account Control (UAC) dialog to request elevated permissions for the specified application.
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 6a4a4493d0c..d02b718d067 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -577,7 +577,7 @@ public long StopwatchLogInfo(string className, string message, Action action, [C
public Task StopwatchLogInfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "") =>
Stopwatch.InfoAsync(className, message, action, methodName);
- public void StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false)
+ public bool StartProcess(string filePath, string workingDirectory = "", string arguments = "", bool runAsAdmin = false)
{
try
{
@@ -590,8 +590,10 @@ public void StartProcess(string filePath, string workingDirectory = "", string a
if (!string.IsNullOrEmpty(errorInfo))
{
LogError(ClassName, $"Failed to start process {filePath} with error: {errorInfo}");
+ return false;
}
- return;
+
+ return true;
}
var info = new ProcessStartInfo
@@ -603,10 +605,12 @@ public void StartProcess(string filePath, string workingDirectory = "", string a
Verb = runAsAdmin ? "runas" : "",
};
Process.Start(info)?.Dispose();
+ return true;
}
catch (Exception e)
{
LogException(ClassName, $"Failed to start process {filePath} with arguments {arguments} under {workingDirectory}", e);
+ return false;
}
}
From a7753231081f58ed8b59b66638ac501626937f06 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 16:07:09 +0800
Subject: [PATCH 47/51] Improve uac dialog api function
---
Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 4 +--
Flow.Launcher/PublicAPIInstance.cs | 4 +--
Flow.Launcher/UACDialog.xaml.cs | 25 +++++++++++++++----
.../Programs/Win32.cs | 2 +-
4 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index 8c02151be82..6382ecd0fbe 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -601,12 +601,12 @@ public interface IPublicAPI
/// If Flow is running under administrator mode, executing elevated actions will not trigger UAC dialog.
/// So this method is used to manually show the UAC dialog when needed.
///
- /// The file path to the icon that will be displayed in the dialog. Must be a valid path to an image file.
/// The name of the application requesting elevation. This will be displayed in the dialog.
+ /// The file path to the icon that will be displayed in the dialog. Must be a valid path to an image file.
/// The full file path of the executable requesting elevation. This is shown to the user for verification.
/// A indicating the user's response to the dialog. Possible values include
/// if the user grants permission,
/// or if the user denies permission.
- public MessageBoxResult ShowUACDialog(string iconPath, string appName, string fullPath);
+ public MessageBoxResult ShowUACDialog(string appName, string iconPath = "", string fullPath = "");
}
}
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index d02b718d067..4ff9659bba7 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -614,8 +614,8 @@ public bool StartProcess(string filePath, string workingDirectory = "", string a
}
}
- public MessageBoxResult ShowUACDialog(string iconPath, string appName, string fullPath) =>
- UACDialog.Show(iconPath, appName, fullPath);
+ public MessageBoxResult ShowUACDialog(string appName, string iconPath = "", string fullPath = "") =>
+ UACDialog.Show(appName, iconPath, fullPath);
#endregion
diff --git a/Flow.Launcher/UACDialog.xaml.cs b/Flow.Launcher/UACDialog.xaml.cs
index 0d5e8a46d1b..e49eac685d6 100644
--- a/Flow.Launcher/UACDialog.xaml.cs
+++ b/Flow.Launcher/UACDialog.xaml.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
@@ -17,11 +18,11 @@ private UACDialog()
InitializeComponent();
}
- public static MessageBoxResult Show(string iconPath, string appName, string fullPath)
+ public static MessageBoxResult Show(string appName, string iconPath, string fullPath)
{
if (!Application.Current.Dispatcher.CheckAccess())
{
- return Application.Current.Dispatcher.Invoke(() => Show(iconPath, appName, fullPath));
+ return Application.Current.Dispatcher.Invoke(() => Show(appName, iconPath, fullPath));
}
try
@@ -31,9 +32,18 @@ public static MessageBoxResult Show(string iconPath, string appName, string full
// Set icon & app name & program location
_ = msgBox.SetImageAsync(iconPath);
msgBox.AppName.Text = appName;
- msgBox.ProgramLocation.Text = string.Format(
- App.API.GetTranslation("userAccountControlProgramLocation"),
- fullPath);
+ if (string.IsNullOrEmpty(fullPath))
+ {
+ msgBox.ProgramLocation.Text = string.Format(
+ App.API.GetTranslation("userAccountControlProgramLocation"),
+ appName);
+ }
+ else
+ {
+ msgBox.ProgramLocation.Text = string.Format(
+ App.API.GetTranslation("userAccountControlProgramLocation"),
+ fullPath);
+ }
// Focus No by default
msgBox.btnNo.Focus();
@@ -52,6 +62,11 @@ public static MessageBoxResult Show(string iconPath, string appName, string full
private async Task SetImageAsync(string imagePath)
{
+ if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
+ {
+ Img.Visibility = Visibility.Collapsed;
+ return;
+ }
var imageSource = await App.API.LoadImageAsync(imagePath);
Img.Source = imageSource;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index 4e256d2cb47..905fab91370 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -211,7 +211,7 @@ private void Launch(bool elevated = false)
if (Main.IsAdmin && elevated)
{
// Since we are already elevated, we need to create UAC dialog manually
- if (Main.Context.API.ShowUACDialog(IcoPath, Name, LnkResolvedPath) != MessageBoxResult.Yes)
+ if (Main.Context.API.ShowUACDialog(Name, IcoPath, LnkResolvedPath) != MessageBoxResult.Yes)
{
return;
}
From a3212affd72aa9bec222c5cab6a1d9f8dd4b3c96 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 16:16:47 +0800
Subject: [PATCH 48/51] Dynamically find wt.exe
---
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index bcc5c470e7a..a00319c7991 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -42,11 +42,9 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @"scoop\apps\pwsh\current\pwsh.exe") // if using Scoop
};
- private static readonly string[] possibleWTPowerShellPaths = new[]
+ private static readonly List possibleWindowsTerminalPaths = new()
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\wt.exe"),
- // if using Windows Terminal from Microsoft Store
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"WindowsApps\Microsoft.WindowsTerminal_*\wt.exe")
};
private Settings _settings;
@@ -406,6 +404,21 @@ public void Init(PluginInitContext context)
Context = context;
_settings = context.API.LoadSettingJsonStorage();
context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent);
+
+ // Search for all folders with Microsoft.WindowsTerminal_ prefix
+ var windowsAppsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps");
+ var windowsTerminalFolders = Directory.EnumerateDirectories(windowsAppsPath, "Microsoft.WindowsTerminal_*",
+ SearchOption.TopDirectoryOnly);
+
+ // Find wt.exe among them
+ foreach (var windowsTerminalFolder in windowsTerminalFolders)
+ {
+ var windowsTerminalPath = Path.Combine(windowsTerminalFolder, "wt.exe");
+ if (File.Exists(windowsTerminalPath))
+ {
+ possibleWindowsTerminalPaths.Insert(0, windowsTerminalPath);
+ }
+ }
}
bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)
From 8b8fcd8c7b0871b49be4af85843782a4d5bf5c6a Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 16:16:58 +0800
Subject: [PATCH 49/51] Add IsAdmin
---
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index a00319c7991..30d96f1b232 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -47,6 +47,8 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDispo
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WindowsApps\wt.exe"),
};
+ private static readonly bool IsAdmin = IsAdministrator();
+
private Settings _settings;
public List Query(Query query)
@@ -518,5 +520,12 @@ public void Dispose()
{
Context.API.RemoveGlobalKeyboardCallback(API_GlobalKeyboardEvent);
}
+
+ private static bool IsAdministrator()
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
}
}
From 94f0bb961ef9b16b293dfe30befbc8f57f0a4237 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 16:21:00 +0800
Subject: [PATCH 50/51] Handle command running with administrator mode
correctly
---
.../Languages/en.xaml | 11 +-
Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 120 ++++++++++++++++--
2 files changed, 114 insertions(+), 17 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
index 645a0e14fd7..026f0b86fa0 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml
@@ -1,6 +1,7 @@
-
+
Replace Win+R
Close Command Prompt after pressing any key
@@ -16,4 +17,8 @@
Run As Administrator
Copy the command
Only show number of most used commands:
+ Windows Command Processor
+ Windows PowerShell
+ Microsoft PowerShell 7
+ Terminal
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index 30d96f1b232..b2b7ab702de 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -4,10 +4,12 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Security.Principal;
using System.Threading.Tasks;
+using System.Windows;
+using Flow.Launcher.Plugin.SharedCommands;
using WindowsInput;
using WindowsInput.Native;
-using Flow.Launcher.Plugin.SharedCommands;
using Control = System.Windows.Controls.Control;
using Keys = System.Windows.Forms.Keys;
@@ -335,21 +337,111 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin
private static Process StartProcess(ProcessStartInfo info)
{
- var absoluteFileName = info.FileName switch
- {
- "cmd.exe" => possibleCmdPaths.FirstOrDefault(File.Exists),
- "powershell.exe" => possiblePowershellPaths.FirstOrDefault(File.Exists),
- "pwsh.exe" => possiblePwshPaths.FirstOrDefault(File.Exists),
- "wt.exe" => possibleWTPowerShellPaths.FirstOrDefault(File.Exists),
- _ => info.FileName,
- };
- if (string.IsNullOrEmpty(absoluteFileName))
+ switch (info.FileName)
{
- Context.API.LogError(ClassName, $"The command '{info.FileName}' could not be found in the system PATH or the specified directories.");
- return null;
+ case "cmd.exe":
+ // Check if cmd.exe exists in the system PATH or the specified directories
+ var cmdPath = possibleCmdPaths.FirstOrDefault(File.Exists);
+ if (string.IsNullOrEmpty(cmdPath))
+ {
+ Context.API.LogError(ClassName, $"Cannot find cmd.exe");
+ // Fall back to using the Process.Start method directly
+ // Show user account control dialog if Flow is running as administrator
+ if (IsAdmin)
+ {
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd"), cmdPath, cmdPath);
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ return Process.Start(info);
+ }
+
+ // If Flow is running as administrator and the command is run as administrator, we need to use UAC dialog
+ if (IsAdmin && info.Verb == "runas")
+ {
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd"), cmdPath, cmdPath);
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ Context.API.StartProcess(cmdPath, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
+ return null;
+
+ case "powershell.exe":
+ var powershellPath = possiblePowershellPaths.FirstOrDefault(File.Exists);
+ if (string.IsNullOrEmpty(powershellPath))
+ {
+ Context.API.LogError(ClassName, $"Cannot find powershell.exe");
+ // Fall back to using the Process.Start method directly
+ // Show user account control dialog if Flow is running as administrator
+ if (IsAdmin)
+ {
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_powershell"), powershellPath, powershellPath);
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ return Process.Start(info);
+ }
+
+ // If Flow is running as administrator and the command is run as administrator, we need to use UAC dialog
+ if (IsAdmin && info.Verb == "runas")
+ {
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_powershell"), powershellPath, powershellPath);
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ Context.API.StartProcess(powershellPath, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
+ return null;
+
+ // Due to arguments pass issues, powershell 7 fails to work with deelevate model
+ // So we use shell to execuete them which means if Flow is running as administrator,
+ // all commands must be running as administrator
+ case "pwsh.exe":
+ // Fall back to using the Process.Start method directly
+ // Show user account control dialog if Flow is running as administrator
+ if (IsAdmin)
+ {
+ var pwshPath = possiblePwshPaths.FirstOrDefault(File.Exists);
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_pwsh"), pwshPath, pwshPath ?? "pwsh.exe");
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ return Process.Start(info);
+
+ // Due to arguments pass issues, windows terminal fails to work with deelevate model
+ // So we use shell to execuete them which means if Flow is running as administrator,
+ // all commands must be running as administrator
+ case "wt.exe":
+ // Fall back to using the Process.Start method directly
+ // Show user account control dialog if Flow is running as administrator
+ if (IsAdmin)
+ {
+ var wtPath = possibleWindowsTerminalPaths.FirstOrDefault(File.Exists);
+ var uacDialog = Context.API.ShowUACDialog(Context.API.GetTranslation("flowlauncher_plugin_cmd_wt"), wtPath, wtPath ?? "wt.exe");
+ if (uacDialog != MessageBoxResult.Yes)
+ {
+ return null;
+ }
+ }
+
+ return Process.Start(info);
}
- Context.API.StartProcess(absoluteFileName, info.WorkingDirectory, string.Join(" ", info.ArgumentList), info.Verb == "runas");
- return null;
+
+ return Process.Start(info);
}
private static void Execute(Func startProcess, ProcessStartInfo info)
From 6842fcaf448fa43c8592946682e5f6be19ce4bae Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 12 Jun 2025 16:30:54 +0800
Subject: [PATCH 51/51] Replace IntPtr.Zero with Handle or HWND
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 9336ce44c61..7c72d085746 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -282,7 +282,7 @@ public static unsafe bool IsForegroundWindowFullscreen()
{
var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
- if (hWndDesktop.Value != IntPtr.Zero)
+ if (hWndDesktop != HWND.Null)
{
return false;
}
@@ -873,7 +873,7 @@ public static unsafe bool RunAsDesktopUser(string app, string currentDir, string
// 4. Open that process
hShellProcess = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION, false, dwPID);
- if (hShellProcess == IntPtr.Zero)
+ if (hShellProcess == HANDLE.Null)
{
errorInfo = $"Can't open desktop shell process: {Marshal.GetLastWin32Error()}";
return false;