Repository: Azure/azure-mcp (C#)

Issue:
AZ executable doesn't launch properly on Windows
After the release of version 0.3.2, we've found that the Azure CLI extension can no longer launch `az` properly.  It always fails with an error like the one below:

`An error occurred trying to start process 'C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az' with working directory 'x'. The specified executable is not a valid application for this OS platform. `

```System.ComponentModel.Win32Exception:
   at System.Diagnostics.Process.StartWithCreateProcess (System.Diagnostics.Process, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
   at AzureMcp.Services.ProcessExecution.ExternalProcessService+<ExecuteAsync>d__1.MoveNext (azmcp, Version=0.3.2.0, Culture=neutral, PublicKeyToken=null: D:\a\_work\1\s\src\Services\ProcessExecution\ExternalProcessService.cs:68)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw (System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult (System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)
   at AzureMcp.Areas.Extension.Commands.AzCommand+<ExecuteAsync>d__18.MoveNext (azmcp, Version=0.3.2.0, Culture=neutral, PublicKeyToken=null: D:\a\_work\1\s\src\Areas\Extension\Commands\AzCommand.cs:169)
```

<img width="1098" height="611" alt="Image" src="https://github.com/user-attachments/assets/3827e562-f458-460f-96ad-e568f5445cc2" />

We've confirmed that version 0.3.1 works properly:

<img width="1107" height="383" alt="Image" src="https://github.com/user-attachments/assets/ab8eacd6-63cd-413c-9123-25740b3b7d3e" />

We've replicated this behavior on Windows Server 2025 and Windows 11, where the AZ CLI was installed with MSI packages.  On our systems, it seems we have an `az` bash script in the PATH:

```
PS C:\> where.exe az
C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az
C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd
```

<img width="1325" height="769" alt="Image" src="https://github.com/user-attachments/assets/d408e37a-0033-4532-8591-147189d1a86e" />

We haven't been able to confirm, but it seems likely the new issue is related to https://github.com/Azure/azure-mcp/issues/351 and https://github.com/Azure/azure-mcp/pull/552.  Thanks in advance!


Discussion:
Same issue encountered and rolling azure mcp back to 0.3.0 fixed this. Running the azure cli paths directly in PWSH work fine. 

The full error log when Claude Desktop executing azmcp-extension-ac:

{"status":500,"message":"An error occurred trying to start process \u0027C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin\\az\u0027 with working directory \u0027C:\\Users\\username\\AppData\\Local\\AnthropicClaude\\app-0.12.16\u0027. The specified executable is not a valid application for this OS platform.. To mitigate this issue, please refer to the troubleshooting guidelines here at https://aka.ms/azmcp/troubleshooting.","results":{"message":"An error occurred trying to start process \u0027C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin\\az\u0027 with working directory \u0027C:\\Users\\username\\AppData\\Local\\AnthropicClaude\\app-0.12.16\u0027. The specified executable is not a valid application for this OS platform.","stackTrace":"   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)\r\n   at AzureMcp.Services.ProcessExecution.ExternalProcessService.ExecuteAsync(String executablePath, String arguments, Int32 timeoutSeconds, IEnumerable\u00601 customPaths) in D:\\a\\_work\\1\\s\\src\\Services\\ProcessExecution\\ExternalProcessService.cs:line 68\r\n   at AzureMcp.Areas.Extension.Commands.AzCommand.ExecuteAsync(CommandContext context, ParseResult parseResult) in D:\\a\\_work\\1\\s\\src\\Areas\\Extension\\Commands\\AzCommand.cs:line 169","type":"Win32Exception"},"duration":0}
Also encountering this issue!

{"status":500,"message":"An error occurred trying to start process \u0027C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin\\az\u0027 with working directory \u0027C:\\Users\\usernme\u0027. The specified executable is not a valid application for this OS platform.. To mitigate this issue, please refer to the troubleshooting guidelines here at https://aka.ms/azmcp/troubleshooting.","results":{"message":"An error occurred trying to start process \u0027C:\\Program Files\\Microsoft SDKs\\Azure\\CLI2\\wbin\\az\u0027 with working directory \u0027C:\\Users\\username\u0027. The specified executable is not a valid application for this OS platform.","stackTrace":"   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)\r\n   at AzureMcp.Services.ProcessExecution.ExternalProcessService.ExecuteAsync(String executablePath, String arguments, Int32 timeoutSeconds, IEnumerable\u00601 customPaths) in D:\\a\\_work\\1\\s\\src\\Services\\ProcessExecution\\ExternalProcessService.cs:line 68\r\n   at AzureMcp.Areas.Extension.Commands.AzCommand.ExecuteAsync(CommandContext context, ParseResult parseResult) in D:\\a\\_work\\1\\s\\src\\Areas\\Extension\\Commands\\AzCommand.cs:line 169","type":"Win32Exception"},"duration":0}
This fix for this will be released today.
Can you please verify this fix?
> Can you please verify this fix?

Yup confirmed fixed in v0.4.1



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a4a1273f..fa5c4354b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,17 @@
 # Release History
 
-## 0.4.1 (Unreleased)
+## 0.4.1 (2025-07-17)
 
 ### Features Added
 - Support for Azure Load testing operations - Modify load testing resource, test and test runs. [#315](https://github.com/Azure/azure-mcp/pull/315)
-
 - Support for Azure Data Lake Storage Gen2 operations - List paths in Data Lake file systems via the command: `azmcp storage datalake file-system list-paths`. [#608](https://github.com/Azure/azure-mcp/pull/608)
-
 - Added new commands for Azure Function code generation and deployment best practices (https://github.com/Azure/azure-mcp/pull/630)
-
- - Add `azmcp sql firewall-rule list` command to list SQL server firewall rules. [[#610](https://github.com/Azure/azure-mcp/pull/610)]
-
-### Breaking Changes
+- Add `azmcp sql firewall-rule list` command to list SQL server firewall rules. [[#610](https://github.com/Azure/azure-mcp/pull/610)]
+- Added support for listing SQL elastic pools via the command: `azmcp sql elastic-pool list`. [[#581](https://github.com/Azure/azure-mcp/pull/581)]
 
 ### Bugs Fixed
 
-### Other Changes
-
-## 0.4.1 (2025-07-17)
-
-### Features Added
-
-- Added support for listing SQL elastic pools via the command: `azmcp sql elastic-pool list`. [[#581](https://github.com/Azure/azure-mcp/pull/581)]
+- Fixed Azure CLI executable path resolution on Windows to prioritize .cmd over bash script. [[#611](https://github.com/Azure/azure-mcp/issues/611)]
 
 ## 0.4.0 (2025-07-15)
 
diff --git a/src/Areas/Extension/Commands/AzCommand.cs b/src/Areas/Extension/Commands/AzCommand.cs
index e511ac1a5..6b5d2cbfc 100644
--- a/src/Areas/Extension/Commands/AzCommand.cs
+++ b/src/Areas/Extension/Commands/AzCommand.cs
@@ -20,6 +20,14 @@ public sealed class AzCommand(ILogger<AzCommand> logger, int processTimeoutSecon
     private volatile bool _isAuthenticated = false;
     private static readonly SemaphoreSlim s_authSemaphore = new(1, 1);
 
+    /// <summary>
+    /// Clears the cached Azure CLI path. Used for testing purposes.
+    /// </summary>
+    internal static void ClearCachedAzPath()
+    {
+        _cachedAzPath = null;
+    }
+
     public override string Name => "az";
 
     public override string Description =>
@@ -51,7 +59,7 @@ protected override AzOptions BindOptions(ParseResult parseResult)
         return options;
     }
 
-    private static string? FindAzCliPath()
+    internal static string? FindAzCliPath()
     {
         string executableName = "az";
 
@@ -66,33 +74,36 @@ protected override AzOptions BindOptions(ParseResult parseResult)
             return null;
 
         string[] paths = pathEnv.Split(Path.PathSeparator);
+        var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+
         foreach (string path in paths)
         {
             string fullPath = Path.Combine(path.Trim(), executableName);
+
+            // On Windows, prioritize .cmd and .bat extensions over the base executable
+            // This ensures we use az.cmd instead of the az bash script which isn't executable by .NET
+            if (isWindows)
+            {
+                string cmdPath = Path.ChangeExtension(fullPath, ".cmd");
+                if (File.Exists(cmdPath))
+                {
+                    _cachedAzPath = cmdPath;
+                    return _cachedAzPath;
+                }
+                string batPath = Path.ChangeExtension(fullPath, ".bat");
+                if (File.Exists(batPath))
+                {
+                    _cachedAzPath = batPath;
+                    return _cachedAzPath;
+                }
+            }
+
+            // Fall back to the base executable name
             if (File.Exists(fullPath))
             {
                 _cachedAzPath = fullPath;
                 return _cachedAzPath;
             }
-            else
-            {
-                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-                if (isWindows)
-                {
-                    string exePath = Path.ChangeExtension(fullPath, ".cmd");
-                    if (File.Exists(exePath))
-                    {
-                        _cachedAzPath = exePath;
-                        return _cachedAzPath;
-                    }
-                    string batPath = Path.ChangeExtension(fullPath, ".bat");
-                    if (File.Exists(batPath))
-                    {
-                        _cachedAzPath = batPath;
-                        return _cachedAzPath;
-                    }
-                }
-            }
         }
         return null;
     }


Test patch:
diff --git a/tests/Areas/Extension/UnitTests/AzCommandTests.cs b/tests/Areas/Extension/UnitTests/AzCommandTests.cs
index 57dea00b3..11889239e 100644
--- a/tests/Areas/Extension/UnitTests/AzCommandTests.cs
+++ b/tests/Areas/Extension/UnitTests/AzCommandTests.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT License.
 
 using System.CommandLine.Parsing;
+using System.Runtime.InteropServices;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using AzureMcp.Areas.Extension.Commands;
@@ -186,6 +187,92 @@ public async Task ExecuteAsync_HandlesNonJsonOutput_AndWrapsInParseOutput()
         Assert.NotNull(response.Results);
     }
 
+    [Fact]
+    public void FindAzCliPath_PrioritizesCmdExtensionOnWindows()
+    {
+        // This test verifies that on Windows, .cmd files are prioritized over bash scripts
+        // which addresses the issue where az bash script cannot be executed by .NET Process.Start()
+
+        // We can only test this meaningfully if we can create a temporary test environment
+        // Since we can't easily mock the file system and PATH in a unit test,
+        // this test documents the expected behavior and validates the logic flow
+
+        // The key insight is that the new logic checks for .cmd/.bat FIRST on Windows
+        // before falling back to the base executable name
+
+        // Clear any cached path to ensure fresh execution
+        AzCommand.ClearCachedAzPath();
+
+        // The method should find az.cmd before az on Windows
+        var result = AzCommand.FindAzCliPath();
+
+        // We can't make strong assertions about the result since it depends on the actual
+        // system PATH, but we can verify the method doesn't throw and returns a string or null
+        Assert.True(result == null || result.Length > 0);
+    }
+
+    [Fact]
+    public void FindAzCliPath_WithTemporaryTestEnvironment_PrioritizesCmdOnWindows()
+    {
+        // Clear any cached path to ensure fresh execution
+        AzCommand.ClearCachedAzPath();
+
+        // Create a temporary directory to simulate the Azure CLI installation
+        const int TempDirSuffixLength = 8;
+        var tempDir = Path.Combine(Path.GetTempPath(), "AzCliTest_" + Guid.NewGuid().ToString("N")[..TempDirSuffixLength]);
+        Directory.CreateDirectory(tempDir);
+
+        try
+        {
+            // Create test files that simulate the Azure CLI installation on Windows
+            var azPath = Path.Combine(tempDir, "az");
+            var azCmdPath = Path.Combine(tempDir, "az.cmd");
+
+            // Create both files
+            File.WriteAllText(azPath, "#!/bin/bash\necho 'This is a bash script'");
+            File.WriteAllText(azCmdPath, "@echo off\necho This is a Windows batch file");
+
+            // Save the original PATH
+            var originalPath = Environment.GetEnvironmentVariable("PATH");
+
+            try
+            {
+                // Temporarily modify PATH to include our test directory
+                Environment.SetEnvironmentVariable("PATH", tempDir + Path.PathSeparator + originalPath);
+
+                // Clear cached path again after PATH change
+                AzCommand.ClearCachedAzPath();
+
+                // Call the method
+                var result = AzCommand.FindAzCliPath();
+
+                // On Windows, it should find the .cmd file; on other platforms, the base file
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                {
+                    Assert.Equal(azCmdPath, result);
+                }
+                else
+                {
+                    Assert.Equal(azPath, result);
+                }
+            }
+            finally
+            {
+                // Restore the original PATH
+                Environment.SetEnvironmentVariable("PATH", originalPath);
+                AzCommand.ClearCachedAzPath();
+            }
+        }
+        finally
+        {
+            // Clean up the temporary directory
+            if (Directory.Exists(tempDir))
+            {
+                Directory.Delete(tempDir, true);
+            }
+        }
+    }
+
     private sealed class AzResult
     {
         [JsonPropertyName("value")]