Automating Azure VM Size Changes with PowerShell and CSV

Table of contents
- Lets understand with why Azure VM size needs to be changed and how many types of VM family are there in the Azure cloud
- Cost Savings with Azure VM Size Changes
- Azure VM Families
- By selecting the appropriate VM family and size for your workloads, you can optimize performance and cost efficiency.
- Script Overview
- Detailed Explanation
- Integration with Jenkins Pipeline -
- Prerequisites
- Step-by-Step Guide
- Explanation of the Jenkins Pipeline Script
- Conclusion

Lets understand with why Azure VM size needs to be changed and how many types of VM family are there in the Azure cloud
Cost Savings with Azure VM Size Changes
Changing the size of your Azure Virtual Machines (VMs) can lead to significant cost savings, especially if your current VM sizes are over-provisioned for your workloads. By resizing VMs to more appropriate sizes, you can optimize resource usage and reduce costs.
Example of Cost Savings
Let's consider an example where you have a VM running with the Standard_D4s_v3 size, which costs approximately $0.20 per hour. If your workload doesn't require such high specifications, you could resize it to a Standard_B2s size, which costs around $0.05 per hour.
Standard_D4s_v3: $0.20 per hour
Standard_B2s: $0.05 per hour
If the VM runs 24/7 for a month (730 hours), the cost difference would be:
Standard_D4s_v3: $0.20 * 730 = $146 per month
Standard_B2s: $0.05 * 730 = $36.50 per month
By resizing the VM, you save:
- Savings: $146 - $36.50 = $109.50 per month
This example demonstrates how resizing VMs to more appropriate sizes can lead to substantial cost savings.
Azure VM Families
Azure offers a variety of VM families, each optimized for different types of workloads. Here are some of the main VM families:
General Purpose:
Balanced CPU-to-memory ratio.
Suitable for testing and development, small to medium databases, and low to medium traffic web servers.
Examples: B-series, D-series, Av2-series.
Compute Optimized:
High CPU-to-memory ratio.
Ideal for medium traffic web servers, network appliances, batch processes, and application servers.
Examples: F-series.
Memory Optimized:
High memory-to-CPU ratio.
Great for relational database servers, medium to large caches, and in-memory analytics.
Examples: E-series, M-series.
Storage Optimized:
High disk throughput and IO.
Suitable for big data, SQL, and NoSQL databases.
Examples: L-series.
GPU:
Specialized VMs with Graphics Processing Units (GPUs).
Ideal for heavy graphics rendering, video editing, and model training for AI and machine learning.
Examples: NC-series, NV-series, ND-series.
High Performance Compute (HPC):
Designed for high-performance computing workloads.
Suitable for molecular modeling, fluid dynamics, and other compute-intensive applications.
Examples: H-series, HB-series, HC-series.
By selecting the appropriate VM family and size for your workloads, you can optimize performance and cost efficiency.
Automating Azure VM Size Changes with PowerShell and CSV
Managing Azure Virtual Machines (VMs) can be a complex task, especially when you need to update the size of multiple VMs across different subscriptions. In this blog post, we'll walk through a PowerShell script that automates the process of changing VM sizes using a CSV file for input. This script leverages Azure Service Principal credentials for authentication and ensures robust error handling.
Prerequisites
Before we dive into the script, make sure you have the following:
Azure subscription ID
Resource group name
Path to a CSV file containing VM names and their desired sizes
Service Principal credentials (tenant ID, client ID, and client secret)
The PowerShell Script
# Parameters
$subscriptionId = "your-subscription-id"
$resourceGroupName = "your-resource-group-name"
$csvFilePath = "path-to-your-csv-file" # Path to the CSV file
# Service Principal credentials
$tenantId = "your-tenant-id"
$clientId = "your-client-id"
$clientSecret = "your-client-secret"
# Login to Azure using SPN
$secureClientSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($clientId, $secureClientSecret)
Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential
try {
# Validate the subscription
$subscription = Get-AzSubscription -SubscriptionId $subscriptionId
if (-not $subscription) {
Write-Host "Invalid subscription ID: $subscriptionId"
exit
} else {
Write-Host "Subscription ID validated: $subscriptionId"
}
# Set the subscription
Set-AzContext -SubscriptionId $subscriptionId
# Read the CSV file
try {
$vmList = Import-Csv -Path $csvFilePath
} catch {
Write-Host "Error reading CSV file: $_"
exit
}
foreach ($vm in $vmList) {
$vmName = $vm.VMName
$newVMSize = $vm.NewVMSize
# Validate CSV data
if (-not $vmName -or -not $newVMSize) {
Write-Host "Invalid data in CSV file for VM: $vmName. Skipping this entry."
continue
}
try {
# Validate the VM name
$vmObject = Get-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -ErrorAction SilentlyContinue
if (-not $vmObject) {
Write-Host "Invalid VM name: $vmName"
continue
} else {
Write-Host "VM name validated: $vmName"
}
# Validate the new VM size
$availableSizes = Get-AzVMSize -ResourceGroupName $resourceGroupName -VMName $vmName
if ($availableSizes.Name -notcontains $newVMSize) {
Write-Host "Invalid VM size: $newVMSize for VM: $vmName. Please choose a valid size."
continue
} else {
Write-Host "VM size validated: $newVMSize for VM: $vmName"
}
# Check the VM state
$vmState = (Get-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -Status).Statuses[1].Code
if ($vmState -eq "PowerState/running") {
# Stop the VM if it is running
Stop-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -Force
Write-Host "VM $vmName was running and has been stopped."
} elseif ($vmState -eq "PowerState/deallocated") {
Write-Host "VM $vmName is already stopped."
}
# Update the VM size
$vmObject.HardwareProfile.VmSize = $newVMSize
Update-AzVM -ResourceGroupName $resourceGroupName -VM $vmObject
# Start the VM if it was running before
if ($vmState -eq "PowerState/running") {
Start-AzVM -ResourceGroupName $resourceGroupName -Name $vmName
Write-Host "VM $vmName has been started with the new size $newVMSize."
} else {
Write-Host "VM $vmName size changed to $newVMSize but remains stopped."
}
} catch {
Write-Host "An error occurred with VM: $vmName - $_"
}
}
} catch {
Write-Host "An error occurred: $_"
}
Steps Explained
Login to Azure using SPN: Authenticate your Azure account using Service Principal credentials.
Validate Subscription: Check if the provided subscription ID is valid. If not, exit the script with an error message. If valid, proceed.
Set Subscription: Switch to the appropriate subscription.
Read CSV File: Import the CSV file containing VM names and their desired sizes. If there's an error reading the file, catch the exception and exit the script.
Loop Through VMs: Iterate through each VM in the CSV file.
Validate CSV Data: Check if the VM name and new VM size are present in the CSV file. If not, skip the entry.
Validate VM Name: Check if each VM name exists in the specified resource group. If not, continue to the next VM.
Validate New VM Size: Check if the new VM size is available for each VM. If not, continue to the next VM.
Check VM State: Determine if the VM is running or stopped.
If the VM is running, stop it.
If the VM is already stopped, proceed without stopping.
Update VM Size: Retrieve each VM, change its size, and update the VM configuration.
Start VM if Necessary: If the VM was running before, start it again. If it was already stopped, leave it stopped.
Error Handling: Use
try-catch
blocks to handle any errors that occur during the process and display appropriate error messages for each VM and the CSV file.
Script Overview
This script is designed to automate the process of changing the size of multiple Azure VMs using a CSV file for input. It uses Service Principal credentials for authentication and includes robust error handling.
Detailed Explanation
Parameters
# Parameters
$subscriptionId = "your-subscription-id"
$resourceGroupName = "your-resource-group-name"
$csvFilePath = "path-to-your-csv-file" # Path to the CSV file
# Service Principal credentials
$tenantId = "your-tenant-id"
$clientId = "your-client-id"
$clientSecret = "your-client-secret"
$subscriptionId: The ID of the Azure subscription where the VMs are located.
$resourceGroupName: The name of the resource group containing the VMs.
$csvFilePath: The path to the CSV file that lists the VM names and their desired sizes.
Service Principal credentials: These are used to authenticate with Azure.
Login to Azure using SPN
$secureClientSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($clientId, $secureClientSecret)
Connect-AzAccount -ServicePrincipal -TenantId $tenantId -Credential $credential
ConvertTo-SecureString: Converts the client secret to a secure string.
New-Object System.Management.Automation.PSCredential: Creates a credential object using the client ID and secure client secret.
Connect-AzAccount: Authenticates to Azure using the Service Principal credentials.
Validate the Subscription
try {
$subscription = Get-AzSubscription -SubscriptionId $subscriptionId
if (-not $subscription) {
Write-Host "Invalid subscription ID: $subscriptionId"
exit
} else {
Write-Host "Subscription ID validated: $subscriptionId"
}
Get-AzSubscription: Retrieves the subscription details.
Validation: Checks if the subscription ID is valid. If not, it exits the script with an error message.
Set the Subscription
Set-AzContext -SubscriptionId $subscriptionId
- Set-AzContext: Sets the Azure context to the specified subscription.
Read the CSV File
try {
$vmList = Import-Csv -Path $csvFilePath
} catch {
Write-Host "Error reading CSV file: $_"
exit
}
Import-Csv: Reads the CSV file and imports the data into a variable.
Error Handling: Catches any errors that occur while reading the CSV file and exits the script with an error message.
Loop Through VMs
foreach ($vm in $vmList) {
$vmName = $vm.VMName
$newVMSize = $vm.NewVMSize
foreach: Iterates through each VM in the CSV file.
$vmName and $newVMSize: Extracts the VM name and desired size from the CSV file.
Validate CSV Data
if (-not $vmName -or -not $newVMSize) {
Write-Host "Invalid data in CSV file for VM: $vmName. Skipping this entry."
continue
}
- Validation: Checks if the VM name and new VM size are present in the CSV file. If not, it skips the entry.
Validate the VM Name
try {
$vmObject = Get-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -ErrorAction SilentlyContinue
if (-not $vmObject) {
Write-Host "Invalid VM name: $vmName"
continue
} else {
Write-Host "VM name validated: $vmName"
}
Get-AzVM: Retrieves the VM details.
Validation: Checks if the VM name exists in the specified resource group. If not, it skips the entry.
Validate the New VM Size
$availableSizes = Get-AzVMSize -ResourceGroupName $resourceGroupName -VMName $vmName
if ($availableSizes.Name -notcontains $newVMSize) {
Write-Host "Invalid VM size: $newVMSize for VM: $vmName. Please choose a valid size."
continue
} else {
Write-Host "VM size validated: $newVMSize for VM: $vmName"
}
Get-AzVMSize: Retrieves the available sizes for the VM.
Validation: Checks if the new VM size is available. If not, it skips the entry.
Check VM State
$vmState = (Get-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -Status).Statuses[1].Code
if ($vmState -eq "PowerState/running") {
Stop-AzVM -ResourceGroupName $resourceGroupName -Name $vmName -Force
Write-Host "VM $vmName was running and has been stopped."
} elseif ($vmState -eq "PowerState/deallocated") {
Write-Host "VM $vmName is already stopped."
}
Get-AzVM -Status: Retrieves the current state of the VM.
Stop-AzVM: Stops the VM if it is running.
Logging: Logs the state of the VM.
Update the VM Size
$vmObject.HardwareProfile.VmSize = $newVMSize
Update-AzVM -ResourceGroupName $resourceGroupName -VM $vmObject
- Update-AzVM: Updates the VM size.
Start VM if Necessary
if ($vmState -eq "PowerState/running") {
Start-AzVM -ResourceGroupName $resourceGroupName -Name $vmName
Write-Host "VM $vmName has been started with the new size $newVMSize."
} else {
Write-Host "VM $vmName size changed to $newVMSize but remains stopped."
}
} catch {
Write-Host "An error occurred with VM: $vmName - $_"
}
}
} catch {
Write-Host "An error occurred: $_"
}
Start-AzVM: Starts the VM if it was running before.
Logging: Logs the result of the size change.
Error Handling: Catches any errors that occur during the process and logs them.
Integration with Jenkins Pipeline -
Integrating your PowerShell script with a Jenkins Pipeline can streamline your automation process and make it easier to manage and execute your scripts. Here's a step-by-step guide to help you integrate the provided PowerShell script into a Jenkins Pipeline.
Prerequisites
Jenkins Installed: Ensure you have Jenkins installed and running.
PowerShell Plugin: Install the PowerShell plugin for Jenkins.
Service Principal Credentials: Ensure you have the necessary Service Principal credentials for Azure authentication.
Step-by-Step Guide
1. Install the PowerShell Plugin
Go to Manage Jenkins > Manage Plugins.
Search for PowerShell Plugin and install it.
2. Create a New Pipeline Job
Go to New Item in Jenkins.
Enter a name for your job and select Pipeline.
Click OK to create the job.
3. Configure the Pipeline Script
In the Pipeline section, select Pipeline script.
Enter the following Groovy script to integrate your PowerShell script:
pipeline {
agent any
environment {
AZURE_SUBSCRIPTION_ID = "your-subscription-id"
AZURE_RESOURCE_GROUP = "your-resource-group-name"
CSV_FILE_PATH = "path-to-your-csv-file"
AZURE_TENANT_ID = "your-tenant-id"
AZURE_CLIENT_ID = "your-client-id"
AZURE_CLIENT_SECRET = "your-client-secret"
}
stages {
stage('Run PowerShell Script') {
steps {
script {
def psScript = """
# Parameters
\$subscriptionId = "\${AZURE_SUBSCRIPTION_ID}"
\$resourceGroupName = "\${AZURE_RESOURCE_GROUP}"
\$csvFilePath = "\${CSV_FILE_PATH}"
# Service Principal credentials
\$tenantId = "\${AZURE_TENANT_ID}"
\$clientId = "\${AZURE_CLIENT_ID}"
\$clientSecret = "\${AZURE_CLIENT_SECRET}"
# Login to Azure using SPN
\$secureClientSecret = ConvertTo-SecureString \$clientSecret -AsPlainText -Force
\$credential = New-Object System.Management.Automation.PSCredential (\$clientId, \$secureClientSecret)
Connect-AzAccount -ServicePrincipal -TenantId \$tenantId -Credential \$credential
try {
# Validate the subscription
\$subscription = Get-AzSubscription -SubscriptionId \$subscriptionId
if (-not \$subscription) {
Write-Host "Invalid subscription ID: \$subscriptionId"
exit
} else {
Write-Host "Subscription ID validated: \$subscriptionId"
}
# Set the subscription
Set-AzContext -SubscriptionId \$subscriptionId
# Read the CSV file
try {
\$vmList = Import-Csv -Path \$csvFilePath
} catch {
Write-Host "Error reading CSV file: \$_"
exit
}
foreach (\$vm in \$vmList) {
\$vmName = \$vm.VMName
\$newVMSize = \$vm.NewVMSize
# Validate CSV data
if (-not \$vmName -or -not \$newVMSize) {
Write-Host "Invalid data in CSV file for VM: \$vmName. Skipping this entry."
continue
}
try {
# Validate the VM name
\$vmObject = Get-AzVM -ResourceGroupName \$resourceGroupName -Name \$vmName -ErrorAction SilentlyContinue
if (-not \$vmObject) {
Write-Host "Invalid VM name: \$vmName"
continue
} else {
Write-Host "VM name validated: \$vmName"
}
# Validate the new VM size
\$availableSizes = Get-AzVMSize -ResourceGroupName \$resourceGroupName -VMName \$vmName
if (\$availableSizes.Name -notcontains \$newVMSize) {
Write-Host "Invalid VM size: \$newVMSize for VM: \$vmName. Please choose a valid size."
continue
} else {
Write-Host "VM size validated: \$newVMSize for VM: \$vmName"
}
# Check the VM state
\$vmState = (Get-AzVM -ResourceGroupName \$resourceGroupName -Name \$vmName -Status).Statuses[1].Code
if (\$vmState -eq "PowerState/running") {
# Stop the VM if it is running
Stop-AzVM -ResourceGroupName \$resourceGroupName -Name \$vmName -Force
Write-Host "VM \$vmName was running and has been stopped."
} elseif (\$vmState -eq "PowerState/deallocated") {
Write-Host "VM \$vmName is already stopped."
}
# Update the VM size
\$vmObject.HardwareProfile.VmSize = \$newVMSize
Update-AzVM -ResourceGroupName \$resourceGroupName -VM \$vmObject
# Start the VM if it was running before
if (\$vmState -eq "PowerState/running") {
Start-AzVM -ResourceGroupName \$resourceGroupName -Name \$vmName
Write-Host "VM \$vmName has been started with the new size \$newVMSize."
} else {
Write-Host "VM \$vmName size changed to \$newVMSize but remains stopped."
}
} catch {
Write-Host "An error occurred with VM: \$vmName - \$_"
}
}
} catch {
Write-Host "An error occurred: \$_"
}
"""
powershell script: psScript
}
}
}
}
}
Explanation of the Jenkins Pipeline Script
Pipeline Block: Defines the Jenkins Pipeline.
Environment Variables: Sets environment variables for the Azure subscription, resource group, CSV file path, and Service Principal credentials.
Stages Block: Defines the stages of the pipeline.
Run PowerShell Script Stage: Executes the PowerShell script.
script Block: Contains the PowerShell script to be executed.
powershell Step: Runs the PowerShell script within the Jenkins Pipeline.
By integrating your PowerShell script with Jenkins Pipeline, you can automate the process of changing Azure VM sizes efficiently. This setup ensures that your script runs in a controlled environment with proper error handling and logging.
Conclusion
This PowerShell script provides a robust and automated way to manage Azure VM sizes using a CSV file for input. By leveraging Service Principal credentials, it ensures secure and efficient authentication. The script includes comprehensive error handling to manage various scenarios, making it a reliable tool for Azure administrators.
Make sure to replace the placeholder values with your actual subscription ID, resource group name, path to the CSV file, and Service Principal credentials. Happy scripting!
If you have any questions or need further assistance, feel free to leave a comment below.
Subscribe to my newsletter
Read articles from Jaydeep Dutta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Jaydeep Dutta
Jaydeep Dutta
Proficient and skilled Cloud and DevOps Engineer with 6+ years of hands-on experience Cloud Infra, automating, orchestrating end to end Containerized application deployment in Cloud environment (Azure, AWS) , with Docker, Kubernetes, Ansible, Terraform IAC, Jenkins CI/CD, and DevOps methodologies and Automating Cloud solutions with Boto3 and Architecting cloud service based solutions