My Azure VM sometimes gets deallocated for some reason, so I created a PowerShell script and configure it as a scheduled task to monitor the VM status and start the VM once it is deallocated. The script uses a precreated Azure service principal
to automatically authenticate Azure and Azure PowerShell cmdlets
to detect the VM status, if the status is deallocated
then it calls Start-AzVm
to start the VM, the script execution result is recorded to a log file.
Install Azure PowerShell cmdlets
Azure PowerShell works with PowerShell 6.2.4 and later on all platforms. It is also supported with PowerShell 5.1 on Windows.
Install for Current User for PowerShell core:
Install-Module -Name Az -AllowClobber -Scope CurrentUser
Install for All Users for PowerShell core:
Install-Module -Name Az -AllowClobber -Scope AllUsers
Install for Windows built-in PowerShell 5.1:
Install-Module -Name PowerShellGet -Force
After installation, you can use command Connect-AzAccount
to test whether the installation succeeds.
Create an Azure service principal
Connect-AzAccount
provides interactive sign in experience by default which is not suitable for an automation script. We can create an Azure service principal and do non-interactive sign in using Connect-AzAccount -ServicePrincipal
.
First, run
Connect-AzAccount
to sign in your Azure account interactively.Connect-AzAccount
connects to the default tenant of your Azure account, useGet-AzVm -Name
to test whether it can get the target VM status. If not, you can useSet-AzContext -SubscriptionId
to switch to the tenant of the subscription that contains the target VM.After that, use
New-AzADServicePrincipal
to create an Azure service principal, you can just specify a displayName parameter. Without any other authentication parameters, password-based authentication is used and a random password is created for you. You can use following commands to display the random password.$sp = New-AzADServicePrincipal -DisplayName "vm-powershell-script-svc-principal" $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sp.Secret) $UnsecureSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) $UnsecureSecret
ApplicationId
can be used as username to sign in AzureUnsecureSecret
can be used as password to sign in AzureId
can be used asObjectId
parameter to delete this service principal using command:Remove-AzADServicePrincipal -ObjectId
Sign out current Azure context using
Disconnect-AzAccount
and then use following commands to test whether the Azure service principal we just created can successfully sign in and get the target VM status:$password = ConvertTo-SecureString -String "cf0d6408-6e25-4685-b4b9-84548821e7d1" -AsPlainText -Force $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "970b5ce1-4dee-47f6-b317-9fb4c925e13c", $password Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant "6e30ec6d-xxxx-xxxx-xxxx-xxxxxxxxxxxx" Get-AzVM -Name "joji-ea"
Set username and password as environment variables
It is unsafe to save the username and password in your script, so let's set them as system environment variables.
PowerShell script content
The script logic is quite simple, it just checks whether the VM status is deallocated
, if yes then it starts the VM using Start-AzVm
, it also records the execution result to a log file.
function log($message) {
$global:logContent += "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") $message`r`n"
}
$logFile = "c:\temp\vm.log"
$global:logContent = ""
if (!(Test-Path $logFile)) {
New-Item $logFile -Force
}
else {
$global:logContent = Get-Content $logFile -Raw
}
$password = ConvertTo-SecureString -String $Env:VM_SCRIPT_PWD -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:VM_SCRIPT_APPID, $password
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant "6e30ec6d-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -WarningAction SilentlyContinue
$vm = Get-AzVm -Name "joji-ea" -Status
log("VM PowerState: $($vm.PowerState)");
if ($vm.PowerState -match "deallocated") {
$startvm = Start-AzVm -Name "joji-ea" -ResourceGroupName "linux-rg"
log("Starting VM...`r`nOperationId: $($startvm.OperationId)`r`nStatus: $($startvm.Status)`r`nStartTime: $($startvm.StartTime.ToString())`r`nEndTime: $($startvm.EndTime.ToString())")
$newVMstatus = Get-AzVm -Name "joji-ea" -Status
log("Refreshing... VM PowerState: $($newVMstatus.PowerState)");
}
Set-Content $logFile $global:logContent.TrimEnd()
Configure as a scheduled task
Finally, let's create a scheduled task to run the PowerShell script repeatedly. Assuming the script is saved at "C:\temp\vm-monitor.ps1", let's open PowerShell
first and run C:\temp\vm-monitor.ps1
to ensure the script is executable. The default ExecutionPolicy
of PowerShell script on Windows is Restricted
, running scripts is disabled if you did not set the ExecutionPolicy
before. You need to run PowerShell as administrator and run command Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
to allow running scripts.
Set the task trigger as: At startup and repeat task every 1 minute indefinitely.
The task command is: pwsh -File c:\temp\vm-monitor.ps1
, if you want to use Windows built-in PowerShell, you can change to powershell -File c:\temp\vm-monitor.ps1
.
Some other task settings like: Stop the task if it runs longer than an hour, run a new instance in parallel if previous task is still running.