This commit is contained in:
Jack Ye 2026-03-10 14:06:39 +08:00 committed by GitHub
commit 1df228510b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 840 additions and 131 deletions

View file

@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Flow.Launcher.Command</RootNamespace>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ApplicationIcon>app.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Output\Debug\Command\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>true</UseVSHostingProcess>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\Output\Release\Command\</OutputPath>
<DefineConstants>TRACE;RELEASE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Content Include="app.ico" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SolutionAssemblyInfo.cs" Link="Properties\SolutionAssemblyInfo.cs" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,132 @@
using System.Diagnostics;
namespace Flow.Launcher.Command;
internal static class Program
{
[STAThread]
private static int Main(string[] args)
{
if (args.Length == 0) return -1;
// Start process with arguments
// Usage: Flow.Launcher.Command -StartProcess -FileName <file> -WorkingDirectory <directory> -Arguments <args> -UseShellExecute <true|false> -Verb <verb> -CreateNoWindow <true|false>
if (args[0] == @"-StartProcess")
{
var fileName = string.Empty;
var workingDirectory = Environment.CurrentDirectory;
var argumentList = new List<string>();
var useShellExecute = true;
var verb = string.Empty;
var createNoWindow = false;
var isArguments = false;
for (int i = 1; i < args.Length; i++)
{
switch (args[i])
{
case "-FileName":
if (i + 1 < args.Length)
fileName = args[++i];
isArguments = false;
break;
case "-WorkingDirectory":
if (i + 1 < args.Length)
workingDirectory = args[++i];
isArguments = false;
break;
case "-Arguments":
if (i + 1 < args.Length)
argumentList.Add(args[++i]);
isArguments = true;
break;
case "-UseShellExecute":
if (i + 1 < args.Length && bool.TryParse(args[++i], out bool useShell))
useShellExecute = useShell;
isArguments = false;
break;
case "-Verb":
if (i + 1 < args.Length)
verb = args[++i];
isArguments = false;
break;
case "-CreateNoWindow":
if (i + 1 < args.Length && bool.TryParse(args[++i], out bool createNoWin))
createNoWindow = createNoWin;
break;
default:
if (isArguments)
argumentList.Add(args[i]);
else
Console.WriteLine($"Unknown parameter: {args[i]}");
break;
}
}
if (string.IsNullOrEmpty(fileName))
{
Console.WriteLine("Error: -FileName is required.");
return -2;
}
try
{
ProcessStartInfo info;
if (argumentList.Count == 0)
{
info = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = workingDirectory,
UseShellExecute = useShellExecute,
Verb = verb,
CreateNoWindow = createNoWindow
};
}
else if (argumentList.Count == 1)
{
info = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = workingDirectory,
Arguments = argumentList[0],
UseShellExecute = useShellExecute,
Verb = verb,
CreateNoWindow = createNoWindow
};
}
else
{
info = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = workingDirectory,
UseShellExecute = useShellExecute,
Verb = verb,
CreateNoWindow = createNoWindow
};
foreach (var arg in argumentList)
{
info.ArgumentList.Add(arg);
}
}
Process.Start(info)?.Dispose();
Console.WriteLine("Success.");
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return -3;
}
}
return -4;
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<PublishDir>..\Output\Release\</PublishDir>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishTrimmed>False</PublishTrimmed>
</PropertyGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -43,7 +43,7 @@ namespace Flow.Launcher.Core.Configuration
PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
PublicApi.Instance.RestartApp();
}
catch (Exception e)
{
@ -66,7 +66,7 @@ namespace Flow.Launcher.Core.Configuration
PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
PublicApi.Instance.RestartApp();
}
catch (Exception e)
{

View file

@ -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;
@ -91,7 +91,7 @@ namespace Flow.Launcher.Core
if (_api.ShowMsgBox(newVersionTips, Localize.update_flowlauncher_new_update(),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
_api.RestartApp();
}
}
catch (Exception e)

View file

@ -16,6 +16,7 @@ namespace Flow.Launcher.Infrastructure
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
public static readonly string ProgramDirectory = Directory.GetParent(Assembly.Location.NonNull()).ToString();
public static readonly string ExecutablePath = Path.Combine(ProgramDirectory, FlowLauncher + ".exe");
public static readonly string CommandExecutablePath = Path.Combine(ProgramDirectory, "Command", "Flow.Launcher.Command.exe");
public static readonly string ApplicationDirectory = Directory.GetParent(ProgramDirectory).ToString();
public static readonly string RootDirectory = Directory.GetParent(ApplicationDirectory).ToString();

View file

@ -226,7 +226,7 @@ namespace Flow.Launcher.Infrastructure.Http
Log.Debug(ClassName, $"Url <{url}>");
return await client.GetStringAsync(url, token);
}
catch (System.Exception e)
catch (System.Exception)
{
return string.Empty;
}

