diff --git a/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs b/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs index 41a3ab6a2c..a7c4171f49 100644 --- a/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs +++ b/src/modules/Projects/ProjectsEditor/Data/ProjectData.cs @@ -33,7 +33,7 @@ namespace ProjectsEditor.Data public string CommandLineArguments { get; set; } - public bool LaunchesAsAdmin { get; set; } + public bool IsElevated { get; set; } public bool Minimized { get; set; } diff --git a/src/modules/Projects/ProjectsEditor/Models/Application.cs b/src/modules/Projects/ProjectsEditor/Models/Application.cs index 84dad01d21..1143b38d04 100644 --- a/src/modules/Projects/ProjectsEditor/Models/Application.cs +++ b/src/modules/Projects/ProjectsEditor/Models/Application.cs @@ -47,14 +47,14 @@ namespace ProjectsEditor.Models public string CommandLineArguments { get; set; } - private bool _launchesAsAdmin; + private bool _isElevated; - public bool LaunchesAsAdmin + public bool IsElevated { - get => _launchesAsAdmin; + get => _isElevated; set { - _launchesAsAdmin = value; + _isElevated = value; OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams))); } } @@ -109,7 +109,7 @@ namespace ProjectsEditor.Models { get { - _appMainParams = _launchesAsAdmin ? Properties.Resources.Admin : string.Empty; + _appMainParams = _isElevated ? Properties.Resources.Admin : string.Empty; if (!string.IsNullOrWhiteSpace(CommandLineArguments)) { _appMainParams += (_appMainParams == string.Empty ? string.Empty : " | ") + Properties.Resources.Args + ": " + CommandLineArguments; diff --git a/src/modules/Projects/ProjectsEditor/Models/Project.cs b/src/modules/Projects/ProjectsEditor/Models/Project.cs index 4effabbacd..3cc0c9edc5 100644 --- a/src/modules/Projects/ProjectsEditor/Models/Project.cs +++ b/src/modules/Projects/ProjectsEditor/Models/Project.cs @@ -231,7 +231,7 @@ namespace ProjectsEditor.Models AppTitle = item.AppTitle, CommandLineArguments = item.CommandLineArguments, PackageFullName = item.PackageFullName, - LaunchesAsAdmin = item.LaunchesAsAdmin, + IsElevated = item.IsElevated, Minimized = item.Minimized, Maximized = item.Maximized, MonitorNumber = item.MonitorNumber, diff --git a/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml b/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml index 9eabe75d4d..2f0a9e858a 100644 --- a/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml +++ b/src/modules/Projects/ProjectsEditor/ProjectEditorPage.xaml @@ -160,11 +160,10 @@ Margin="100 5 0 0"> public partial class ProjectEditor : Page { - private const double ScrollSpeed = 20; + private const double ScrollSpeed = 15; private MainViewModel _mainViewModel; public ProjectEditor(MainViewModel mainViewModel) diff --git a/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs b/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs index 12a72dd46c..77b98daa9c 100644 --- a/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs +++ b/src/modules/Projects/ProjectsEditor/Utils/ProjectsEditorIO.cs @@ -94,7 +94,7 @@ namespace ProjectsEditor.Utils PackageFullName = app.PackageFullName, Parent = newProject, CommandLineArguments = app.CommandLineArguments, - LaunchesAsAdmin = app.LaunchesAsAdmin, + IsElevated = app.IsElevated, Maximized = app.Maximized, Minimized = app.Minimized, IsNotFound = false, @@ -149,7 +149,7 @@ namespace ProjectsEditor.Utils Title = app.AppTitle, PackageFullName = app.PackageFullName, CommandLineArguments = app.CommandLineArguments, - LaunchesAsAdmin = app.LaunchesAsAdmin, + IsElevated = app.IsElevated, Maximized = app.Maximized, Minimized = app.Minimized, Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper diff --git a/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp b/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp index da1238b0ad..2746a78d06 100644 --- a/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp +++ b/src/modules/Projects/ProjectsLauncher/AppLauncher.cpp @@ -134,53 +134,25 @@ namespace FancyZones } } -bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs) +bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated) { - STARTUPINFO startupInfo; - ZeroMemory(&startupInfo, sizeof(startupInfo)); - startupInfo.cb = sizeof(startupInfo); + SHELLEXECUTEINFO sei = { 0 }; + sei.cbSize = sizeof(SHELLEXECUTEINFO); + sei.hwnd = nullptr; + sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE; + sei.lpVerb = elevated ? L"runas" : L"open"; + sei.lpFile = appPath.c_str(); + sei.lpParameters = commandLineArgs.c_str(); + sei.lpDirectory = nullptr; + sei.nShow = SW_SHOWNORMAL; - PROCESS_INFORMATION processInfo; - ZeroMemory(&processInfo, sizeof(processInfo)); - - std::wstring fullCommandLine = L"\"" + appPath + L"\" " + commandLineArgs; - if (CreateProcess(nullptr, fullCommandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo)) - { - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - return true; - } - else + if (!ShellExecuteEx(&sei)) { Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError())); + return false; } - return false; -} - -bool LaunchUsingUriProtocolName(const std::wstring& uriProtocolName, const std::wstring& commandLineArgs) -{ - std::wstring command = std::wstring(uriProtocolName + (commandLineArgs.starts_with(L":") ? L"" : L":") + commandLineArgs); - HINSTANCE result = ShellExecute( - nullptr, - L"open", - command.c_str(), - nullptr, - nullptr, - SW_SHOWNORMAL - ); - - // If the function succeeds, it returns a value greater than 32. - if (result > reinterpret_cast(32)) - { - return true; - } - else - { - Logger::error(L"Failed to launch process. {}", get_last_error_or_default(GetLastError())); - return false; - } + return true; } bool LaunchPackagedApp(const std::wstring& packageFullName) @@ -219,24 +191,29 @@ bool LaunchPackagedApp(const std::wstring& packageFullName) bool Launch(const Project::Application& app) { bool launched { false }; - if (!app.packageFullName.empty() && app.commandLineArgs.empty()) + if (!app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated) { - Logger::trace(L"Launching packaged without command line args {}", app.name); + Logger::trace(L"Launching packaged app {}", app.name); launched = LaunchPackagedApp(app.packageFullName); } - else if (!app.packageFullName.empty() && !app.commandLineArgs.empty()) + + if (!launched && !app.packageFullName.empty()) { auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName); if (!names.empty()) { Logger::trace(L"Launching packaged by protocol with command line args {}", app.name); - launched = LaunchUsingUriProtocolName(names[0], app.commandLineArgs); - } + + std::wstring uriProtocolName = names[0]; + std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs); + + launched = LaunchApp(command, L"", app.isElevated); + } else { Logger::info(L"Uri protocol names not found for {}", app.packageFullName); } - } + } if (!launched) { @@ -249,7 +226,7 @@ bool Launch(const Project::Application& app) return false; } - launched = LaunchApp(app.path, app.commandLineArgs); + launched = LaunchApp(app.path, app.commandLineArgs, app.isElevated); } Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path); diff --git a/src/modules/Projects/ProjectsLauncher/main.cpp b/src/modules/Projects/ProjectsLauncher/main.cpp index 570f987ff1..fb3375640e 100644 --- a/src/modules/Projects/ProjectsLauncher/main.cpp +++ b/src/modules/Projects/ProjectsLauncher/main.cpp @@ -24,6 +24,13 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm return 0; } + // COM should be initialized before ShellExecuteEx is called. + if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) + { + Logger::error("CoInitializeEx failed"); + return 1; + } + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // read projects @@ -91,5 +98,6 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm } json::to_file(JsonUtils::ProjectsFile(), JsonUtils::ProjectsListJSON::ToJson(projects)); + CoUninitialize(); return 0; } diff --git a/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp index 1e998c3008..5165f23bd5 100644 --- a/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp +++ b/src/modules/Projects/ProjectsSnapshotTool/SnapshotUtils.cpp @@ -147,6 +147,32 @@ namespace SnapshotUtils return commandLineArgs; } + bool IsProcessElevated(DWORD processID) + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processID); + if (!hProcess) + { + return false; + } + + HANDLE hToken = NULL; + if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) + { + return false; + } + + TOKEN_ELEVATION elevation; + DWORD cbSize = sizeof(TOKEN_ELEVATION); + if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &cbSize)) + { + CloseHandle(hToken); + return false; + } + + CloseHandle(hToken); + return elevation.TokenIsElevated; + } + std::vector GetApps(const std::function getMonitorNumberFromWindowHandle) { std::vector apps{}; @@ -219,6 +245,7 @@ namespace SnapshotUtils .path = processPath, .packageFullName = data.value().packageFullName, .commandLineArgs = L"", // GetCommandLineArgs(pid, wbemHelper), + .isElevated = IsProcessElevated(pid), .isMinimized = WindowUtils::IsMinimized(window), .isMaximized = WindowUtils::IsMaximized(window), .position = Project::Application::Position{ diff --git a/src/modules/Projects/projects-common/Data.h b/src/modules/Projects/projects-common/Data.h index 04f08f4c20..108db56bab 100644 --- a/src/modules/Projects/projects-common/Data.h +++ b/src/modules/Projects/projects-common/Data.h @@ -29,6 +29,7 @@ struct Project std::wstring path; std::wstring packageFullName; std::wstring commandLineArgs; + bool isElevated{}; bool isMinimized{}; bool isMaximized{}; Position position{}; @@ -137,6 +138,7 @@ namespace JsonUtils const static wchar_t* AppPackageFullNameID = L"package-full-name"; const static wchar_t* AppTitleID = L"title"; const static wchar_t* CommandLineArgsID = L"command-line-arguments"; + const static wchar_t* ElevatedID = L"is-elevated"; const static wchar_t* MinimizedID = L"minimized"; const static wchar_t* MaximizedID = L"maximized"; const static wchar_t* PositionID = L"position"; @@ -151,6 +153,7 @@ namespace JsonUtils json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title)); json.SetNamedValue(NonLocalizable::AppPackageFullNameID, json::value(data.packageFullName)); json.SetNamedValue(NonLocalizable::CommandLineArgsID, json::value(data.commandLineArgs)); + json.SetNamedValue(NonLocalizable::ElevatedID, json::value(data.isElevated)); json.SetNamedValue(NonLocalizable::MinimizedID, json::value(data.isMinimized)); json.SetNamedValue(NonLocalizable::MaximizedID, json::value(data.isMaximized)); json.SetNamedValue(NonLocalizable::PositionID, PositionJSON::ToJson(data.position)); @@ -177,6 +180,12 @@ namespace JsonUtils } result.commandLineArgs = json.GetNamedString(NonLocalizable::CommandLineArgsID); + + if (json.HasKey(NonLocalizable::ElevatedID)) + { + result.isElevated = json.GetNamedBoolean(NonLocalizable::ElevatedID); + } + result.isMaximized = json.GetNamedBoolean(NonLocalizable::MaximizedID); result.isMinimized = json.GetNamedBoolean(NonLocalizable::MinimizedID); result.monitor = static_cast(json.GetNamedNumber(NonLocalizable::MonitorID));