diff --git a/src/Compute/Compute/Common/ComputeClientBaseCmdlet.cs b/src/Compute/Compute/Common/ComputeClientBaseCmdlet.cs
index db55f9f3a2c2..6c1280eda305 100644
--- a/src/Compute/Compute/Common/ComputeClientBaseCmdlet.cs
+++ b/src/Compute/Compute/Common/ComputeClientBaseCmdlet.cs
@@ -22,6 +22,14 @@
using Microsoft.Azure.Management.Internal.Resources;
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using Newtonsoft.Json;
+using Azure.Identity;
+using Azure.Core;
+using Newtonsoft.Json.Linq;
+using System.Threading.Tasks;
namespace Microsoft.Azure.Commands.Compute
{
@@ -34,6 +42,12 @@ public abstract class ComputeClientBaseCmdlet : AzureRMCmdlet
private ComputeClient computeClient;
+ // Reusable static HttpClient for DryRun posts
+ private static readonly HttpClient _dryRunHttpClient = new HttpClient();
+
+ [Parameter(Mandatory = false, HelpMessage = "Send the invoked PowerShell command (ps_script) and subscription id to a remote endpoint without executing the real operation.")]
+ public SwitchParameter DryRun { get; set; }
+
public ComputeClient ComputeClient
{
get
@@ -54,9 +68,293 @@ public ComputeClient ComputeClient
public override void ExecuteCmdlet()
{
StartTime = DateTime.Now;
+
+ // Intercept early if DryRun requested
+ if (DryRun.IsPresent && TryHandleDryRun())
+ {
+ return;
+ }
base.ExecuteCmdlet();
}
+ ///
+ /// Handles DryRun processing: capture command text and subscription id and POST to endpoint.
+ /// Returns true if DryRun was processed (and normal execution should stop).
+ ///
+ protected virtual bool TryHandleDryRun()
+ {
+ try
+ {
+ string psScript = this.MyInvocation?.Line ?? this.MyInvocation?.InvocationName ?? string.Empty;
+ string subscriptionId = this.DefaultContext?.Subscription?.Id ?? DefaultProfile.DefaultContext?.Subscription?.Id ?? string.Empty;
+
+ var payload = new
+ {
+ ps_script = psScript,
+ subscription_id = subscriptionId,
+ timestamp_utc = DateTime.UtcNow.ToString("o"),
+ source = "Az.Compute.DryRun"
+ };
+
+ // Endpoint + token provided via environment variables to avoid changing all cmdlet signatures
+ string endpoint = Environment.GetEnvironmentVariable("AZURE_POWERSHELL_DRYRUN_ENDPOINT");
+ if (string.IsNullOrWhiteSpace(endpoint))
+ {
+ // Default local endpoint (e.g., local Azure Function) if not provided via environment variable
+ endpoint = "http://localhost:7071/api/what_if_ps_preview";
+ }
+ // Acquire token via Azure Identity (DefaultAzureCredential). Optional scope override via AZURE_POWERSHELL_DRYRUN_SCOPE
+ string token = GetDryRunAuthToken();
+
+ // endpoint is always non-empty now (falls back to local default)
+
+ var dryRunResult = PostDryRun(endpoint, token, payload);
+ if (dryRunResult != null)
+ {
+ // Display the response in a user-friendly format
+ WriteVerbose("========== DryRun Response ==========");
+
+ // Try to pretty-print the JSON response
+ try
+ {
+ string formattedJson = JsonConvert.SerializeObject(dryRunResult, Formatting.Indented);
+ // Only output to pipeline once, not both WriteObject and WriteInformation
+ WriteObject(formattedJson);
+ }
+ catch
+ {
+ // Fallback: just write the object
+ WriteObject(dryRunResult);
+ }
+
+ WriteVerbose("=====================================");
+ }
+ else
+ {
+ WriteWarning("DryRun request completed but no response data was returned.");
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteWarning($"DryRun error: {ex.Message}");
+ }
+ return true; // Always prevent normal execution when -DryRun is used
+ }
+
+ ///
+ /// Posts DryRun payload and returns parsed JSON response or raw string.
+ /// Mirrors Python test_what_if_ps_preview() behavior.
+ ///
+ private object PostDryRun(string endpoint, string bearerToken, object payload)
+ {
+ string json = JsonConvert.SerializeObject(payload);
+ using (var request = new HttpRequestMessage(HttpMethod.Post, endpoint))
+ {
+ request.Content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ // Add Accept header and correlation id like Python script
+ request.Headers.Accept.Clear();
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ string correlationId = Guid.NewGuid().ToString();
+ request.Headers.Add("x-ms-client-request-id", correlationId);
+
+ if (!string.IsNullOrWhiteSpace(bearerToken))
+ {
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
+ }
+
+ WriteVerbose($"DryRun POST -> {endpoint}");
+ WriteVerbose($"DryRun correlation-id: {correlationId}");
+ WriteVerbose($"DryRun Payload: {Truncate(json, 1024)}");
+
+ try
+ {
+ var response = _dryRunHttpClient.SendAsync(request).GetAwaiter().GetResult();
+ string respBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+
+ WriteVerbose($"DryRun HTTP Status: {(int)response.StatusCode} {response.ReasonPhrase}");
+
+ if (response.IsSuccessStatusCode)
+ {
+ WriteVerbose("DryRun post succeeded.");
+ WriteVerbose($"DryRun response body: {Truncate(respBody, 2048)}");
+
+ // Parse JSON and return as object (similar to Python result = response.json())
+ try
+ {
+ var jToken = !string.IsNullOrWhiteSpace(respBody) ? JToken.Parse(respBody) : null;
+ if (jToken != null)
+ {
+ // Enrich with correlation and status
+ if (jToken.Type == JTokenType.Object)
+ {
+ ((JObject)jToken)["_correlation_id"] = correlationId;
+ ((JObject)jToken)["_http_status"] = (int)response.StatusCode;
+ ((JObject)jToken)["_success"] = true;
+ }
+ return jToken.ToObject