Search code examples
azurevirtual-machineazureportal

Can I add a start/stop button on my Azure Dashboard for virtual machines using Azure Resource Graph query?


I have a Dashboard on my Azure Portal with a tile describing one of my resource group virtual machines.

I would like to add a column to quick start and stop a machine directly from the Dashboard but I could not find any query matching my need.

I already managed to display a link to the resource and the power status to know if the machine is on or off using this query :

resources
| where type == "microsoft.compute/virtualmachines"
| project id, PowerState = tostring(properties.extended.instanceView.powerState.displayStatus)
| order by PowerState desc

Solution

  • Can I add a start/stop button on my Azure Dashboard for virtual machines using Azure Resource Graph query?

    I agree with Peter Bons you cannot start or stop the VM with an Azure Resource Graph query.

    To stop or start the VM from the Azure dashboard, you can use an Azure Function App by following these steps

    1. Create a Function App and deploy the function using the steps below. Refer to the Microsoft documentation for creating a Function App.

    2. Here is the function code to start the VM

    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Azure.Identity;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
     
    namespace FunctionApp173
    {
        public class Function1
        {
            private readonly ILogger<Function1> _logger;
            private static readonly HttpClient HttpClient = new HttpClient();
     
            public Function1(ILogger<Function1> logger)
            {
                _logger = logger;
            }
     
            [Function("Function1")]
            public async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
                FunctionContext executionContext)
            {
                _logger.LogInformation("C# HTTP trigger function processed a request.");
     
                try
                {
                    // Get the access token using DefaultAzureCredential
                    string token = await GetAccessTokenAsync("https://management.azure.com/");
     
                    // URL to start the VM
                    string url = "https://management.azure.com/subscriptions/158b8345-c359-4d98-95c5-f21815dd048f/resourceGroups/Venkat/providers/Microsoft.Compute/virtualMachines/venkat-vm/start?api-version=2024-03-01";
     
                    // Send POST request to start the VM
                    await SendPostRequestAsync(url, token);
                    _logger.LogInformation("VM Started Successfully");
     
                    // Create response
                    var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
                    await response.WriteStringAsync("VM Started Successfully");
                    return response;
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Failed to Start VM: {ex.Message}");
     
                    // Create response
                    var response = req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
                    await response.WriteStringAsync($"Failed to Start VM: {ex.Message}");
                    return response;
                }
            }
     
            private static async Task<string> GetAccessTokenAsync(string resourceUrl)
            {
                var credential = new DefaultAzureCredential();
                var tokenRequestContext = new Azure.Core.TokenRequestContext(new[] { resourceUrl + "/.default" });
                var accessToken = await credential.GetTokenAsync(tokenRequestContext);
                return accessToken.Token;
            }
     
            private static async Task SendPostRequestAsync(string url, string token)
            {
                HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                var response = await HttpClient.PostAsync(url, null);
                response.EnsureSuccessStatusCode();
            }
        }
    }
    

    Stop the VM

    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Azure.Identity;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    
    namespace FunctionApp173
    {
        public class Function1
        {
            private readonly ILogger<Function1> _logger;
            private static readonly HttpClient HttpClient = new HttpClient();
    
            public Function1(ILogger<Function1> logger)
            {
                _logger = logger;
            }
    
            [Function("Function1")]
            public async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
                FunctionContext executionContext)
            {
                _logger.LogInformation("C# HTTP trigger function processed a request.");
    
                try
                {
                    // Get the access token using DefaultAzureCredential
                    string token = await GetAccessTokenAsync("https://management.azure.com/");
    
                    // URL to stop the VM
                    string url = "https://management.azure.com/subscriptions/158b8345-c359-4d98-95c5-f21815dd048f/resourceGroups/Venkat/providers/Microsoft.Compute/virtualMachines/venkat-vm/deallocate?api-version=2024-03-01";
    
                    // Send POST request to stop the VM
                    await SendPostRequestAsync(url, token);
                    _logger.LogInformation("VM Stopped Successfully");
    
                    // Create response
                    var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
                    await response.WriteStringAsync("VM Stopped Successfully");
                    return response;
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Failed to Stop VM: {ex.Message}");
    
                    // Create response
                    var response = req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
                    await response.WriteStringAsync($"Failed to Stop VM: {ex.Message}");
                    return response;
                }
            }
    
            private static async Task<string> GetAccessTokenAsync(string resourceUrl)
            {
                var credential = new DefaultAzureCredential();
                var tokenRequestContext = new Azure.Core.TokenRequestContext(new[] { resourceUrl + "/.default" });
                var accessToken = await credential.GetTokenAsync(tokenRequestContext);
                return accessToken.Token;
            }
    
            private static async Task SendPostRequestAsync(string url, string token)
            {
                HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                var response = await HttpClient.PostAsync(url, null);
                response.EnsureSuccessStatusCode();
            }
        }
    }
    
    1. Once you deploy the function to the Function App, open the function, select Get Function URL, and copy the second URL.

    enter image description here

    enter image description here

    1. Enable Identity on the Function App and assign the Virtual Machine Administrator role to the Identity to Stop/Start the VM

    enter image description here

    1. Now create a dashboard with the function URL link. Go to Dashboard > Create a dashboard > Custom > Markdown.

    enter image description here

    Now you can start the VM from the dashboard

    enter image description here