如何在.NET Core后端中调用PowerShell脚本?附Azure虚拟机构建场景Workshop解决方案
Hey there! Let's tackle your two related needs step by step—first, how to call PowerShell scripts from a .NET Core backend, then we'll build a complete Workshop-ready solution for deploying Azure VMs with a button click using templates. Let's get into it!
First, you'll need the right NuGet package to interact with PowerShell in .NET Core. Grab the Microsoft.PowerShell.SDK package—it's cross-platform and officially supported, so it works seamlessly on Windows, Linux, and macOS.
基础同步调用示例
Here's a straightforward snippet that runs a PowerShell script file and captures both output and errors:
using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; public class PowerShellExecutor { public string RunScript(string scriptPath, params string[] arguments) { // Create a runspace to host PowerShell execution using var runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); // Initialize a PowerShell instance using var ps = PowerShell.Create(); ps.Runspace = runspace; // Add the script file and any required arguments ps.AddCommand(scriptPath); foreach (var arg in arguments) { ps.AddArgument(arg); } // Execute the script and collect results var results = ps.Invoke(); var outputBuilder = new StringBuilder(); // Process successful output foreach (var result in results) { outputBuilder.AppendLine(result.ToString()); } // Capture and append any errors if (ps.Streams.Error.Count > 0) { outputBuilder.AppendLine("=== Errors Encountered ==="); foreach (var error in ps.Streams.Error) { outputBuilder.AppendLine(error.ToString()); } } return outputBuilder.ToString(); } }
异步版本(适合Web场景)
For web backends, async execution is critical to avoid blocking threads. Here's how to adjust the code for async/await:
public async Task<string> RunScriptAsync(string scriptPath, params string[] arguments) { using var runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); using var ps = PowerShell.Create(); ps.Runspace = runspace; ps.AddCommand(scriptPath); foreach (var arg in arguments) { ps.AddArgument(arg); } // Execute asynchronously var results = await ps.InvokeAsync().ConfigureAwait(false); var outputBuilder = new StringBuilder(); // Same output/error handling as before foreach (var result in results) { outputBuilder.AppendLine(result.ToString()); } if (ps.Streams.Error.Count > 0) { outputBuilder.AppendLine("=== Errors Encountered ==="); foreach (var error in ps.Streams.Error) { outputBuilder.AppendLine(error.ToString()); } } return outputBuilder.ToString(); }
Key Gotchas to Remember
- Execution Policy: On Windows, you may need to run
Set-ExecutionPolicy RemoteSigned(as admin) to allow script execution. On Linux/macOS, grant execute permissions withchmod +x your-script.ps1. - Security: Never hardcode sensitive data (like Azure credentials) in scripts or .NET code—use environment variables or Azure Key Vault instead.
- Cross-Platform Compatibility: Ensure your PowerShell scripts avoid Windows-specific cmdlets if you're deploying to Linux/macOS backends.
Now let's build the end-to-end scenario: a button click triggers a .NET Core API, which runs a PowerShell script to deploy an Azure VM using an ARM template. This is perfect for a Workshop where participants can learn infrastructure-as-code workflows hands-on.
整体架构
- Frontend: Simple HTML page with a button that calls the .NET API via JavaScript.
- Backend: .NET Core Web API endpoint that invokes the PowerShell deployment script.
- PowerShell Script: Uses Azure PowerShell (
Azmodule) to authenticate with Azure and deploy the ARM template. - Azure Setup: A service principal with permissions to create VMs, and a reusable ARM template for VM deployment.
Step 1: Prepare Azure Resources & ARM Template
First, create a minimal ARM template (vm-template.json) to define your VM and related resources:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "vmName": { "type": "string", "defaultValue": "WorkshopVM" }, "adminUsername": { "type": "string", "defaultValue": "workshopadmin" }, "adminPassword": { "type": "securestring" }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]" } }, "resources": [ { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2023-07-01", "name": "[parameters('vmName')]", "location": "[parameters('location')]", "properties": { "hardwareProfile": { "vmSize": "Standard_D2s_v3" }, "osProfile": { "computerName": "[parameters('vmName')]", "adminUsername": "[parameters('adminUsername')]", "adminPassword": "[parameters('adminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "MicrosoftWindowsServer", "offer": "WindowsServer", "sku": "2022-Datacenter", "version": "latest" }, "osDisk": { "createOption": "FromImage" } }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(parameters('vmName'), '-nic'))]" } ] } } }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-05-01", "name": "[concat(parameters('vmName'), '-nic')]", "location": "[parameters('location')]", "properties": { "ipConfigurations": [ { "name": "ipconfig1", "properties": { "privateIPAllocationMethod": "Dynamic", "subnet": { "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', concat(parameters('vmName'), '-vnet'), 'default')]" } } } ] } }, { "type": "Microsoft.Network/virtualNetworks", "apiVersion": "2023-05-01", "name": "[concat(parameters('vmName'), '-vnet')]", "location": "[parameters('location')]", "properties": { "addressSpace": { "addressPrefixes": ["10.0.0.0/16"] }, "subnets": [{ "name": "default", "properties": { "addressPrefix": "10.0.0.0/24" } }] } } ] }
Next, create an Azure service principal for automated authentication (run this in PowerShell):
az ad sp create-for-rbac --name "WorkshopVMDeploySP" --role "Contributor" --scopes "/subscriptions/<YOUR_SUBSCRIPTION_ID>"
Save the output (tenant ID, client ID, client secret) — we'll use these in our .NET app.
Step 2: Write the PowerShell Deployment Script
Create Deploy-VM.ps1 to handle Azure authentication and template deployment:
param( [string]$SubscriptionId, [string]$TenantId, [string]$ClientId, [string]$ClientSecret, [string]$ResourceGroupName, [string]$VmName, [string]$AdminPassword, [string]$Location = "eastus" ) # Authenticate with the service principal $secureSecret = ConvertTo-SecureString $ClientSecret -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential ($ClientId, $secureSecret) Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $TenantId -Subscription $SubscriptionId # Create resource group if it doesn't exist if (-not (Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue)) { New-AzResourceGroup -Name $ResourceGroupName -Location $Location } # Deploy the ARM template New-AzResourceGroupDeployment ` -ResourceGroupName $ResourceGroupName ` -TemplateFile "./vm-template.json" ` -vmName $VmName ` -adminPassword $AdminPassword ` -Location $Location Write-Output "✅ VM deployment initiated successfully for $VmName"
Step 3: Build the .NET Core Web API
Create a new .NET Core Web API project, then add a controller to handle deployment requests:
using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/[controller]")] public class VmDeploymentController : ControllerBase { private readonly IConfiguration _config; private readonly PowerShellExecutor _psExecutor; public VmDeploymentController(IConfiguration config) { _config = config; _psExecutor = new PowerShellExecutor(); } [HttpPost("deploy")] public async Task<IActionResult> DeployVm([FromBody] VmDeploymentRequest request) { try { // Pull Azure credentials from config (use environment variables in production!) var subscriptionId = _config["Azure:SubscriptionId"]; var tenantId = _config["Azure:TenantId"]; var clientId = _config["Azure:ClientId"]; var clientSecret = _config["Azure:ClientSecret"]; // Prepare script arguments var args = new[] { subscriptionId, tenantId, clientId, clientSecret, request.ResourceGroupName, request.VmName, request.AdminPassword, request.Location }; // Run the deployment script var result = await _psExecutor.RunScriptAsync("./Deploy-VM.ps1", args); return Ok(new { Status = "Success", Details = result }); } catch (Exception ex) { return BadRequest(new { Status = "Failed", Error = ex.Message }); } } } // Request model to accept frontend input public class VmDeploymentRequest { public string ResourceGroupName { get; set; } public string VmName { get; set; } public string AdminPassword { get; set; } public string Location { get; set; } = "eastus"; }
Add Azure credentials to appsettings.json (replace placeholders with your service principal details):
{ "Azure": { "SubscriptionId": "YOUR_SUBSCRIPTION_ID", "TenantId": "YOUR_TENANT_ID", "ClientId": "YOUR_CLIENT_ID", "ClientSecret": "YOUR_CLIENT_SECRET" } }
Step 4: Create a Simple Frontend
Build a basic HTML page (index.html) for participants to interact with:
<!DOCTYPE html> <html> <head> <title>Workshop VM Deployer</title> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; } .form-group { margin-bottom: 1rem; } button { padding: 0.5rem 1rem; background: #0078d4; color: white; border: none; border-radius: 4px; cursor: pointer; } #result { margin-top: 1rem; padding: 1rem; border-radius: 4px; } .success { background: #d4edda; color: #155724; } .error { background: #f8d7da; color: #721c24; } </style> </head> <body> <h1>Deploy Your Workshop VM</h1> <div class="form-group"> <label>Resource Group Name:</label> <input type="text" id="rgName" value="WorkshopRG"> </div> <div class="form-group"> <label>VM Name:</label> <input type="text" id="vmName" value="MyWorkshopVM"> </div> <div class="form-group"> <label>Admin Password:</label> <input type="password" id="adminPassword" placeholder="Enter a strong password"> </div> <div class="form-group"> <label>Location:</label> <input type="text" id="location" value="eastus"> </div> <button onclick="deployVm()">Deploy VM</button> <div id="result"></div> <script> async function deployVm() { const resultDiv = document.getElementById('result'); resultDiv.textContent = "Starting deployment..."; resultDiv.className = ""; const requestData = { resourceGroupName: document.getElementById('rgName').value, vmName: document.getElementById('vmName').value, adminPassword: document.getElementById('adminPassword').value, location: document.getElementById('location').value }; try { const response = await fetch('/api/vmdeployment/deploy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); const data = await response.json(); if (response.ok) { resultDiv.innerHTML = `<strong>Success!</strong><br>${data.Details}`; resultDiv.className = "success"; } else { resultDiv.innerHTML = `<strong>Error!</strong><br>${data.Error}`; resultDiv.className = "error"; } } catch (ex) { resultDiv.innerHTML = `<strong>Error!</strong><br>Failed to connect to API: ${ex.message}`; resultDiv.className = "error"; } } </script> </body> </html>
Step 5: Workshop Setup Tips
- Pre-Requisites: Ask participants to install .NET Core SDK, Azure PowerShell module, and have an Azure subscription.
- Hands-On Tasks: Let participants modify the ARM template (e.g., switch to a Linux image, change VM size) or add more input fields to the frontend.
- Cleanup Script: Provide a quick script to delete resources after the Workshop:
param([string]$ResourceGroupName) Remove-AzResourceGroup -Name $ResourceGroupName -Force -Confirm:$false
内容的提问来源于stack exchange,提问作者MBob