View file

@ -88,9 +88,25 @@ EVENT_SYSTEM_DIALOGEND
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
WM_POWERBROADCAST
PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMESUSPEND
PowerRegisterSuspendResumeNotification
PowerUnregisterSuspendResumeNotification
OpenProcessToken
GetCurrentProcess
LookupPrivilegeValue
SE_INCREASE_QUOTA_NAME
CloseHandle
TOKEN_PRIVILEGES
AdjustTokenPrivileges
GetShellWindow
GetWindowThreadProcessId
OpenProcess
GetProcessId
DuplicateTokenEx
CreateProcessWithTokenW
DeviceNotifyCallbackRoutine
MonitorFromWindow
MonitorFromWindow

View file

@ -483,6 +483,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool HideWhenDeactivated { get; set; } = true;
public bool ShowTaskbarWhenInvoked { get; set; } = false;
public bool AlwaysRunAsAdministrator { get; set; } = false;
private bool _showAtTopmost = false;
public bool ShowAtTopmost
{

View file

@ -6,6 +6,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@ -19,6 +20,7 @@ using Microsoft.Win32.SafeHandles;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.Security;
using Windows.Win32.System.Power;
using Windows.Win32.System.Threading;
using Windows.Win32.UI.Input.KeyboardAndMouse;
@ -690,6 +692,7 @@ namespace Flow.Launcher.Infrastructure
{
try
{
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start(new ProcessStartInfo("ms-settings:regionlanguage") { UseShellExecute = true });
}
catch (System.Exception)
@ -1017,6 +1020,164 @@ namespace Flow.Launcher.Infrastructure
#endregion
#region Administrator Mode
public static bool IsAdministrator()
{
using var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
/// <summary>
/// Inspired by <see href="https://github.com/jay/RunAsDesktopUser">
/// Document: <see href="https://learn.microsoft.com/en-us/archive/blogs/aaron_margosis/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app">
/// </summary>
public static unsafe bool RunAsDesktopUser(string app, string currentDir, string cmdLine, bool loadProfile, bool createNoWindow, out string errorInfo)
{
STARTUPINFOW si = new()
{
cb = (uint)Marshal.SizeOf<STARTUPINFOW>()
};
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();
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();
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 == HANDLE.Null)
{
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)
// Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line
// So we add one more dash before the command line to make command line work correctly
fixed (char* cmdLinePtr = $"- {cmdLine}")
fixed (char* currentDirPtr = currentDir)
{
if (!PInvoke.CreateProcessWithToken(
hPrimaryToken,
// If you need to access content in HKEY_CURRENT_USER, please set loadProfile to true
loadProfile ? CREATE_PROCESS_LOGON_FLAGS.LOGON_WITH_PROFILE : 0,
appPtr,
cmdLinePtr,
// If you do not want to create a window for console app, please set createNoWindow to true
createNoWindow ? PROCESS_CREATION_FLAGS.CREATE_NO_WINDOW : 0,
null,
currentDirPtr,
&si,
&pi))
{
errorInfo = $"CreateProcessWithTokenW failed: {Marshal.GetLastWin32Error()}";
goto cleanup;
}
}
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:
if (hShellProcessToken != HANDLE.Null) PInvoke.CloseHandle(hShellProcessToken);
if (hPrimaryToken != HANDLE.Null) PInvoke.CloseHandle(hPrimaryToken);
if (hShellProcess != HANDLE.Null) PInvoke.CloseHandle(hShellProcess);
return false;
}
#endregion
#region Taskbar
public static unsafe void ShowTaskbar()

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
@ -29,13 +30,21 @@ namespace Flow.Launcher.Plugin
void ChangeQuery(string query, bool requery = false);
/// <summary>
/// Restart Flow Launcher
/// Restart Flow Launcher without changing the user privileges.
/// </summary>
void RestartApp();
/// <summary>
/// Restart Flow Launcher as administrator.
/// </summary>
void RestartAppAsAdmin();
/// <summary>
/// Run a shell command
/// </summary>
/// <remarks>
/// It can help to start a de-elevated process and show user account control dialog when Flow is running as administrator.
/// </remarks>
/// <param name="cmd">The command or program to run</param>
/// <param name="filename">the shell type to run, e.g. powershell.exe</param>
/// <exception cref="FileNotFoundException">Thrown when unable to find the file specified in the command </exception>
@ -637,5 +646,35 @@ namespace Flow.Launcher.Plugin
/// </summary>
/// <returns></returns>
string GetLogDirectory();
/// <summary>
/// Start a process with support for handling administrative privileges
/// </summary>
/// <remarks>
/// It can help to start a de-elevated process and show user account control dialog when Flow is running as administrator.
/// </remarks>
/// <param name="fileName">File name</param>
/// <param name="workingDirectory">Working directory. If not specified, the current directory will be used</param>
/// <param name="arguments">Optional arguments to pass to the process. If not specified, no arguments will be passed</param>
/// <param name="useShellExecute">Whether to use shell to execute the process</param>
/// <param name="verb">Verb to use when starting the process, e.g. "runas" for elevated permissions. If not specified, no verb will be used.</param>
/// <param name="createNoWindow">Whether to create console window</param>
/// <returns>Whether process is started successfully</returns>
public bool StartProcess(string fileName, string workingDirectory = "", string arguments = "", bool useShellExecute = false, string verb = "", bool createNoWindow = false);
/// <summary>
/// Start a process with support for handling administrative privileges
/// </summary>
/// <remarks>
/// It can help to start a de-elevated process and show user account control dialog when Flow is running as administrator.
/// </remarks>
/// <param name="fileName">File name</param>
/// <param name="workingDirectory">Working directory. If not specified, the current directory will be used</param>
/// <param name="argumentList">Optional argument list to pass to the process. If not specified, no arguments will be passed</param>
/// <param name="useShellExecute">Whether to use shell to execute the process</param>
/// <param name="verb">Verb to use when starting the process, e.g. "runas" for elevated permissions. If not specified, no verb will be used.</param>
/// <param name="createNoWindow">Whether to create console window</param>
/// <returns>Whether process is started successfully</returns>
public bool StartProcess(string fileName, string workingDirectory = "", Collection<string> argumentList = null, bool useShellExecute = false, string verb = "", bool createNoWindow = false);
}
}

