diff --git a/docs/usage.md b/docs/usage.md
index 549aeef14..03c57e62a 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -963,7 +963,7 @@ winapp run . --no-build --detach --json
```
- A WinUI 3 C++/WinRT app from the Visual Studio **WinUI Blank App (Packaged)** template runs packaged. Other application projects, such as a console app or a WinUI app built with `-p WindowsPackageType=None`, run unpackaged from their built `.exe`.
-- Builds `Debug` for the current architecture by default. `--arch x64|arm64|x86` or `-p Platform=x64|ARM64|Win32` selects the architecture (passing both with different architectures is an error); a custom `-p Platform` name is passed through.
+- Builds `Debug` for the current architecture by default. `-c Release` or `-p Configuration=Release` selects the configuration, and `--arch x64|arm64|x86` or `-p Platform=x64|ARM64|Win32` selects the architecture. Passing both forms with different values is an error. A custom `-p Platform` name is passed through.
- Restores the `packages.config` NuGet packages first (skip with `--no-restore`) and installs the Windows App Runtime version the build uses, including when the Windows App SDK props are imported from a shared `.props` file rather than listed in the project's own `packages.config`. Packaged apps also get the framework packages the build references, such as the Debug VC++ runtime, installed when missing.
- `--framework` and `--aot` apply only to .NET projects.
- Build output shows MSBuild's warnings and errors only, with a spinner while it builds; `--verbose` shows MSBuild's full output and exact command.
diff --git a/src/winapp-CLI/WinApp.Cli.Tests/ProjectRunServiceCppTests.cs b/src/winapp-CLI/WinApp.Cli.Tests/ProjectRunServiceCppTests.cs
index 3c7874e1e..d05cca761 100644
--- a/src/winapp-CLI/WinApp.Cli.Tests/ProjectRunServiceCppTests.cs
+++ b/src/winapp-CLI/WinApp.Cli.Tests/ProjectRunServiceCppTests.cs
@@ -58,6 +58,7 @@ public sealed class ProjectRunServiceCppTests : IDisposable
private static readonly string[] ExpectedBuildTokens = ["-restore", "-p:RestorePackagesConfig=true", "-t:Build", "-p:Configuration=Debug", "-p:Platform=x64"];
private static readonly string[] ExpectedArm64Tokens = ["-p:Foo=Bar", "-p:Configuration=Release", "-p:Platform=ARM64"];
+ private static readonly string[] ExpectedReleaseArm64Tokens = ["-p:Platform=ARM64", "-p:Configuration=Release"];
private static readonly string[] ExpectedPackageIds = ["Microsoft.WindowsAppSDK", "Microsoft.WindowsAppSDK.Runtime"];
private DirectoryInfo _tempDir = null!;
@@ -537,6 +538,24 @@ public void CppArchitectureFromProperties_MapsStandardPlatforms()
Assert.IsNull(ProjectRunService.CppArchitectureFromProperties([]));
}
+ [TestMethod]
+ public void CppConfigurationFromProperties_ReadsLastNonEmptyValue()
+ {
+ Assert.AreEqual("Release", ProjectRunService.CppConfigurationFromProperties(["Configuration=Release"]));
+ Assert.AreEqual("Release", ProjectRunService.CppConfigurationFromProperties(["Configuration=Debug", "configuration=Release", "Configuration="]));
+ Assert.IsNull(ProjectRunService.CppConfigurationFromProperties(["Platform=ARM64"]));
+ }
+
+ [TestMethod]
+ public void BuildCppPropertyTokens_UserConfigurationProperty_EmitsOneConfiguration()
+ {
+ var options = new ProjectRunOptions("Release", "arm64", null, false, false, ["Configuration=Release", "Platform=ARM64"]);
+
+ var tokens = ProjectRunService.BuildCppPropertyTokens(options);
+
+ CollectionAssert.AreEqual(ExpectedReleaseArm64Tokens, tokens);
+ }
+
[TestMethod]
public void BuildCppCommandDisplay_ShowsOnlyWhatChangesTheBuild()
{
diff --git a/src/winapp-CLI/WinApp.Cli.Tests/RunCommandProjectModeTests.cs b/src/winapp-CLI/WinApp.Cli.Tests/RunCommandProjectModeTests.cs
index d5fd748d4..ac4cfa23c 100644
--- a/src/winapp-CLI/WinApp.Cli.Tests/RunCommandProjectModeTests.cs
+++ b/src/winapp-CLI/WinApp.Cli.Tests/RunCommandProjectModeTests.cs
@@ -874,6 +874,42 @@ public async Task ProjectMode_Cpp_PlatformThisMachineCannotRun_StillBuildsWithNo
Assert.AreEqual("arm64", _fakeProjectRunService.BuildOptions[0].Architecture);
}
+ [TestMethod]
+ [DataRow(new[] { "-p", "Configuration=Release" }, DisplayName = "-p Configuration alone")]
+ [DataRow(new[] { "-c", "Release", "-p", "Configuration=Release" }, DisplayName = "-c with matching -p Configuration")]
+ public async Task ProjectMode_Cpp_ConfigurationProperty_SelectsConfiguration(string[] configArgs)
+ {
+ var vcxproj = new FileInfo(Path.Join(_tempDirectory.FullName, "App.vcxproj"));
+ File.WriteAllText(vcxproj.FullName, "");
+ _fakeProjectRunService.InputResolutionOverride = new RunInputResolution(WinAppRunMode.Project, vcxproj, _tempDirectory);
+ SetUnpackagedOutcome(vcxproj, CreateTargetDir(withManifest: false), selfContained: false, arch: "arm64");
+ GetRequiredService().OsArchitecture = () => System.Runtime.InteropServices.Architecture.Arm64;
+ var command = GetRequiredService();
+
+ var exitCode = await ParseAndInvokeWithCaptureAsync(command, [vcxproj.FullName, .. configArgs, "-p", "Platform=ARM64", "--detach"]);
+
+ Assert.AreEqual(0, exitCode);
+ Assert.AreEqual("Release", _fakeProjectRunService.BuildOptions[0].Configuration,
+ "-p Configuration=Release must select Release instead of the Debug default");
+ StringAssert.Contains(TestAnsiConsole.Output, "Release | arm64");
+ }
+
+ [TestMethod]
+ public async Task ProjectMode_Cpp_ConfigurationPropertyConflictingWithConfigurationOption_FailsBeforeBuilding()
+ {
+ var vcxproj = new FileInfo(Path.Join(_tempDirectory.FullName, "App.vcxproj"));
+ File.WriteAllText(vcxproj.FullName, "");
+ _fakeProjectRunService.InputResolutionOverride = new RunInputResolution(WinAppRunMode.Project, vcxproj, _tempDirectory);
+ var command = GetRequiredService();
+
+ var exitCode = await ParseAndInvokeWithCaptureAsync(command, [vcxproj.FullName, "-c", "Debug", "-p", "Configuration=Release", "--detach"]);
+
+ Assert.AreEqual(1, exitCode);
+ Assert.AreEqual(0, _fakeProjectRunService.BuildAndResolveCalls.Count, "a conflicting configuration must fail before building");
+ StringAssert.Contains($"{ConsoleStdOut}{ConsoleStdErr}{TestAnsiConsole.Output}",
+ "-p Configuration=Release conflicts with -c Debug. Pass only one of them.");
+ }
+
[TestMethod]
public async Task ProjectMode_Unpackaged_ExeRequiringAdministrator_ExplainsElevation()
{
diff --git a/src/winapp-CLI/WinApp.Cli/Commands/RunCommand.ProjectMode.cs b/src/winapp-CLI/WinApp.Cli/Commands/RunCommand.ProjectMode.cs
index 499b0b79b..2a7356afc 100644
--- a/src/winapp-CLI/WinApp.Cli/Commands/RunCommand.ProjectMode.cs
+++ b/src/winapp-CLI/WinApp.Cli/Commands/RunCommand.ProjectMode.cs
@@ -196,6 +196,19 @@ private async Task RunProjectModeAsync(
return Fail($"{(aot ? "--aot" : "--framework")} applies to .NET projects and can't be used with a C++ project ({csproj.Name}).", isJson);
}
+ // For C++, -p Configuration=Release is how Visual Studio users pick the configuration, so treat it
+ // like -c. Both are allowed only when they name the same configuration.
+ if (isCpp && ProjectRunService.CppConfigurationFromProperties(properties) is { } propertyConfiguration)
+ {
+ if (parseResult.GetResult(ConfigurationOption) is { Implicit: false }
+ && !string.Equals(propertyConfiguration, configuration, StringComparison.OrdinalIgnoreCase))
+ {
+ return Fail($"-p Configuration={propertyConfiguration} conflicts with -c {configuration}. Pass only one of them.", isJson);
+ }
+
+ configuration = propertyConfiguration;
+ }
+
// For C++, -p Platform=ARM64 is how Visual Studio users pick the architecture, so honor it when
// --arch/--runtime weren't given instead of defaulting to the machine's architecture.
if (isCpp && archOption is null && runtimeOption is null
diff --git a/src/winapp-CLI/WinApp.Cli/Services/ProjectRunService.Cpp.cs b/src/winapp-CLI/WinApp.Cli/Services/ProjectRunService.Cpp.cs
index 71d84a102..e98bf55a2 100644
--- a/src/winapp-CLI/WinApp.Cli/Services/ProjectRunService.Cpp.cs
+++ b/src/winapp-CLI/WinApp.Cli/Services/ProjectRunService.Cpp.cs
@@ -108,6 +108,13 @@ internal static bool IsCppWindowsStoreProject(FileInfo project)
internal static string? CppArchitectureFromProperties(IReadOnlyList properties) =>
TryGetUserProperty(properties, "Platform", out var platform) ? ArchitectureOfCppPlatform(platform) : null;
+ ///
+ /// The configuration a user -p Configuration names (last non-empty value wins), or null when none
+ /// is set. Lets -p Configuration=Release select the configuration like -c Release.
+ ///
+ internal static string? CppConfigurationFromProperties(IReadOnlyList properties) =>
+ TryGetUserProperty(properties, "Configuration", out var configuration) ? configuration : null;
+
private async Task BuildAndResolveCppAsync(
FileInfo project,
ProjectRunOptions options,
@@ -166,7 +173,8 @@ private async Task BuildAndResolveCppAsync(
///
/// The global properties shared by the build and evaluate passes: the user's -p first, then the
/// solution context and Configuration/Platform. MSBuild is last-wins, so a user -p:Platform or
- /// Solution* is honored by not emitting winapp's own; Configuration always follows -c.
+ /// Solution* is honored by not emitting winapp's own; Configuration always follows the resolved
+ /// configuration (-c, or -p Configuration when -c isn't given).
///
internal static List BuildCppPropertyTokens(ProjectRunOptions options)
{