From 1e25d179202c5df03e71c236fa58d4f08bf3f6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Mon, 9 Feb 2026 21:00:01 +0100 Subject: [PATCH] CmdPal: ApplicationInfoService + fix version in the error report builder (#45374) ## Summary of the Pull Request This PR introduces a new service, ApplicationInfoService, that encapsulates host app state and infrastructure and moves the logic for obtaining version information there. It then uses this information to fix an issue with incorrect version reporting for unpackaged CmdPal in ErrorReportBuilder. - Adds ApplicationInfoService. - Fixes an error in ErrorReportBuilder when the app runs unpackaged. - Adds logging of the app version and environmental info at startup. ## PR Checklist - [x] Closes: #45368 - [x] Closes: #45370 - [ ] **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 ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../AppPackagingFlavor.cs | 26 ++++ .../Helpers/VersionHelper.cs | 87 ++++++++++++ .../Services/ApplicationInfoService.cs | 125 ++++++++++++++++++ .../Services/IApplicationInfoService.cs | 49 +++++++ .../Services/Reports/ErrorReportBuilder.cs | 55 +++----- .../cmdpal/Microsoft.CmdPal.UI/App.xaml.cs | 17 ++- .../Helpers/GlobalErrorHandler.cs | 10 +- .../cmdpal/Microsoft.CmdPal.UI/Program.cs | 20 +++ .../Settings/GeneralPage.xaml.cs | 55 +------- .../Settings/InternalPage.xaml.cs | 11 +- 10 files changed, 357 insertions(+), 98 deletions(-) create mode 100644 src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs create mode 100644 src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs create mode 100644 src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs create mode 100644 src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs new file mode 100644 index 0000000000..2d23db30b3 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs @@ -0,0 +1,26 @@ +// 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. + +namespace Microsoft.CmdPal.Core.Common; + +/// +/// Represents the packaging flavor of the application. +/// +public enum AppPackagingFlavor +{ + /// + /// Application is packaged as a Windows MSIX package. + /// + Packaged, + + /// + /// Application is running unpackaged (native executable). + /// + Unpackaged, + + /// + /// Application is running as unpackaged portable (self-contained distribution). + /// + UnpackagedPortable, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs new file mode 100644 index 0000000000..ad280b6c52 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs @@ -0,0 +1,87 @@ +// 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.Diagnostics; +using Windows.ApplicationModel; + +namespace Microsoft.CmdPal.Core.Common.Helpers; + +/// +/// Helper class for retrieving application version information safely. +/// +internal static class VersionHelper +{ + /// + /// Gets the application version as a string in the format "Major.Minor.Build.Revision". + /// Falls back to assembly version if packaged version is unavailable, and returns a default value if both fail. + /// + /// The application version string, or a fallback value if retrieval fails. + public static string GetAppVersionSafe() + { + if (TryGetPackagedVersion(out var version)) + { + return version; + } + + if (TryGetAssemblyVersion(out version)) + { + return version; + } + + return "?"; + } + + /// + /// Attempts to retrieve the application version from the package manifest. + /// + /// The version string if successful, or an empty string if unsuccessful. + /// True if the version was retrieved successfully; otherwise, false. + private static bool TryGetPackagedVersion(out string version) + { + version = string.Empty; + try + { + // Package.Current throws InvalidOperationException if the app is not packaged + var v = Package.Current.Id.Version; + version = $"{v.Major}.{v.Minor}.{v.Build}.{v.Revision}"; + return true; + } + catch (InvalidOperationException) + { + return false; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to get version from the package", ex); + return false; + } + } + + /// + /// Attempts to retrieve the application version from the executable file. + /// + /// The version string if successful, or an empty string if unsuccessful. + /// True if the version was retrieved successfully; otherwise, false. + private static bool TryGetAssemblyVersion(out string version) + { + version = string.Empty; + try + { + var processPath = Environment.ProcessPath; + if (string.IsNullOrEmpty(processPath)) + { + return false; + } + + var info = FileVersionInfo.GetVersionInfo(processPath); + version = $"{info.FileMajorPart}.{info.FileMinorPart}.{info.FileBuildPart}.{info.FilePrivatePart}"; + return true; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to get version from the executable", ex); + return false; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs new file mode 100644 index 0000000000..0919c38946 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs @@ -0,0 +1,125 @@ +// 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.Globalization; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.ApplicationModel; + +namespace Microsoft.CmdPal.Core.Common.Services; + +/// +/// Implementation of IApplicationInfoService providing application-wide information. +/// +public sealed class ApplicationInfoService : IApplicationInfoService +{ + private readonly Lazy _configDirectory = new(() => Utilities.BaseSettingsPath("Microsoft.CmdPal")); + private readonly Lazy _isElevated; + private readonly Lazy _logDirectory; + private readonly Lazy _packagingFlavor; + private Func? _getLogDirectory; + + /// + /// Initializes a new instance of the class. + /// The log directory delegate can be set later via . + /// + public ApplicationInfoService() + { + _packagingFlavor = new Lazy(DeterminePackagingFlavor); + _isElevated = new Lazy(DetermineElevationStatus); + _logDirectory = new Lazy(() => _getLogDirectory?.Invoke() ?? "Not available"); + } + + /// + /// Initializes a new instance of the class with an optional log directory provider. + /// + /// Optional delegate to retrieve the log directory path. If not provided, the log directory will be unavailable. + public ApplicationInfoService(Func? getLogDirectory) + : this() + { + _getLogDirectory = getLogDirectory; + } + + /// + /// Sets the log directory delegate to be used for retrieving the log directory path. + /// This allows deferred initialization of the logger path. + /// + /// Delegate to retrieve the log directory path. + public void SetLogDirectory(Func getLogDirectory) + { + ArgumentNullException.ThrowIfNull(getLogDirectory); + _getLogDirectory = getLogDirectory; + } + + public string AppVersion => VersionHelper.GetAppVersionSafe(); + + public AppPackagingFlavor PackagingFlavor => _packagingFlavor.Value; + + public string LogDirectory => _logDirectory.Value; + + public string ConfigDirectory => _configDirectory.Value; + + public bool IsElevated => _isElevated.Value; + + public string GetApplicationInfoSummary() + { + return $""" + Application: + App version: {AppVersion} + Packaging flavor: {PackagingFlavor} + Is elevated: {(IsElevated ? "yes" : "no")} + + Environment: + OS version: {RuntimeInformation.OSDescription} + OS architecture: {RuntimeInformation.OSArchitecture} + Runtime identifier: {RuntimeInformation.RuntimeIdentifier} + Framework: {RuntimeInformation.FrameworkDescription} + Process architecture: {RuntimeInformation.ProcessArchitecture} + Culture: {CultureInfo.CurrentCulture.Name} + UI culture: {CultureInfo.CurrentUICulture.Name} + + Paths: + Log directory: {LogDirectory} + Config directory: {ConfigDirectory} + """; + } + + private static AppPackagingFlavor DeterminePackagingFlavor() + { + // Try to determine if running as packaged + try + { + // If this doesn't throw, we're packaged + _ = Package.Current.Id.Version; + return AppPackagingFlavor.Packaged; + } + catch (InvalidOperationException) + { + // Not packaged, check if portable + // For now, we don't support portable yet, so return Unpackaged + // In the future, check for a marker file or environment variable + return AppPackagingFlavor.Unpackaged; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to determine packaging flavor", ex); + return AppPackagingFlavor.Unpackaged; + } + } + + private static bool DetermineElevationStatus() + { + try + { + var isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + return isElevated; + } + catch (Exception) + { + return false; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs new file mode 100644 index 0000000000..f687333d20 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs @@ -0,0 +1,49 @@ +// 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. + +namespace Microsoft.CmdPal.Core.Common.Services; + +/// +/// Provides access to application-wide information such as version, packaging flavor, and directory paths. +/// +public interface IApplicationInfoService +{ + /// + /// Gets the application version as a string in the format "Major.Minor.Build.Revision". + /// + string AppVersion { get; } + + /// + /// Gets the packaging flavor of the application. + /// + AppPackagingFlavor PackagingFlavor { get; } + + /// + /// Gets the directory path where application logs are stored. + /// + string LogDirectory { get; } + + /// + /// Gets the directory path where application configuration files are stored. + /// + string ConfigDirectory { get; } + + /// + /// Gets a value indicating whether the application is running with administrator privileges. + /// + bool IsElevated { get; } + + /// + /// Gets a formatted summary of application information suitable for logging. + /// + /// A formatted string containing application information. + string GetApplicationInfoSummary(); + + /// + /// Sets the log directory delegate to be used for retrieving the log directory path. + /// This allows deferred initialization of the logger path. + /// + /// Delegate to retrieve the log directory path. + void SetLogDirectory(Func getLogDirectory); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs index 0c966f2593..c98740aaf5 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs @@ -2,20 +2,27 @@ // 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.Globalization; using System.Runtime.InteropServices; -using System.Security.Principal; using Microsoft.CmdPal.Core.Common.Services.Sanitizer; -using Windows.ApplicationModel; namespace Microsoft.CmdPal.Core.Common.Services.Reports; public sealed class ErrorReportBuilder : IErrorReportBuilder { private readonly ErrorReportSanitizer _sanitizer = new(); + private readonly IApplicationInfoService _appInfoService; private static string Preamble => Properties.Resources.ErrorReport_Global_Preamble; + /// + /// Initializes a new instance of the class. + /// + /// Optional application info service. If not provided, a default instance is created. + public ErrorReportBuilder(IApplicationInfoService? appInfoService = null) + { + _appInfoService = appInfoService ?? new ApplicationInfoService(null); + } + public string BuildReport(Exception exception, string context, bool redactPii = true) { ArgumentNullException.ThrowIfNull(exception); @@ -24,6 +31,9 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder var sanitizedMessage = redactPii ? _sanitizer.Sanitize(exceptionMessage) : exceptionMessage; var sanitizedFormattedException = redactPii ? _sanitizer.Sanitize(exception.ToString()) : exception.ToString(); + var applicationInfoSummary = GetAppInfoSafe(); + var applicationInfoSummarySanitized = redactPii ? _sanitizer.Sanitize(applicationInfoSummary) : applicationInfoSummary; + // Note: // - do not localize technical part of the report, we need to ensure it can be read by developers // - keep timestamp format should be consistent with the log (makes it easier to search) @@ -38,18 +48,7 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder HRESULT: 0x{exception.HResult:X8} ({exception.HResult}) Context: {context ?? "N/A"} - Application: - App version: {GetAppVersionSafe()} - Is elevated: {GetElevationStatus()} - - Environment: - OS version: {RuntimeInformation.OSDescription} - OS architecture: {RuntimeInformation.OSArchitecture} - Runtime identifier: {RuntimeInformation.RuntimeIdentifier} - Framework: {RuntimeInformation.FrameworkDescription} - Process architecture: {RuntimeInformation.ProcessArchitecture} - Culture: {CultureInfo.CurrentCulture.Name} - UI culture: {CultureInfo.CurrentUICulture.Name} + {applicationInfoSummarySanitized} Stack Trace: {exception.StackTrace} @@ -66,31 +65,17 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder """; } - private static string GetElevationStatus() + private string? GetAppInfoSafe() { - // Note: do not localize technical part of the report, we need to ensure it can be read by developers try { - var isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); - return isElevated ? "yes" : "no"; + return _appInfoService.GetApplicationInfoSummary(); } - catch (Exception) + catch (Exception ex) { - return "Failed to determine elevation status"; - } - } - - private static string GetAppVersionSafe() - { - // Note: do not localize technical part of the report, we need to ensure it can be read by developers - try - { - var version = Package.Current.Id.Version; - return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; - } - catch (Exception) - { - return "Failed to retrieve app version"; + // Getting application info should never throw, but if it does, we don't want it to prevent the report from being generated + var message = CoalesceExceptionMessage(ex); + return $"Failed to get application info summary: {message}"; } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs index 152bf95a62..789f1aaa03 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs @@ -68,11 +68,13 @@ public partial class App : Application, IDisposable /// public App() { + var appInfoService = new ApplicationInfoService(); + #if !CMDPAL_DISABLE_GLOBAL_ERROR_HANDLER - _globalErrorHandler.Register(this, GlobalErrorHandler.Options.Default); + _globalErrorHandler.Register(this, GlobalErrorHandler.Options.Default, appInfoService); #endif - Services = ConfigureServices(); + Services = ConfigureServices(appInfoService); IconCacheProvider.Initialize(Services); @@ -93,6 +95,9 @@ public partial class App : Application, IDisposable // This way, log statements from the core project will be captured by the PT logs var logWrapper = new LogWrapper(); CoreLogger.InitializeLogger(logWrapper); + + // Now that CoreLogger is initialized, initialize the logger delegate in ApplicationInfoService + appInfoService.SetLogDirectory(() => Logger.CurrentVersionLogDirectoryPath); } /// @@ -110,7 +115,7 @@ public partial class App : Application, IDisposable /// /// Configures the services for the application /// - private static ServiceProvider ConfigureServices() + private static ServiceProvider ConfigureServices(IApplicationInfoService appInfoService) { // TODO: It's in the Labs feed, but we can use Sergio's AOT-friendly source generator for this: https://github.com/CommunityToolkit/Labs-Windows/discussions/463 ServiceCollection services = new(); @@ -121,7 +126,7 @@ public partial class App : Application, IDisposable AddBuiltInCommands(services); - AddCoreServices(services); + AddCoreServices(services, appInfoService); AddUIServices(services, dispatcherQueue); @@ -197,9 +202,11 @@ public partial class App : Application, IDisposable services.AddIconServices(dispatcherQueue); } - private static void AddCoreServices(ServiceCollection services) + private static void AddCoreServices(ServiceCollection services, IApplicationInfoService appInfoService) { // Core services + services.AddSingleton(appInfoService); + services.AddSingleton(); services.AddSingleton(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs index 156f29be2e..12454d16c3 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/GlobalErrorHandler.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using ManagedCommon; +using Microsoft.CmdPal.Core.Common.Services; using Microsoft.CmdPal.Core.Common.Services.Reports; using Windows.Win32; using Windows.Win32.Foundation; @@ -17,19 +18,20 @@ namespace Microsoft.CmdPal.UI.Helpers; /// internal sealed partial class GlobalErrorHandler : IDisposable { - private readonly ErrorReportBuilder _errorReportBuilder = new(); + private ErrorReportBuilder? _errorReportBuilder; private Options? _options; private App? _app; // GlobalErrorHandler is designed to be self-contained; it can be registered and invoked before a service provider is available. - internal void Register(App app, Options options) + internal void Register(App app, Options options, IApplicationInfoService? appInfoService = null) { ArgumentNullException.ThrowIfNull(app); ArgumentNullException.ThrowIfNull(options); _options = options; - _app = app; + _errorReportBuilder = new ErrorReportBuilder(appInfoService); + _app.UnhandledException += App_UnhandledException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; @@ -68,7 +70,7 @@ internal sealed partial class GlobalErrorHandler : IDisposable if (context == Context.MainThreadException) { - var report = _errorReportBuilder.BuildReport(ex, context.ToString(), _options?.RedactPii ?? true); + var report = _errorReportBuilder!.BuildReport(ex, context.ToString(), _options?.RedactPii ?? true); StoreReport(report, storeOnDesktop: _options?.StoreReportOnUserDesktop == true); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Program.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Program.cs index 3a41fa95c8..3426fb4777 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Program.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Program.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using ManagedCommon; +using Microsoft.CmdPal.Core.Common.Services; using Microsoft.CmdPal.UI.Events; using Microsoft.PowerToys.Telemetry; using Microsoft.UI.Dispatching; @@ -65,6 +66,25 @@ internal sealed class Program } Logger.LogDebug($"Starting at {DateTime.UtcNow}"); + + // Log application startup information + try + { + var appInfoService = new ApplicationInfoService(() => Logger.CurrentVersionLogDirectoryPath); + var startupMessage = $""" + ============================================================ + Hello World! Command Palette is starting. + + {appInfoService.GetApplicationInfoSummary()} + ============================================================ + """; + Logger.LogInfo(startupMessage); + } + catch (Exception ex) + { + Logger.LogError("Failed to log application startup information", ex); + } + PowerToysTelemetry.Log.WriteEvent(new CmdPalProcessStarted()); WinRT.ComWrappersSupport.InitializeComWrappers(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml.cs index 064eab2f64..cb9157021f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml.cs @@ -2,15 +2,13 @@ // 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.Diagnostics; using System.Globalization; -using ManagedCommon; +using Microsoft.CmdPal.Core.Common.Services; using Microsoft.CmdPal.UI.Helpers; using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml.Controls; -using Windows.ApplicationModel; namespace Microsoft.CmdPal.UI.Settings; @@ -19,6 +17,7 @@ public sealed partial class GeneralPage : Page private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); private readonly SettingsViewModel? viewModel; + private readonly IApplicationInfoService _appInfoService; public GeneralPage() { @@ -27,6 +26,7 @@ public sealed partial class GeneralPage : Page var settings = App.Current.Services.GetService()!; var topLevelCommandManager = App.Current.Services.GetService()!; var themeService = App.Current.Services.GetService()!; + _appInfoService = App.Current.Services.GetRequiredService(); viewModel = new SettingsViewModel(settings, topLevelCommandManager, _mainTaskScheduler, themeService); } @@ -35,55 +35,8 @@ public sealed partial class GeneralPage : Page get { var versionNo = ResourceLoaderInstance.GetString("Settings_GeneralPage_VersionNo"); - if (!TryGetPackagedVersion(out var version) && !TryGetAssemblyVersion(out version)) - { - version = "?"; - } - + var version = _appInfoService.AppVersion; return string.Format(CultureInfo.CurrentCulture, versionNo, version); } } - - private static bool TryGetPackagedVersion(out string version) - { - version = string.Empty; - try - { - // Package.Current throws InvalidOperationException if the app is not packaged - var v = Package.Current.Id.Version; - version = $"{v.Major}.{v.Minor}.{v.Build}.{v.Revision}"; - return true; - } - catch (InvalidOperationException) - { - return false; - } - catch (Exception ex) - { - Logger.LogError("Failed to get version from the package", ex); - return false; - } - } - - private static bool TryGetAssemblyVersion(out string version) - { - version = string.Empty; - try - { - var processPath = Environment.ProcessPath; - if (string.IsNullOrEmpty(processPath)) - { - return false; - } - - var info = FileVersionInfo.GetVersionInfo(processPath); - version = $"{info.FileMajorPart}.{info.FileMinorPart}.{info.FileBuildPart}.{info.FilePrivatePart}"; - return true; - } - catch (Exception ex) - { - Logger.LogError("Failed to get version from the executable", ex); - return false; - } - } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml.cs index f4a5929ecb..a797a6a25d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/InternalPage.xaml.cs @@ -3,7 +3,8 @@ // See the LICENSE file in the project root for more information. using ManagedCommon; -using Microsoft.CommandPalette.Extensions.Toolkit; +using Microsoft.CmdPal.Core.Common.Services; +using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; using Windows.System; using Page = Microsoft.UI.Xaml.Controls.Page; @@ -15,9 +16,13 @@ namespace Microsoft.CmdPal.UI.Settings; /// public sealed partial class InternalPage : Page { + private readonly IApplicationInfoService _appInfoService; + public InternalPage() { InitializeComponent(); + + _appInfoService = App.Current.Services.GetRequiredService(); } private void ThrowPlainMainThreadException_Click(object sender, RoutedEventArgs e) @@ -46,7 +51,7 @@ public sealed partial class InternalPage : Page { try { - var logFolderPath = Logger.CurrentVersionLogDirectoryPath; + var logFolderPath = _appInfoService.LogDirectory; if (Directory.Exists(logFolderPath)) { await Launcher.LaunchFolderPathAsync(logFolderPath); @@ -78,7 +83,7 @@ public sealed partial class InternalPage : Page { try { - var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); + var directory = _appInfoService.ConfigDirectory; if (Directory.Exists(directory)) { await Launcher.LaunchFolderPathAsync(directory);