mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
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. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #45368 - [x] Closes: #45370 <!-- - [ ] 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
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the packaging flavor of the application.
|
||||
/// </summary>
|
||||
public enum AppPackagingFlavor
|
||||
{
|
||||
/// <summary>
|
||||
/// Application is packaged as a Windows MSIX package.
|
||||
/// </summary>
|
||||
Packaged,
|
||||
|
||||
/// <summary>
|
||||
/// Application is running unpackaged (native executable).
|
||||
/// </summary>
|
||||
Unpackaged,
|
||||
|
||||
/// <summary>
|
||||
/// Application is running as unpackaged portable (self-contained distribution).
|
||||
/// </summary>
|
||||
UnpackagedPortable,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for retrieving application version information safely.
|
||||
/// </summary>
|
||||
internal static class VersionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>The application version string, or a fallback value if retrieval fails.</returns>
|
||||
public static string GetAppVersionSafe()
|
||||
{
|
||||
if (TryGetPackagedVersion(out var version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
if (TryGetAssemblyVersion(out version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the application version from the package manifest.
|
||||
/// </summary>
|
||||
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
|
||||
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the application version from the executable file.
|
||||
/// </summary>
|
||||
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
|
||||
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IApplicationInfoService providing application-wide information.
|
||||
/// </summary>
|
||||
public sealed class ApplicationInfoService : IApplicationInfoService
|
||||
{
|
||||
private readonly Lazy<string> _configDirectory = new(() => Utilities.BaseSettingsPath("Microsoft.CmdPal"));
|
||||
private readonly Lazy<bool> _isElevated;
|
||||
private readonly Lazy<string> _logDirectory;
|
||||
private readonly Lazy<AppPackagingFlavor> _packagingFlavor;
|
||||
private Func<string>? _getLogDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class.
|
||||
/// The log directory delegate can be set later via <see cref="SetLogDirectory(Func{string})"/>.
|
||||
/// </summary>
|
||||
public ApplicationInfoService()
|
||||
{
|
||||
_packagingFlavor = new Lazy<AppPackagingFlavor>(DeterminePackagingFlavor);
|
||||
_isElevated = new Lazy<bool>(DetermineElevationStatus);
|
||||
_logDirectory = new Lazy<string>(() => _getLogDirectory?.Invoke() ?? "Not available");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class with an optional log directory provider.
|
||||
/// </summary>
|
||||
/// <param name="getLogDirectory">Optional delegate to retrieve the log directory path. If not provided, the log directory will be unavailable.</param>
|
||||
public ApplicationInfoService(Func<string>? getLogDirectory)
|
||||
: this()
|
||||
{
|
||||
_getLogDirectory = getLogDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the log directory delegate to be used for retrieving the log directory path.
|
||||
/// This allows deferred initialization of the logger path.
|
||||
/// </summary>
|
||||
/// <param name="getLogDirectory">Delegate to retrieve the log directory path.</param>
|
||||
public void SetLogDirectory(Func<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to application-wide information such as version, packaging flavor, and directory paths.
|
||||
/// </summary>
|
||||
public interface IApplicationInfoService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the application version as a string in the format "Major.Minor.Build.Revision".
|
||||
/// </summary>
|
||||
string AppVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packaging flavor of the application.
|
||||
/// </summary>
|
||||
AppPackagingFlavor PackagingFlavor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory path where application logs are stored.
|
||||
/// </summary>
|
||||
string LogDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory path where application configuration files are stored.
|
||||
/// </summary>
|
||||
string ConfigDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the application is running with administrator privileges.
|
||||
/// </summary>
|
||||
bool IsElevated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted summary of application information suitable for logging.
|
||||
/// </summary>
|
||||
/// <returns>A formatted string containing application information.</returns>
|
||||
string GetApplicationInfoSummary();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the log directory delegate to be used for retrieving the log directory path.
|
||||
/// This allows deferred initialization of the logger path.
|
||||
/// </summary>
|
||||
/// <param name="getLogDirectory">Delegate to retrieve the log directory path.</param>
|
||||
void SetLogDirectory(Func<string> getLogDirectory);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ErrorReportBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appInfoService">Optional application info service. If not provided, a default instance is created.</param>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,11 +68,13 @@ public partial class App : Application, IDisposable
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +115,7 @@ public partial class App : Application, IDisposable
|
||||
/// <summary>
|
||||
/// Configures the services for the application
|
||||
/// </summary>
|
||||
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<IExtensionService, ExtensionService>();
|
||||
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<SettingsModel>()!;
|
||||
var topLevelCommandManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
var themeService = App.Current.Services.GetService<IThemeService>()!;
|
||||
_appInfoService = App.Current.Services.GetRequiredService<IApplicationInfoService>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
public sealed partial class InternalPage : Page
|
||||
{
|
||||
private readonly IApplicationInfoService _appInfoService;
|
||||
|
||||
public InternalPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_appInfoService = App.Current.Services.GetRequiredService<IApplicationInfoService>();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user