View file

@ -1,21 +1,23 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
# Visual Studio Version 18
VisualStudioVersion = 18.4.11519.219 insiders
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher", "Flow.Launcher\Flow.Launcher.csproj", "{DB90F671-D861-46BB-93A3-F1304F5BA1C5}"
ProjectSection(ProjectDependencies) = postProject
{0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {0B9DE348-9361-4940-ADB6-F5953BFFCCEC}
{4792A74A-0CEA-4173-A8B2-30E6764C6217} = {4792A74A-0CEA-4173-A8B2-30E6764C6217}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26}
{5043CECE-E6A7-4867-9CBE-02D27D83747A} = {5043CECE-E6A7-4867-9CBE-02D27D83747A}
{403B57F2-1856-4FC7-8A24-36AB346B763E} = {403B57F2-1856-4FC7-8A24-36AB346B763E}
{4792A74A-0CEA-4173-A8B2-30E6764C6217} = {4792A74A-0CEA-4173-A8B2-30E6764C6217}
{5043CECE-E6A7-4867-9CBE-02D27D83747A} = {5043CECE-E6A7-4867-9CBE-02D27D83747A}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {588088F4-3262-4F9F-9663-A05DE12534C3}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {9B130CC5-14FB-41FF-B310-0A95B6894C37}
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {A3DCCBCA-ACC1-421D-B16E-210896234C26}
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C} = {A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {F9C4C081-4CC3-4146-95F1-E102B4E10A5F}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
{FDED22C8-B637-42E8-824A-63B5B6E05A3A} = {FDED22C8-B637-42E8-824A-63B5B6E05A3A}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Test", "Flow.Launcher.Test\Flow.Launcher.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}"
@ -53,8 +55,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE = LICENSE
Scripts\post_build.ps1 = Scripts\post_build.ps1
README.md = README.md
SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs
Settings.XamlStyler = Settings.XamlStyler
SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}"
@ -71,6 +73,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Plugin
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.WindowsSettings", "Plugins\Flow.Launcher.Plugin.WindowsSettings\Flow.Launcher.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flow.Launcher.Command", "Flow.Launcher.Command\Flow.Launcher.Command.csproj", "{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -81,8 +85,19 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|Any CPU
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|Any CPU
{FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x86.ActiveCfg = Debug|Any CPU
@ -105,18 +120,6 @@ Global
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.Build.0 = Release|Any CPU
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.ActiveCfg = Release|Any CPU
{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.Build.0 = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU
{DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -286,6 +289,18 @@ Global
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.Build.0 = Release|Any CPU
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.ActiveCfg = Release|Any CPU
{5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.Build.0 = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|x64.ActiveCfg = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|x64.Build.0 = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|x86.ActiveCfg = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Debug|x86.Build.0 = Debug|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|Any CPU.Build.0 = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|x64.ActiveCfg = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|x64.Build.0 = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|x86.ActiveCfg = Release|Any CPU
{A9976C5C-B73A-4D29-B654-EF1C0C4C9C8C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -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;
@ -57,6 +59,13 @@ namespace Flow.Launcher
public App()
{
// Check if the application is running as administrator
if (_settings.AlwaysRunAsAdministrator && !Win32Helper.IsAdministrator())
{
RestartApp(true);
return;
}
// Do not use bitmap cache since it can cause WPF second window freezing issue
ShadowAssist.UseBitmapCache = false;
@ -293,12 +302,23 @@ namespace Flow.Launcher
{
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)
{
RestartApp(true);
}
}
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.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
@ -408,6 +428,59 @@ namespace Flow.Launcher
#endregion
#region Restart
/// <summary>
/// Restart the application without changing the user privileges.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
/// <param name="forceAdmin">
/// If true, the application will be restarted as administrator.
/// If false, it will be restarted with the same privileges as the current user.
/// </param>
/// <exception cref="Exception">Thrown when the Update.exe is not found in the expected location</exception>
public static void RestartApp(bool forceAdmin = false)
{
// 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.
var startInfo = new ProcessStartInfo
{
FileName = getUpdateExe(),
Arguments = $"--processStartAndWait \"{Constant.ExecutablePath}\"",
UseShellExecute = true,
Verb = Win32Helper.IsAdministrator() || forceAdmin ? "runas" : ""
};
// No need to de-elevate since we are restarting Flow Launcher which cannot bring security risks
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)

View file

@ -18,18 +18,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)
@ -42,7 +42,7 @@ public class AutoStartup
// Enable registry
if (!registryEnabled)
{
Enable(false);
Enable(false, alwaysRunAsAdministrator);
}
// Disable logon task
if (logonTaskEnabled)
@ -52,7 +52,7 @@ public class AutoStartup
}
}
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);
@ -60,22 +60,47 @@ public class AutoStartup
{
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();
var needsRecreation = !action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase)
|| task.Definition.Settings.Priority != ProcessPriorityClass.Normal;
if (needsRecreation)
var action = taskAction.ToString().Trim();
var pathCorrect = action.Equals(Constant.ExecutablePath, StringComparison.OrdinalIgnoreCase);
var runLevelCorrect = CheckRunLevel(task.Definition.Principal.RunLevel, alwaysRunAsAdministrator);
var priorityCorrect = task.Definition.Settings.Priority == ProcessPriorityClass.Normal;
if (Win32Helper.IsAdministrator())
{
UnscheduleLogonTask();
ScheduleLogonTask();
// If path, run level or priority is not correct, we need to unschedule and reschedule the task
if (!pathCorrect || !runLevelCorrect || !priorityCorrect)
{
UnscheduleLogonTask();
ScheduleLogonTask(alwaysRunAsAdministrator);
}
}
else
{
// 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)
{
throw new UnauthorizedAccessException("Cannot edit task run level because the app is not running as administrator.");
}
// If run level is correct and path or priority is not correct, we need to unschedule and reschedule the task
if (!pathCorrect || !priorityCorrect)
{
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,6 +111,11 @@ public class AutoStartup
return false;
}
private static bool CheckRunLevel(TaskRunLevel rl, bool alwaysRunAsAdministrator)
{
return alwaysRunAsAdministrator ? rl == TaskRunLevel.Highest : rl != TaskRunLevel.Highest;
}
private static bool CheckRegistry()
{
try
@ -120,16 +150,19 @@ public class AutoStartup
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)
@ -152,13 +185,13 @@ public class AutoStartup
}
}
private static void Enable(bool logonTask)
private static void Enable(bool logonTask, bool alwaysRunAsAdministrator)
{
try
{
if (logonTask)
{
ScheduleLogonTask();
ScheduleLogonTask(alwaysRunAsAdministrator);
}
else
{
@ -172,14 +205,15 @@ public class AutoStartup
}
}
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;
}
@ -216,13 +250,6 @@ public class AutoStartup
}
}
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);

