Skip to content

Support Always Run As Administrator #3573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7549bba
Add administrator mode check
Jack251970 May 22, 2025
369ed86
Add administrator text in tray icon
Jack251970 May 22, 2025
10d379f
Support running application under non-admin mode & Show UAC dialogs w…
Jack251970 May 22, 2025
484f91c
Improve UAC dialog
Jack251970 May 22, 2025
7611b00
Add new restart api
Jack251970 May 22, 2025
d29a5d4
Add setting model
Jack251970 May 22, 2025
40cf413
Add admin support for auto startup
Jack251970 May 22, 2025
fe70a72
Add admin mode configuration in general page
Jack251970 May 22, 2025
0edd626
Remove useless semicolon
Jack251970 May 22, 2025
91f7a4f
Remove useless semicolon
Jack251970 May 22, 2025
902c8f4
Check value changed
Jack251970 May 22, 2025
c5c24ac
Check run level
Jack251970 May 22, 2025
1073061
Remove old before creating new one
Jack251970 May 22, 2025
e9c40e4
Revert "Add new restart api"
Jack251970 May 23, 2025
365ba6a
Code quality
Jack251970 May 23, 2025
eae4a95
Use readonly bool variable
Jack251970 May 23, 2025
52c36ff
Only restart from non-admin to admin & Fix restart as administrator i…
Jack251970 May 23, 2025
35f4fd5
Fix restart as administrator issue
Jack251970 May 23, 2025
eacccf9
Add code comments & Use local function
Jack251970 May 23, 2025
d6462f4
Move restart function to app class
Jack251970 May 23, 2025
479b49d
Throw UnauthorizedAccessException when encountering admin issue
Jack251970 May 23, 2025
c3ec002
Show message box to ask users to restart as administrator when encoun…
Jack251970 May 23, 2025
80c0288
Throw exception when editing highest run level task
Jack251970 May 23, 2025
1b80290
Do not show noticification when run level is correct
Jack251970 May 23, 2025
ca6f077
Merge branch 'dev' into administrator_mode
Jack251970 Jun 3, 2025
ba1ada7
Continue to restart app as administrator if app is run as administrat…
Jack251970 Jun 3, 2025
b2edea4
Improve code quality
Jack251970 Jun 3, 2025
de26abe
Use api functions instead
Jack251970 Jun 3, 2025
23f2489
Force admin restart and fix build issue
Jack251970 Jun 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
12 changes: 12 additions & 0 deletions Flow.Launcher.Infrastructure/Win32Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,7 +39,7 @@
{
var cloaked = cloak ? 1 : 0;

return PInvoke.DwmSetWindowAttribute(

Check warning on line 42 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Dwm` is not a recognized word. (unrecognized-spelling)
GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_CLOAK,
&cloaked,
Expand All @@ -50,12 +51,12 @@
var backdropType = backdrop switch
{
BackdropTypes.Acrylic => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW,
BackdropTypes.Mica => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW,

Check warning on line 54 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`SYSTEMBACKDROP` is not a recognized word. (unrecognized-spelling)

Check warning on line 54 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`DWMSBT` is not a recognized word. (unrecognized-spelling)
BackdropTypes.MicaAlt => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TABBEDWINDOW,

Check warning on line 55 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`SYSTEMBACKDROP` is not a recognized word. (unrecognized-spelling)

Check warning on line 55 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`DWMSBT` is not a recognized word. (unrecognized-spelling)
_ => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_AUTO

Check warning on line 56 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`SYSTEMBACKDROP` is not a recognized word. (unrecognized-spelling)

Check warning on line 56 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`DWMSBT` is not a recognized word. (unrecognized-spelling)
};

return PInvoke.DwmSetWindowAttribute(

Check warning on line 59 in Flow.Launcher.Infrastructure/Win32Helper.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`Dwm` is not a recognized word. (unrecognized-spelling)
GetWindowHandle(window),
DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
&backdropType,
Expand Down Expand Up @@ -753,5 +754,16 @@
}

#endregion

#region Administrator Mode

public static bool IsAdministrator()
{
using var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}

#endregion
}
}
58 changes: 55 additions & 3 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -236,12 +238,23 @@ private void AutoStartup()
{
try
{
Helper.AutoStartup.CheckIsEnabled(_settings.UseLogonTaskForStartup);
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);
}
Expand Down Expand Up @@ -319,6 +332,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)
Expand Down
77 changes: 53 additions & 24 deletions Flow.Launcher/Helper/AutoStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ public class AutoStartup
private const string LogonTaskName = $"{Constant.FlowLauncher} Startup";
private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup";

public static void CheckIsEnabled(bool useLogonTaskForStartup)
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,
// 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)
Expand All @@ -41,7 +43,7 @@ public static void CheckIsEnabled(bool useLogonTaskForStartup)
// Enable registry
if (!registryEnabled)
{
Enable(false);
Enable(false, alwaysRunAsAdministrator);
}
// Disable logon task
if (logonTaskEnabled)
Expand All @@ -51,28 +53,53 @@ 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);
if (task != null)
{
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))
var pathCorrect = action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase);
var runLevelCorrect = CheckRunLevel(task.Definition.Principal.RunLevel, alwaysRunAsAdministrator);

if (_isAdministrator)
{
UnscheduleLogonTask();
ScheduleLogonTask();
// If path or run level is not correct, we need to unschedule and reschedule the task
if (!pathCorrect || !runLevelCorrect)
{
UnscheduleLogonTask();
ScheduleLogonTask(alwaysRunAsAdministrator);
}
}
else
{
// 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}");
Expand All @@ -83,6 +110,11 @@ private static bool CheckLogonTask()
return false;
}

private static bool CheckRunLevel(TaskRunLevel rl, bool alwaysRunAsAdministrator)
{
return alwaysRunAsAdministrator ? rl == TaskRunLevel.Highest : rl != TaskRunLevel.Highest;
}

private static bool CheckRegistry()
{
try
Expand Down Expand Up @@ -117,16 +149,19 @@ public static void DisableViaLogonTaskAndRegistry()
Disable(false);
}

public static void ChangeToViaLogonTask()
public static void ChangeToViaLogonTask(bool alwaysRunAsAdministrator)
{
Disable(false);
Enable(true);
Disable(true); // Remove old logon task so that we can create a new one
Enable(true, alwaysRunAsAdministrator);
}

public static void ChangeToViaRegistry()
{
Disable(true);
Enable(false);
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);
}

private static void Disable(bool logonTask)
Expand All @@ -149,13 +184,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
{
Expand All @@ -169,14 +204,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 (_isAdministrator && alwaysRunAsAdministrator)
{
td.Principal.RunLevel = TaskRunLevel.Highest;
}
Expand Down Expand Up @@ -212,13 +248,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);
Expand Down
5 changes: 5 additions & 0 deletions Flow.Launcher/Languages/en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<system:String x:Key="PositionReset">Position Reset</system:String>
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String>
<system:String x:Key="queryTextBoxPlaceholder">Type here to search</system:String>
<system:String x:Key="admin">(Admin)</system:String>

<!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Settings</system:String>
Expand Down Expand Up @@ -131,6 +132,10 @@
<system:String x:Key="historyResultsForHomePage">Show History Results in Home Page</system:String>
<system:String x:Key="historyResultsCountForHomePage">Maximum History Results Shown in Home Page</system:String>
<system:String x:Key="homeToggleBoxToolTip">This can only be edited if plugin supports Home feature and Home Page is enabled.</system:String>
<system:String x:Key="alwaysRunAsAdministrator">Always run as administrator</system:String>
<system:String x:Key="alwaysRunAsAdministratorToolTip">Run Flow Launcher as administrator on system startup</system:String>
<system:String x:Key="runAsAdministratorChange">Administrator Mode Change</system:String>
<system:String x:Key="runAsAdministratorChangeAndRestart">Do you want to restart as administrator to apply this change? Or you need to run as administrator during next start manually.</system:String>

<!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String>
Expand Down
6 changes: 5 additions & 1 deletion Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
1 change: 0 additions & 1 deletion Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void ChangeAutoStartup(bool value)
{
if (Settings.UseLogonTaskForStartup)
{
AutoStartup.ChangeToViaLogonTask();
AutoStartup.ChangeToViaLogonTask(Settings.AlwaysRunAsAdministrator);
}
else
{
Expand Down
Loading
Loading