mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 10:16:24 +02:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Installer built, and every command works as expected, Now use sparse app deployment, so we don't need an extra msix --------- Co-authored-by: kaitao-ms <kaitao1105@gmail.com>
169 lines
5.5 KiB
C#
169 lines
5.5 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.Versioning;
|
|
using Microsoft.Win32;
|
|
|
|
namespace ManagedCommon
|
|
{
|
|
[SupportedOSPlatform("windows")]
|
|
public class PowerToysPathResolver
|
|
{
|
|
private const string PowerToysRegistryKey = @"Software\Classes\powertoys";
|
|
private const string PowerToysExe = "PowerToys.exe";
|
|
|
|
/// <summary>
|
|
/// Gets the PowerToys installation path by checking registry entries
|
|
/// </summary>
|
|
/// <returns>The path to PowerToys installation directory, or null if not found</returns>
|
|
public static string GetPowerToysInstallPath()
|
|
{
|
|
#if DEBUG
|
|
// In debug builds, resolve directly from the running process (no installer/registry involved).
|
|
return GetPathFromCurrentProcess();
|
|
#else
|
|
// Try to get path from Per-User installation first
|
|
string path = GetPathFromRegistry(RegistryHive.CurrentUser);
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
// Fall back to Per-Machine installation
|
|
path = GetPathFromRegistry(RegistryHive.LocalMachine);
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
private static string GetPathFromRegistry(RegistryHive hive)
|
|
{
|
|
try
|
|
{
|
|
using var baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64);
|
|
|
|
// First try to get path from the powertoys protocol registration
|
|
string path = GetPathFromProtocolRegistration(baseKey);
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Ignore registry access errors
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string GetPathFromProtocolRegistration(RegistryKey baseKey)
|
|
{
|
|
try
|
|
{
|
|
using var key = baseKey.OpenSubKey($@"{PowerToysRegistryKey}\shell\open\command");
|
|
|
|
if (key != null)
|
|
{
|
|
string command = key.GetValue(string.Empty)?.ToString();
|
|
if (!string.IsNullOrEmpty(command))
|
|
{
|
|
// Parse command like: "C:\Program Files\PowerToys\PowerToys.exe" "%1"
|
|
return ExtractPathFromCommand(command);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Ignore registry access errors
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string GetPathFromCurrentProcess()
|
|
{
|
|
try
|
|
{
|
|
// If we're running inside PowerToys.exe (dev/debug builds), use the executable location.
|
|
var processPath = Process.GetCurrentProcess().MainModule?.FileName;
|
|
if (!string.IsNullOrEmpty(processPath))
|
|
{
|
|
var processDir = Path.GetDirectoryName(processPath);
|
|
if (!string.IsNullOrEmpty(processDir) && File.Exists(Path.Combine(processDir, PowerToysExe)))
|
|
{
|
|
return processDir;
|
|
}
|
|
}
|
|
|
|
// As a fallback, walk up from AppContext.BaseDirectory to find PowerToys.exe.
|
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (directory != null)
|
|
{
|
|
var candidate = Path.Combine(directory.FullName, PowerToysExe);
|
|
if (File.Exists(candidate))
|
|
{
|
|
return directory.FullName;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore reflection/process permission errors; caller will see null and handle accordingly.
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ExtractPathFromCommand(string command)
|
|
{
|
|
if (string.IsNullOrEmpty(command))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Handle quoted paths: "C:\Program Files\PowerToys\PowerToys.exe" "%1"
|
|
if (command.StartsWith('\"'))
|
|
{
|
|
int endQuote = command.IndexOf('\"', 1);
|
|
if (endQuote > 1)
|
|
{
|
|
string exePath = command.Substring(1, endQuote - 1);
|
|
if (File.Exists(exePath))
|
|
{
|
|
return Path.GetDirectoryName(exePath);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle unquoted paths (less common)
|
|
string[] parts = command.Split(' ');
|
|
if (parts.Length > 0 && File.Exists(parts[0]))
|
|
{
|
|
return Path.GetDirectoryName(parts[0]);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Ignore path parsing errors
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|