Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
66137a5
Enable running Microsoft Store Developer CLI commands via a new 'stor…
azchohfi Jan 6, 2026
e474de1
PR Feedback.
azchohfi Jan 6, 2026
e0cd4de
Added docs.
azchohfi Jan 8, 2026
1793946
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 8, 2026
2381b87
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 8, 2026
f7ecd5b
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 12, 2026
1e82b0d
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 13, 2026
5d533bb
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 14, 2026
f37604e
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 21, 2026
c529e7f
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Jan 22, 2026
9f78814
Merge branch 'main' into alzollin/msstoreCommand
nmetulev Jan 22, 2026
95e357d
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Feb 4, 2026
34bdc27
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Feb 9, 2026
b9457a9
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Feb 12, 2026
497dc55
Refactor MSStoreCLI install to use GitHub zip download
azchohfi Feb 12, 2026
d17794b
Update copyright.
azchohfi Feb 12, 2026
6cff29f
Addressed PR feedback.
azchohfi Feb 13, 2026
b7acb51
Merge branch 'main' into alzollin/msstoreCommand
nmetulev Feb 13, 2026
415695e
Merge branch 'main' into alzollin/msstoreCommand
azchohfi Feb 17, 2026
0f1a7fc
Merge branch 'main' into alzollin/msstoreCommand
nmetulev Feb 17, 2026
2166e9c
Merge branch 'main' into alzollin/msstoreCommand
nmetulev Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/winapp-CLI/WinApp.Cli/Commands/MSStoreCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;
using System.CommandLine;
using System.CommandLine.Invocation;
using WinApp.Cli.Services;

namespace WinApp.Cli.Commands;

internal class MSStoreCommand : Command
{
public MSStoreCommand() : base("store", "Run a Microsoft Store Developer CLI command.")
{
this.TreatUnmatchedTokensAsErrors = false;
}

public class Handler(IMSStoreCLIService msStoreCLIService, ILogger<MSStoreCommand> logger) : AsynchronousCommandLineAction
{
public override async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
{
var args = parseResult.UnmatchedTokens.ToArray();

var msstoreArgs = args.ToArray();

try
{
// Ensure the build tool is available, installing BuildTools if necessary
await msStoreCLIService.EnsureMSStoreCLIAvailableAsync(cancellationToken: cancellationToken);

var processStartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "msstore",
Arguments = string.Join(" ", msstoreArgs.Select(a => a.Contains(' ') ? $"\"{a}\"" : a)),
RedirectStandardInput = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = false
};

using var process = System.Diagnostics.Process.Start(processStartInfo);
if (process == null)
{
logger.LogError("Failed to start process for MSStoreCLI.");
return 1;
}

await process.WaitForExitAsync(cancellationToken);
return process.ExitCode;
}
catch (Exception ex)
{
logger.LogError("Error executing MSStoreCLI: {ErrorMessage}", ex.Message);
return 1;
}
}
}
}
4 changes: 3 additions & 1 deletion src/winapp-CLI/WinApp.Cli/Commands/WinAppRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public WinAppRootCommand(
GetWinappPathCommand getWinappPathCommand,
CertCommand certCommand,
SignCommand signCommand,
ToolCommand toolCommand) : base("Setup Windows SDK and Windows App SDK for use in your app, create MSIX packages, generate manifests and certificates, and use build tools.")
ToolCommand toolCommand,
MSStoreCommand msStoreCommand) : base("Setup Windows SDK and Windows App SDK for use in your app, create MSIX packages, generate manifests and certificates, and use build tools.")
{
Subcommands.Add(initCommand);
Subcommands.Add(restoreCommand);
Expand All @@ -39,5 +40,6 @@ public WinAppRootCommand(
Subcommands.Add(certCommand);
Subcommands.Add(signCommand);
Subcommands.Add(toolCommand);
Subcommands.Add(msStoreCommand);
}
}
6 changes: 4 additions & 2 deletions src/winapp-CLI/WinApp.Cli/Helpers/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
.AddSingleton<IWinappDirectoryService, WinappDirectoryService>()
.AddSingleton<IWorkspaceSetupService, WorkspaceSetupService>()
.AddSingleton<IGitignoreService, GitignoreService>()
.AddSingleton<IFirstRunService, FirstRunService>();
.AddSingleton<IFirstRunService, FirstRunService>()
.AddSingleton<IMSStoreCLIService, MSStoreCLIService>();
}

public static IServiceCollection ConfigureCommands(this IServiceCollection serviceCollection)
Expand All @@ -54,7 +55,8 @@ public static IServiceCollection ConfigureCommands(this IServiceCollection servi
.UseCommandHandler<CertGenerateCommand, CertGenerateCommand.Handler>()
.UseCommandHandler<CertInstallCommand, CertInstallCommand.Handler>()
.UseCommandHandler<SignCommand, SignCommand.Handler>()
.UseCommandHandler<ToolCommand, ToolCommand.Handler>();
.UseCommandHandler<ToolCommand, ToolCommand.Handler>()
.UseCommandHandler<MSStoreCommand, MSStoreCommand.Handler>();
}

public static IServiceCollection UseCommandHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>(this IServiceCollection services)
Expand Down
9 changes: 9 additions & 0 deletions src/winapp-CLI/WinApp.Cli/Services/IMSStoreCLIService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace WinApp.Cli.Services;

internal interface IMSStoreCLIService
{
Task EnsureMSStoreCLIAvailableAsync(CancellationToken cancellationToken = default);
}
71 changes: 71 additions & 0 deletions src/winapp-CLI/WinApp.Cli/Services/MSStoreCLIService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;

namespace WinApp.Cli.Services;

internal class MSStoreCLIService(ILogger<MSStoreCLIService> logger) : IMSStoreCLIService
{
public async Task EnsureMSStoreCLIAvailableAsync(CancellationToken cancellationToken = default)
{
if (!IsMSStoreCLIInPath())
{
// MSStoreCLI is not in the path, proceed to install it via winget
var confirm = Program.PromptYesNo("MSStoreCLI not installed - install MSStore Developer CLI with winget (user interaction may be required)? (y/N) ");
if (!confirm)
{
throw new InvalidOperationException("MSStoreCLI is required but not installed.");
}

var args = "install \"Microsoft Store Developer CLI\" --source msstore";
logger.LogDebug("Running winget with arguments: {Args}", args);

ProcessStartInfo installProcessStartInfo = new()
{
FileName = "winget",
Arguments = "install \"Microsoft Store Developer CLI\" --source msstore",
RedirectStandardInput = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = false
};

using Process? installProcess = Process.Start(installProcessStartInfo)
?? throw new InvalidOperationException("Failed to start process to install MSStoreCLI.");

await installProcess.WaitForExitAsync(cancellationToken);
const int NoApplicableUpgradeFound = -1978335189;
if (installProcess.ExitCode != 0 && installProcess.ExitCode != NoApplicableUpgradeFound)
{
throw new InvalidOperationException("Failed to install MSStoreCLI via winget.");
}

logger.LogInformation("MSStoreCLI installation completed.");
}
}

private bool IsMSStoreCLIInPath()
{
string? pathEnv = Environment.GetEnvironmentVariable("PATH");
if (string.IsNullOrEmpty(pathEnv))
{
return false;
}

string[] paths = pathEnv.Split(Path.PathSeparator);
var isAvailable = paths.Any(p =>
{
string fullPath = Path.Combine(p, "MSStore.exe");
return File.Exists(fullPath);
});
if (isAvailable)
{
logger.LogDebug("MSStoreCLI is available in PATH.");
}
return isAvailable;
}
}