From 4a608905bd82e45998d8cfc3fbc04408dd9594ec Mon Sep 17 00:00:00 2001 From: Muyuan Li Date: Tue, 31 Mar 2026 16:04:22 +0800 Subject: [PATCH 1/3] Add telemetry for CLI modules --- DATA_AND_PRIVACY.md | 12 ++++++ .../FileLocksmith/FileLocksmithCLI/main.cpp | 5 +++ .../FileLocksmith/FileLocksmithLib/Trace.cpp | 11 ++++++ .../FileLocksmith/FileLocksmithLib/Trace.h | 1 + src/modules/awake/Awake/Program.cs | 24 +++++++++++- .../Awake/Telemetry/AwakeCLICommandEvent.cs | 37 +++++++++++++++++++ .../imageresizer/ImageResizerCLI/Program.cs | 23 +++++++++++- .../Telemetry/ImageResizerCLICommandEvent.cs | 36 ++++++++++++++++++ 8 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs create mode 100644 src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md index 81dc855e6308..94e42a5443f1 100644 --- a/DATA_AND_PRIVACY.md +++ b/DATA_AND_PRIVACY.md @@ -274,6 +274,10 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.AwakeTimedKeepAwakeEvent Triggered when the system is kept awake for a specified time duration. + + + Microsoft.PowerToys.Awake_CLICommand + Triggered when an Awake CLI command is executed, logging the command name and success status. @@ -592,6 +596,10 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.FileLocksmith_QueryContextMenuError Occurs when there is an error querying the context menu for File Locksmith. + + Microsoft.PowerToys.FileLocksmith_CLICommand + Triggered when a File Locksmith CLI command is executed, logging the command name and success status. + ### FileExplorerAddOns @@ -776,6 +784,10 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.ImageResizer_QueryContextMenuError Triggered when there is an error querying the context menu for Image Resizer. + + Microsoft.PowerToys.ImageResizer_CLICommand + Triggered when an Image Resizer CLI command is executed, logging the command name and success status. + ### Keyboard Manager diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp index 67a4304b4e0a..200b611d10d5 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp +++ b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "CLILogic.h" #include "FileLocksmithLib/FileLocksmith.h" +#include "FileLocksmithLib/Trace.h" #include #include "resource.h" #include @@ -47,6 +48,7 @@ struct RealStringProvider : IStringProvider int wmain(int argc, wchar_t* argv[]) { winrt::init_apartment(); + Trace::RegisterProvider(); LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName); Logger::info("FileLocksmithCLI started"); @@ -65,7 +67,10 @@ int wmain(int argc, wchar_t* argv[]) Logger::info("Command succeeded"); } + Trace::CLICommand(L"filelocksmith", result.exit_code == 0); + std::wcout << result.output; + Trace::UnregisterProvider(); return result.exit_code; } #endif diff --git a/src/modules/FileLocksmith/FileLocksmithLib/Trace.cpp b/src/modules/FileLocksmith/FileLocksmithLib/Trace.cpp index a3d8e9038e0e..bd2c751e074b 100644 --- a/src/modules/FileLocksmith/FileLocksmithLib/Trace.cpp +++ b/src/modules/FileLocksmith/FileLocksmithLib/Trace.cpp @@ -49,3 +49,14 @@ void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept TraceLoggingHResult(hr), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); } + +void Trace::CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "FileLocksmith_CLICommand", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingWideString(commandName, "CommandName"), + TraceLoggingBoolean(successful, "Successful")); +} diff --git a/src/modules/FileLocksmith/FileLocksmithLib/Trace.h b/src/modules/FileLocksmith/FileLocksmithLib/Trace.h index 98642de854d1..9f8f090b52d2 100644 --- a/src/modules/FileLocksmith/FileLocksmithLib/Trace.h +++ b/src/modules/FileLocksmith/FileLocksmithLib/Trace.h @@ -11,4 +11,5 @@ class Trace : public telemetry::TraceBase static void Invoked() noexcept; static void InvokedRet(_In_ HRESULT hr) noexcept; static void QueryContextMenuError(_In_ HRESULT hr) noexcept; + static void CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept; }; diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 6b65e9eea31b..21e3c64176c3 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -19,6 +19,7 @@ using Awake.Core.Models; using Awake.Core.Native; using Awake.Properties; +using Awake.Telemetry; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Telemetry; @@ -68,6 +69,7 @@ private static async Task Main(string[] args) if (parseResult.Errors.Count > 0) { // Shows errors and returns non-zero. + LogCLITelemetry(successful: false); return rootCommand.Invoke(args); } @@ -97,6 +99,7 @@ private static async Task Main(string[] args) // Awake is already running - there is no need for us to process // anything further Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); + LogCLITelemetry(successful: false); return 1; } else @@ -104,6 +107,7 @@ private static async Task Main(string[] args) if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); + LogCLITelemetry(successful: false); return 1; } else @@ -125,7 +129,9 @@ private static async Task Main(string[] args) Bridge.GetPwrCapabilities(out _powerCapabilities); Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities, _serializerOptions)); - return await rootCommand.InvokeAsync(args); + var result = await rootCommand.InvokeAsync(args); + LogCLITelemetry(successful: result == 0); + return result; } } } @@ -216,6 +222,22 @@ private static RootCommand BuildRootCommand() return rootCommand; } + private static void LogCLITelemetry(bool successful) + { + try + { + PowerToysTelemetry.Log.WriteEvent(new AwakeCLICommandEvent + { + CommandName = "awake", + Successful = successful, + }); + } + catch (Exception ex) + { + Logger.LogError($"Failed to log CLI telemetry: {ex.Message}"); + } + } + private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception exception) diff --git a/src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs b/src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs new file mode 100644 index 000000000000..098d3d911450 --- /dev/null +++ b/src/modules/awake/Awake/Telemetry/AwakeCLICommandEvent.cs @@ -0,0 +1,37 @@ +// 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.CodeAnalysis; +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Awake.Telemetry +{ + /// + /// Telemetry event for Awake CLI command execution. + /// + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class AwakeCLICommandEvent : EventBase, IEvent + { + public AwakeCLICommandEvent() + { + EventName = "Awake_CLICommand"; + CommandName = string.Empty; + } + + /// + /// Gets or sets the name of the CLI command that was executed. + /// + public string CommandName { get; set; } + + /// + /// Gets or sets a value indicating whether the command executed successfully. + /// + public bool Successful { get; set; } + + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/modules/imageresizer/ImageResizerCLI/Program.cs b/src/modules/imageresizer/ImageResizerCLI/Program.cs index d24ab93bde01..c73476d7aa5c 100644 --- a/src/modules/imageresizer/ImageResizerCLI/Program.cs +++ b/src/modules/imageresizer/ImageResizerCLI/Program.cs @@ -7,7 +7,9 @@ using System.Text; using ImageResizer.Cli; +using ImageResizer.Cli.Telemetry; using ManagedCommon; +using Microsoft.PowerToys.Telemetry; namespace ImageResizerCLI; @@ -37,14 +39,33 @@ private static int Main(string[] args) try { var executor = new ImageResizerCliExecutor(); - return executor.Run(args); + int result = executor.Run(args); + LogCLITelemetry(result == 0); + return result; } catch (Exception ex) { CliLogger.Error($"Unhandled exception: {ex.Message}"); CliLogger.Error($"Stack trace: {ex.StackTrace}"); Console.Error.WriteLine($"Fatal error: {ex.Message}"); + LogCLITelemetry(successful: false); return 1; } } + + private static void LogCLITelemetry(bool successful) + { + try + { + PowerToysTelemetry.Log.WriteEvent(new ImageResizerCLICommandEvent + { + CommandName = "resize", + Successful = successful, + }); + } + catch (Exception ex) + { + CliLogger.Error($"Failed to log CLI telemetry: {ex.Message}"); + } + } } diff --git a/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs b/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs new file mode 100644 index 000000000000..fc6fb5c116ca --- /dev/null +++ b/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs @@ -0,0 +1,36 @@ +// 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.CodeAnalysis; +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace ImageResizer.Cli.Telemetry +{ + /// + /// Telemetry event for Image Resizer CLI command execution. + /// + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class ImageResizerCLICommandEvent : EventBase, IEvent + { + public ImageResizerCLICommandEvent() + { + EventName = "ImageResizer_CLICommand"; + } + + /// + /// Gets or sets the name of the CLI command that was executed. + /// + public string CommandName { get; set; } + + /// + /// Gets or sets a value indicating whether the command executed successfully. + /// + public bool Successful { get; set; } + + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} From 282a43b2eb1c033077c7a773a2e8486943e6c8df Mon Sep 17 00:00:00 2001 From: "Muyuan Li (from Dev Box)" Date: Fri, 10 Apr 2026 15:16:40 +0800 Subject: [PATCH 2/3] fix: align CLI telemetry with FancyZonesCLI pattern - FileLocksmith: log actual operation mode (query, kill, query-wait, query-json, help) instead of hardcoded 'filelocksmith' - ImageResizerCLICommandEvent: initialize CommandName to string.Empty for consistency with AwakeCLICommandEvent - Update DATA_AND_PRIVACY.md to document FileLocksmith operation modes - Add command_name assertions to FileLocksmith CLI tests --- DATA_AND_PRIVACY.md | 2 +- .../FileLocksmith/FileLocksmithCLI/CLILogic.cpp | 17 +++++++++-------- .../FileLocksmith/FileLocksmithCLI/CLILogic.h | 1 + .../FileLocksmith/FileLocksmithCLI/main.cpp | 2 +- .../tests/FileLocksmithCLITests.cpp | 6 ++++++ .../Telemetry/ImageResizerCLICommandEvent.cs | 1 + 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md index 94e42a5443f1..bc7bb2a9e05e 100644 --- a/DATA_AND_PRIVACY.md +++ b/DATA_AND_PRIVACY.md @@ -598,7 +598,7 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.FileLocksmith_CLICommand - Triggered when a File Locksmith CLI command is executed, logging the command name and success status. + Triggered when a File Locksmith CLI command is executed, logging the operation mode (query, kill, query-wait, query-json, or help) and success status. diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp index 17015e1ea336..39d9fec0d2d7 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp +++ b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp @@ -121,7 +121,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr if (argc < 2) { Logger::warn("No arguments provided"); - return { 1, get_usage(strings) }; + return { 1, get_usage(strings), L"help" }; } bool json_output = false; @@ -156,18 +156,18 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr catch (...) { Logger::error("Invalid timeout value"); - return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT) }; + return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT), L"query-wait" }; } } else { Logger::error("Timeout argument missing"); - return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG) }; + return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG), L"query-wait" }; } } else if (arg == L"--help") { - return { 0, get_usage(strings) }; + return { 0, get_usage(strings), L"help" }; } else { @@ -178,7 +178,7 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr if (paths.empty()) { Logger::error("No paths specified"); - return { 1, strings.GetString(IDS_ERROR_NO_PATHS) }; + return { 1, strings.GetString(IDS_ERROR_NO_PATHS), L"query" }; } Logger::info("Processing {} paths", paths.size()); @@ -213,13 +213,13 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr { Logger::warn("Timeout waiting for files to be unlocked"); ss << strings.GetString(IDS_TIMEOUT); - return { 1, ss.str() }; + return { 1, ss.str(), L"query-wait" }; } } Sleep(200); } - return { 0, ss.str() }; + return { 0, ss.str(), L"query-wait" }; } auto results = finder.find(paths); @@ -244,5 +244,6 @@ CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IPr output_ss << get_text(results, strings); } - return { 0, output_ss.str() }; + std::wstring cmd_name = kill ? L"kill" : (json_output ? L"query-json" : L"query"); + return { 0, output_ss.str(), cmd_name }; } diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h index c8f519592f33..eba9003c364f 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h +++ b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h @@ -8,6 +8,7 @@ struct CommandResult { int exit_code; std::wstring output; + std::wstring command_name; }; struct IProcessFinder diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp index 200b611d10d5..15a8c4b4a138 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp +++ b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp @@ -67,7 +67,7 @@ int wmain(int argc, wchar_t* argv[]) Logger::info("Command succeeded"); } - Trace::CLICommand(L"filelocksmith", result.exit_code == 0); + Trace::CLICommand(result.command_name.c_str(), result.exit_code == 0); std::wcout << result.output; Trace::UnregisterProvider(); diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp index a67e42badf64..da58127e4df6 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp +++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp @@ -52,6 +52,7 @@ namespace FileLocksmithCLIUnitTests auto result = run_command(1, argv, finder, terminator, strings); Assert::AreEqual(1, result.exit_code); + Assert::AreEqual(std::wstring(L"help"), result.command_name); } TEST_METHOD(TestHelp) @@ -64,6 +65,7 @@ namespace FileLocksmithCLIUnitTests auto result = run_command(2, argv, finder, terminator, strings); Assert::AreEqual(0, result.exit_code); + Assert::AreEqual(std::wstring(L"help"), result.command_name); } TEST_METHOD(TestFindProcesses) @@ -77,6 +79,7 @@ namespace FileLocksmithCLIUnitTests auto result = run_command(2, argv, finder, terminator, strings); Assert::AreEqual(0, result.exit_code); + Assert::AreEqual(std::wstring(L"query"), result.command_name); Assert::IsTrue(result.output.find(L"123") != std::wstring::npos); Assert::IsTrue(result.output.find(L"process") != std::wstring::npos); } @@ -94,6 +97,7 @@ namespace FileLocksmithCLIUnitTests Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str()); Assert::AreEqual(0, result.exit_code); + Assert::AreEqual(std::wstring(L"query-json"), result.command_name); Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos); Assert::IsTrue(result.output.find(L"123") != std::wstring::npos); } @@ -109,6 +113,7 @@ namespace FileLocksmithCLIUnitTests auto result = run_command(3, argv, finder, terminator, strings); Assert::AreEqual(0, result.exit_code); + Assert::AreEqual(std::wstring(L"kill"), result.command_name); Assert::AreEqual((size_t)1, terminator.terminatedPids.size()); Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]); } @@ -125,6 +130,7 @@ namespace FileLocksmithCLIUnitTests auto result = run_command(5, argv, finder, terminator, strings); Assert::AreEqual(1, result.exit_code); + Assert::AreEqual(std::wstring(L"query-wait"), result.command_name); } }; } diff --git a/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs b/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs index fc6fb5c116ca..fc8450b710f9 100644 --- a/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs +++ b/src/modules/imageresizer/ui/Cli/Telemetry/ImageResizerCLICommandEvent.cs @@ -19,6 +19,7 @@ public class ImageResizerCLICommandEvent : EventBase, IEvent public ImageResizerCLICommandEvent() { EventName = "ImageResizer_CLICommand"; + CommandName = string.Empty; } /// From cda09be27ed7d5e21629f0f2a106db22487bfdb7 Mon Sep 17 00:00:00 2001 From: "Muyuan Li (from Dev Box)" Date: Fri, 10 Apr 2026 15:35:28 +0800 Subject: [PATCH 3/3] fix: address PR review comments - Awake: move LogCLITelemetry before Exit() calls since Manager.CompleteExit calls Environment.Exit, making code after Exit() unreachable - ImageResizer: derive CommandName from actual operation (help, show-config, error, resize) instead of hardcoded 'resize', via new CommandName property on ImageResizerCliExecutor --- src/modules/awake/Awake/Program.cs | 4 ++-- src/modules/imageresizer/ImageResizerCLI/Program.cs | 8 ++++---- .../imageresizer/ui/Cli/ImageResizerCliExecutor.cs | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 21e3c64176c3..5d3b591c84a3 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -98,16 +98,16 @@ private static async Task Main(string[] args) { // Awake is already running - there is no need for us to process // anything further - Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); LogCLITelemetry(successful: false); + Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); return 1; } else { if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { - Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); LogCLITelemetry(successful: false); + Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); return 1; } else diff --git a/src/modules/imageresizer/ImageResizerCLI/Program.cs b/src/modules/imageresizer/ImageResizerCLI/Program.cs index c73476d7aa5c..2876caa32aa7 100644 --- a/src/modules/imageresizer/ImageResizerCLI/Program.cs +++ b/src/modules/imageresizer/ImageResizerCLI/Program.cs @@ -40,7 +40,7 @@ private static int Main(string[] args) { var executor = new ImageResizerCliExecutor(); int result = executor.Run(args); - LogCLITelemetry(result == 0); + LogCLITelemetry(executor.CommandName, result == 0); return result; } catch (Exception ex) @@ -48,18 +48,18 @@ private static int Main(string[] args) CliLogger.Error($"Unhandled exception: {ex.Message}"); CliLogger.Error($"Stack trace: {ex.StackTrace}"); Console.Error.WriteLine($"Fatal error: {ex.Message}"); - LogCLITelemetry(successful: false); + LogCLITelemetry("resize", successful: false); return 1; } } - private static void LogCLITelemetry(bool successful) + private static void LogCLITelemetry(string commandName, bool successful) { try { PowerToysTelemetry.Log.WriteEvent(new ImageResizerCLICommandEvent { - CommandName = "resize", + CommandName = commandName, Successful = successful, }); } diff --git a/src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs b/src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs index 851d3e798314..00d593344adf 100644 --- a/src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs +++ b/src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs @@ -19,6 +19,11 @@ namespace ImageResizer.Cli /// public class ImageResizerCliExecutor { + /// + /// Gets the name of the last CLI operation that was executed. + /// + public string CommandName { get; private set; } = "resize"; + /// /// Runs the CLI executor with the provided command-line arguments. /// @@ -37,18 +42,21 @@ public int Run(string[] args) } CliOptions.PrintUsage(); + CommandName = "error"; return 1; } if (cliOptions.ShowHelp) { CliOptions.PrintUsage(); + CommandName = "help"; return 0; } if (cliOptions.ShowConfig) { CliOptions.PrintConfig(Settings.Default); + CommandName = "show-config"; return 0; } @@ -56,6 +64,7 @@ public int Run(string[] args) { Console.WriteLine(Resources.CLI_NoInputFiles); CliOptions.PrintUsage(); + CommandName = "error"; return 1; }