In this blog post, we'll explore how to configure Azure Functions to execute PowerShell scripts on an Azure Virtual Machine (VM) using Invoke-AzVMRunCommand or Set-AzVMRunCommand with domain user authentication.
Azure Functions provides a powerful platform for automating various tasks within your Azure environment. In this specific scenario, we’ll use Azure Functions to run commands remotely on a VM while authenticating as a domain user, not as a system-assigned or user-assigned managed identity.
Prerequisites
Before we dive into the implementation, ensure you have the following prerequisites:
- Azure Subscription: You'll need an Azure subscription.
- Azure VM: A Windows VM is required where the PowerShell script will be executed.
- Azure Function: You will need an Azure Function with the appropriate configurations to run the PowerShell script.
- Domain User Account: A domain user account for authentication.
- Azure PowerShell Modules: The Azure PowerShell modules (Az) installed to work with Azure resources.
Workflow Diagram: Based on demo script example
Step 1: Setting Up Azure Function with Managed Identity
We'll start by setting up an Azure Function that will handle the execution of commands on a remote Azure VM.
1.1. Create a New Azure Function
- Go to the Azure Portal and navigate to Azure Functions.
- Create a new Function App. This will be your container for the function that will execute the commands.
- Set up the Function App with appropriate configurations such as region, runtime stack (select PowerShell), and storage account.
1.2. Configure Managed Identity
To authenticate the Azure Function to Azure resources like the VM, we’ll use Managed Identity:
- Go to the Identity section of your Function App and enable System Assigned Managed Identity.
- Ensure the Function App has the necessary permissions to interact with the VM and other resources.
- Go to the Azure VM and assign the Managed Identity with the required RBAC roles (e.g., Virtual Machine Contributor).
Step 2: Script for Running PowerShell Commands on Azure VM
Now let’s focus on writing the script that will execute the PowerShell commands remotely on the Azure VM using domain authentication.
2.1. Set Up PowerShell Script for Remote Execution
The PowerShell script to be executed should be capable of running commands like user creation, creating test files and folders, etc. We can use complex script to execute on Azure Windows VM automation tasks except few operations which requires special configuration (drive map, Azure File Share, SMB share).
Below is the script for the task:
#Script to create a folder on Windows VM using Azure Functions with Domain User Authentication. Script name testing.ps1
# Log file path
$logFilePath = "C:\Users\Public\script.log"# Function to log messages to the log file
function Write-Log {
param([string]$message)
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$logMessage = "$timestamp - $message"
Add-Content -Path $logFilePath -Value "$logMessage`r`n"
}Write-Log "Script execution started."
# Authenticate with domain user credentials (you can remove this part if not needed for local script)
$domainUser = "dctest01\test" #replace with your domain user. Use Azure Key Vault for production.
$domainPassword = "xxxxxxx" #replace with your domain user password. Use Azure Key Vault for production.
$securePassword = ConvertTo-SecureString $domainPassword -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential($domainUser, $securePassword)Write-Log "Executing script as user: $domainUser"
# Define the folder path to create
$folderPath = "C:\Demo"# Check if the folder already exists
if (Test-Path -Path $folderPath) {
Write-Log "Folder 'Demo' already exists at $folderPath."
} else {
# Create the folder if it doesn't exist
New-Item -Path $folderPath -ItemType Directory
Write-Log "Folder 'Demo' created at $folderPath."
}Write-Log "Test Ended."
Step 3: Configure Azure Function to Run PowerShell Commands on VM
In the Azure Function, we will use the Set-AzVMRunCommand or Invoke-AzVMRunCommand cmdlets to execute this script on the Azure VM. For production use Azure Key Vault to store sensitive information like subscription Id, username, password.
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Log file path
$logFilePath = "./Invoke-AzVMRunCommand/LogFiles/Scriptlog.log" #Custom logfile to generate logfile of Azure Function execution. This is optional as Function generates logs.
# Function to log messages to the log file
function Write-Log {
param([string]$message)
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$logMessage = "$timestamp - $message"
# Overwrite log file each time (no history kept)
$logMessage | Out-File -FilePath $logFilePath -Append -Force
}
# Start logging all output into the log file
Write-Log "Script execution started."
# Define the Subscription Id directly
$SubscriptionId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Set your Subscription ID here
# Authenticate using System Assigned Managed Identity
$AzureContext = Connect-AzAccount -Identity
# Set the subscription context explicitly
$AzureContext = Set-AzContext -SubscriptionId $SubscriptionId -DefaultProfile $AzureContext
# Check and output the current Azure context
$azContext = Get-AzContext
Write-Log "First Identity CONTEXT AFTER:"
Write-Log ($azContext | Format-Table | Out-String)
# Define VM details and script
$un = 'dctest01\test' # Domain user executing the script. Replace with your domain user.
$pw = 'xxxxxxxxxxxxx' # Password for the domain user #Replace with domain user password. For production use Azure Key Vault
$vmname = 'workernode01-win' #Replace with your Azure Windows VM
# Local Script on Windows VM
$scriptPath = "./Invoke-AzVMRunCommand/testing.ps1" #testing.ps1 PowerShell script, which runs on Azure Windows VM for local commands.
# Define Resource Group Name
$resourceGroupName = "test-ub" #Replace with resource group on Windows VM
# Log the user executing the script
Write-Log "Executing the command as user: $un"
# Delete all previous run commands, except the most recent one
$existingRunCommands = Get-AzVMRunCommand -ResourceGroupName $resourceGroupName -VMName $vmname
# If any run commands exist, remove them except for the latest one
if ($existingRunCommands.Count -gt 0) {
Write-Log "Removing previous run commands..."
$commandsToRemove = $existingRunCommands | Sort-Object -Property CreatedTime -Descending | Select-Object -Skip 1
foreach ($command in $commandsToRemove) {
Remove-AzVMRunCommand -ResourceGroupName $resourceGroupName -VMName $vmname -RunCommandName $command.Name
Write-Log "Removed Run Command: $($command.Name)"
}
}
# Generate a random string to append to the script name (e.g., RunPowerShellScript_A1B2C3)
$randomString = [System.Guid]::NewGuid().ToString("N").Substring(0, 6)
# Define the name for the script to run with the random string appended
$scriptName = "RunPowerShellScript_" + $randomString
Write-Log "Running script: $scriptName"
# Set up and run the VM command using Set-AzVMRunCommand
Set-AzVMRunCommand -ResourceGroupName $resourceGroupName `
-VMName $vmname `
-RunCommandName $scriptName `
-Location "East US" `
-ScriptLocalPath $scriptPath `
-RunAsUser $un `
-RunAsPassword $pw `
-Verbose `
-Debug `
-Confirm:$false
# End logging
Write-Log "End of execution."
# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
$name = $Request.Body.Name
}
$body = "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
if ($name) {
$body = "Hello, $name. This HTTP triggered function executed successfully."
}
# Add-Type for HttpStatusCode
Add-Type -TypeDefinition @"
using System.Net;
"@
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [System.Net.HttpStatusCode]::OK
Body = $body
})
This script will use Set-AzVMRunCommand to run the PowerShell script on the specified VM (workernode01-win) using domain user credentials (dctest01\test).
Important Tweaks:
RunCommand has limitations for maximum number of executions. To solve the maximum number of executions, we are using delete all previous run commands, except the most recent one. By deleting all previous RunCommand , it will force Azure Windows VM to install the agent again, which will take some time. Run Command execution should generate unique command name which can be performed by generating random string and append with RunCommand name.
Step 4: Output and Logs
The function will execute the script and return the output, including success or failure logs. The script logs the following:
- Start and end of the script execution.
- Which domain user is executing the script.
- Any errors or successful actions such as mapping drives or creating files.
Step 5: Feature and Limitations for Run Commands
Microsoft document: RunCommand
Note
The maximum number of allowed Managed Run Commands is currently limited to 25. For certain tasks related to SMB, Azure File Share or Network drive mapping does not work properly on GUI or Windows Remote Desktop Session.
Step 6: Troubleshooting RunCommand
RunCommand execution can be tracked on Azure Windows VM. Logfiles are generated under C:\WindowsAzure\Logs\Plugins\Microsoft.CPlat.Core.RunCommandHandlerWindows\2.0.14 (Check for latest version path based on version)
RunCommandHandler.log Can be used to trace Azure Function execution and PowerShell script on Azure Windows VM.
Conclusion
With this implementation, you can automate the execution of PowerShell scripts on your Azure VMs using Azure Functions. The domain user authentication allows for secure access to Azure resources while also providing a log file for debugging and auditing purposes. Using Set-AzVMRunCommand and Invoke-AzVMRunCommand, we can execute the script and interact with Azure VMs remotely, all while logging relevant details for transparency and monitoring. Make sure check feature support for both commands for features and limitations.
Updated Mar 20, 2025
Version 5.0SaswatMohanty
Microsoft
Joined December 07, 2020
Apps on Azure Blog
Follow this blog board to get notified when there's new activity