View file

@ -70,6 +70,7 @@
<system:String x:Key="pluginStillInitializingSubtitle">Select this result to requery</system:String>
<system:String x:Key="pluginFailedToRespond">{0}: Failed to respond!</system:String>
<system:String x:Key="pluginFailedToRespondSubtitle">Select this result for more info</system:String>
<system:String x:Key="admin">(Admin)</system:String>
<!-- Setting General -->
<system:String x:Key="flowlauncher_settings">Settings</system:String>
@ -185,6 +186,10 @@
<system:String x:Key="showUnknownSourceWarningToolTip">Show warning when installing plugins from unknown sources</system:String>
<system:String x:Key="autoUpdatePlugins">Auto update plugins</system:String>
<system:String x:Key="autoUpdatePluginsToolTip">Automatically check plugin updates and notify if there are any updates available</system:String>
<system:String x:Key="alwaysRunAsAdministrator">Always run as administrator</system:String>
<system:String x:Key="alwaysRunAsAdministratorToolTip">Run Flow Launcher as administrator on 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? Otherwise, you will need to run as administrator manually on the next start.</system:String>
<!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String>

View file

@ -765,9 +765,13 @@ namespace Flow.Launcher
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
};

View file

@ -1,10 +1,12 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -73,7 +75,12 @@ namespace Flow.Launcher
_mainVM.ChangeQueryText(query, requery);
}
public void RestartApp()
public void RestartApp() => RestartApp(false);
public void RestartAppAsAdmin() => RestartApp(true);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
private async void RestartApp(bool runAsAdmin)
{
_mainVM.Hide();
@ -85,7 +92,7 @@ namespace Flow.Launcher
// 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);
App.RestartApp(runAsAdmin);
}
public void ShowMainWindow() => _mainVM.Show();
@ -151,8 +158,7 @@ namespace Flow.Launcher
{
var args = filename == "cmd.exe" ? $"/C {cmd}" : $"{cmd}";
var startInfo = ShellCommand.SetProcessStartInfo(filename, arguments: args, createNoWindow: true);
ShellCommand.Execute(startInfo);
StartProcess(filename, arguments: args, createNoWindow: true);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
@ -450,11 +456,7 @@ namespace Flow.Launcher
{
try
{
Process.Start(new ProcessStartInfo()
{
FileName = uri.AbsoluteUri,
UseShellExecute = true
})?.Dispose();
StartProcess(uri.AbsoluteUri, arguments: string.Empty, useShellExecute: true);
}
catch (Exception e)
{
@ -623,6 +625,85 @@ namespace Flow.Launcher
public string GetLogDirectory() => DataLocation.VersionLogDirectory;
public bool StartProcess(string fileName, string workingDirectory = "", string arguments = "", bool useShellExecute = false, string verb = "", bool createNoWindow = false)
{
try
{
workingDirectory = string.IsNullOrEmpty(workingDirectory) ? Environment.CurrentDirectory : workingDirectory;
// Use command executer to run the process as desktop user if running as admin
if (Win32Helper.IsAdministrator())
{
var result = Win32Helper.RunAsDesktopUser(
Constant.CommandExecutablePath,
Environment.CurrentDirectory,
$"-StartProcess " +
$"-FileName {AddDoubleQuotes(fileName)} " +
$"-WorkingDirectory {AddDoubleQuotes(workingDirectory)} " +
$"-Arguments {AddDoubleQuotes(arguments)} " +
$"-UseShellExecute {useShellExecute} " +
$"-Verb {AddDoubleQuotes(verb)} " +
$"-CreateNoWindow {createNoWindow}",
false,
true, // Do not show the command window
out var errorInfo);
if (!string.IsNullOrEmpty(errorInfo))
{
LogError(ClassName, $"Failed to start process {fileName} with arguments {arguments} under {workingDirectory}: {errorInfo}");
}
return result;
}
var info = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = workingDirectory,
Arguments = arguments,
UseShellExecute = useShellExecute,
Verb = verb,
CreateNoWindow = createNoWindow
};
Process.Start(info)?.Dispose();
return true;
}
catch (Exception e)
{
LogException(ClassName, $"Failed to start process {fileName} with arguments {arguments} under {workingDirectory}", e);
return false;
}
}
public bool StartProcess(string fileName, string workingDirectory = "", Collection<string> argumentList = null, bool useShellExecute = false, string verb = "", bool createNoWindow = false) =>
StartProcess(fileName, workingDirectory, JoinArgumentList(argumentList), useShellExecute, verb, createNoWindow);
private static string AddDoubleQuotes(string arg)
{
if (string.IsNullOrEmpty(arg))
return "\"\"";
// If already wrapped in double quotes, return as is
if (arg.Length >= 2 && arg[0] == '"' && arg[^1] == '"')
return arg;
return $"\"{arg}\"";
}
private static string JoinArgumentList(Collection<string> args)
{
if (args == null || args.Count == 0)
return string.Empty;
return string.Join(" ", args.Select(arg =>
{
if (string.IsNullOrEmpty(arg))
return "\"\"";
// Add double quotes
return AddDoubleQuotes(arg);
}));
}
#endregion
#region Private Methods

View file

@ -45,7 +45,7 @@ namespace Flow.Launcher.Resources.Pages
{
if (Settings.UseLogonTaskForStartup)
{
AutoStartup.ChangeToViaLogonTask();
AutoStartup.ChangeToViaLogonTask(Settings.AlwaysRunAsAdministrator);
}
else
{

View file

@ -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;
@ -43,6 +44,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
get => Settings.StartFlowLauncherOnSystemStartup;
set
{
if (Settings.StartFlowLauncherOnSystemStartup == value) return;
Settings.StartFlowLauncherOnSystemStartup = value;
try
@ -51,7 +54,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
if (UseLogonTaskForStartup)
{
AutoStartup.ChangeToViaLogonTask();
AutoStartup.ChangeToViaLogonTask(AlwaysRunAsAdministrator);
}
else
{
@ -67,6 +70,13 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
App.API.ShowMsgError(Localize.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();
}
}
}
@ -75,6 +85,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
get => Settings.UseLogonTaskForStartup;
set
{
if (UseLogonTaskForStartup == value) return;
Settings.UseLogonTaskForStartup = value;
if (StartFlowLauncherOnSystemStartup)
@ -83,7 +95,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
if (value)
{
AutoStartup.ChangeToViaLogonTask();
AutoStartup.ChangeToViaLogonTask(AlwaysRunAsAdministrator);
}
else
{
@ -94,10 +106,61 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
App.API.ShowMsgError(Localize.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
{
if (AlwaysRunAsAdministrator == value) return;
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()
{
// 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 && !Win32Helper.IsAdministrator())
{
if (App.API.ShowMsgBox(
App.API.GetTranslation("runAsAdministratorChangeAndRestart"),
App.API.GetTranslation("runAsAdministratorChange"),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// Restart the app as administrator
App.API.RestartAppAsAdmin();
}
}
}
public List<SearchWindowScreenData> SearchWindowScreens { get; } =
DropdownDataGeneric<SearchWindowScreens>.GetValues<SearchWindowScreenData>("SearchWindowScreen");

View file

@ -49,8 +49,8 @@
OnContent="{DynamicResource enable}" />
</ui:SettingsCard>
</ui:SettingsExpander.Items>
</ui:SettingsExpander>
<ui:SettingsCard
Margin="0 4 0 0"
Description="{DynamicResource hideOnStartupToolTip}"
@ -65,6 +65,20 @@
OnContent="{DynamicResource enable}" />
</ui:SettingsCard>
<ui:SettingsCard
Margin="0 4 0 0"
Description="{DynamicResource alwaysRunAsAdministratorToolTip}"
Header="{DynamicResource alwaysRunAsAdministrator}">
<ui:SettingsCard.HeaderIcon>
<ui:FontIcon Glyph="&#xE7EF;" />
</ui:SettingsCard.HeaderIcon>
<ui:ToggleSwitch
IsOn="{Binding AlwaysRunAsAdministrator}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</ui:SettingsCard>
<ui:SettingsCard Margin="0 12 0 0" Header="{DynamicResource hideFlowLauncherWhenLoseFocus}">
<ui:ToggleSwitch
IsOn="{Binding Settings.HideWhenDeactivated}"

View file

@ -344,11 +344,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
try
{
Process.Start(new ProcessStartInfo()
{
FileName = editorPath,
ArgumentList = { record.FullPath }
});
Context.API.StartProcess(editorPath, arguments: record.FullPath);
return true;
}
catch (Exception e)
@ -377,10 +373,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
try
{
Process.Start(new ProcessStartInfo()
{
FileName = shellPath, WorkingDirectory = record.FullPath
});
Context.API.StartProcess(shellPath, workingDirectory: record.FullPath, arguments: string.Empty);
return true;
}
catch (Exception e)
@ -444,6 +437,7 @@ namespace Flow.Launcher.Plugin.Explorer
Arguments = "srchadmin.dll"
};
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start(psi);
return true;
}
@ -468,6 +462,7 @@ namespace Flow.Launcher.Plugin.Explorer
SubTitle = Localize.plugin_explorer_openwith_subtitle(),
Action = _ =>
{
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");
return true;
},

View file

@ -57,7 +57,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
}
Settings.EverythingInstalledPath = installedPath;
Process.Start(installedPath, "-startup");
Main.Context.API.StartProcess(installedPath, arguments: "-startup");
return true;
}

View file

@ -506,6 +506,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
Arguments = Constants.WindowsIndexingOptions
};
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start(psi);
}

View file

@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
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
{
@ -455,7 +454,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
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 +498,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
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 +512,14 @@ namespace Flow.Launcher.Plugin.Program.Programs
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" : "" };
Main.StartProcess(Process.Start, info);
_ = Task.Run(() => Main.Context.API.StartProcess(
command,
arguments: string.Empty,
useShellExecute: true,
verb: elevated ? "runas" : ""));
}
internal static bool IfAppCanRunElevated(XmlNode appNode)

View file

@ -1,21 +1,21 @@
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.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 +196,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
// 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 +205,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
return result;
}
private void Launch(bool runAsAdmin = false)
{
_ = Task.Run(() => Main.Context.API.StartProcess(
FullPath,
workingDirectory: ParentDirectory,
arguments: string.Empty,
useShellExecute: true,
verb: runAsAdmin ? "runas" : ""));
}
public List<Result> ContextMenus(IPublicAPI api)
{
@ -240,15 +241,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
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;
},

View file

@ -81,7 +81,7 @@ namespace Flow.Launcher.Plugin.Shell
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
Execute(Process.Start, PrepareProcessStartInfo(m, runAsAdministrator));
Execute(StartProcess, PrepareProcessStartInfo(m, runAsAdministrator));
return true;
},
CopyText = m
@ -121,7 +121,7 @@ namespace Flow.Launcher.Plugin.Shell
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
Execute(Process.Start, PrepareProcessStartInfo(m.Key, runAsAdministrator));
Execute(StartProcess, PrepareProcessStartInfo(m.Key, runAsAdministrator));
return true;
},
CopyText = m.Key
@ -151,7 +151,7 @@ namespace Flow.Launcher.Plugin.Shell
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
Execute(Process.Start, PrepareProcessStartInfo(cmd, runAsAdministrator));
Execute(StartProcess, PrepareProcessStartInfo(cmd, runAsAdministrator));
return true;
},
CopyText = cmd
@ -176,7 +176,7 @@ namespace Flow.Launcher.Plugin.Shell
!c.SpecialKeyState.AltPressed &&
!c.SpecialKeyState.WinPressed;
Execute(Process.Start, PrepareProcessStartInfo(m.Key, runAsAdministrator));
Execute(StartProcess, PrepareProcessStartInfo(m.Key, runAsAdministrator));
return true;
},
CopyText = m.Key
@ -328,6 +328,17 @@ namespace Flow.Launcher.Plugin.Shell
return info;
}
private static Process StartProcess(ProcessStartInfo info)
{
Context.API.StartProcess(
info.FileName,
workingDirectory: info.WorkingDirectory,
argumentList: info.ArgumentList,
useShellExecute: info.UseShellExecute,
verb: info.Verb);
return null;
}
private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
{
try
@ -464,7 +475,7 @@ namespace Flow.Launcher.Plugin.Shell
Title = Localize.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",

View file

@ -219,6 +219,7 @@ namespace Flow.Launcher.Plugin.Sys
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_SHUTDOWN | EXIT_WINDOWS_FLAGS.EWX_POWEROFF, REASON);
else
// No need to de-elevate since we already have message box asking for confirmation
Process.Start("shutdown", "/s /t 0");
}
return true;
@ -244,6 +245,7 @@ namespace Flow.Launcher.Plugin.Sys
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT, REASON);
else
// No need to de-elevate since we already have message box asking for confirmation
Process.Start("shutdown", "/r /t 0");
}
return true;
@ -271,6 +273,7 @@ namespace Flow.Launcher.Plugin.Sys
if (EnableShutdownPrivilege())
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_REBOOT | EXIT_WINDOWS_FLAGS.EWX_BOOTOPTIONS, REASON);
else
// No need to de-elevate since we already have message box asking for confirmation
Process.Start("shutdown", "/r /o /t 0");
}
return true;
@ -335,6 +338,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe773"),
Action = c =>
{
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start("control.exe", "srchadmin.dll");
return true;
}
@ -368,6 +372,7 @@ namespace Flow.Launcher.Plugin.Sys
CopyText = recycleBinFolder,
Action = c =>
{
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start("explorer", recycleBinFolder);
return true;
}

View file

@ -198,6 +198,7 @@ namespace Flow.Launcher.Plugin.WindowsSettings.Helper
try
{
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start(processStartInfo);
return true;
}
@ -207,6 +208,7 @@ namespace Flow.Launcher.Plugin.WindowsSettings.Helper
{
processStartInfo.UseShellExecute = true;
processStartInfo.Verb = "runas";
// No need to de-elevate since we are opening windows settings which cannot bring security risks
Process.Start(processStartInfo);
return true;
}