Azure-NVMe-Utils/Azure-NVMe-Conversion.ps1 (811 lines of code) (raw):
<#
.SYNOPSIS
Convert Virtual Machines from SCSI to NVMe controller
.DESCRIPTION
The script helps converting Azure Virtual Machines from SCSI to NVMe controller.
This will change the way how disks are presented inside the operating systems.
The script will check if the VM is running Windows or Linux and will run the necessary commands to prepare the operating system for the conversion when specifying the -FixOperatingSystemSettings switch.
.PARAMETER ResourceGroupName:
Name of the resource group where the VM is located
.PARAMETER VMName:
Name of the VM to be converted
.PARAMETER NewControllerType:
Type of controller to be used (NVMe or SCSI)
.PARAMETER VMSize:
Size of the VM to be used
.PARAMETER StartVM:
Start the VM after conversion
.PARAMETER WriteLogfile:
Write log file to disk
.PARAMETER IgnoreSKUCheck:
Ignore SKU check for availability in region/zone
.PARAMETER IgnoreWindowsVersionCheck:
Ignore Windows version check
.PARAMETER FixOperatingSystemSettings:
Fix operating system settings
.INPUTS
None.
.OUTPUTS
Log file with the results of the script execution
The log file will be written to the current directory with the name Azure-NVMe-Conversion-<VMName>-<timestamp>.log when the -WriteLogfile switch is used
.EXAMPLE
PS> .\Azure-NVMe-Conversion.ps1 -ResourceGroupName "myResourceGroup" -VMName "myVM" -NewControllerType NVMe -VMSize "Standard_E4bds_v5" -StartVM -WriteLogfile
.LINK
https://github.com/Azure/SAP-on-Azure-Scripts-and-Utilities
#>
<#
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
#>
[CmdletBinding()]
param (
# Resource Group
[Parameter(Mandatory=$true)][string]$ResourceGroupName,
# VM Name
[Parameter(Mandatory=$true)][string]$VMName,
# Disk Controller Type
[ValidateSet("NVMe", "SCSI")][string]$NewControllerType="NVMe",
# New VM Size
[Parameter(Mandatory=$true)][string]$VMSize,
# Start VM after update
[switch]$StartVM,
# Write Log File
[switch]$WriteLogfile,
# Ignore Check if SKU is available in the region/zone
[switch]$IgnoreSKUCheck,
# Ignore Windows Operating System Version Check
[switch]$IgnoreWindowsVersionCheck,
# Fix operating system settings
[switch]$FixOperatingSystemSettings
)
# function to write log messages
function WriteRunLog {
[CmdletBinding()]
param (
# Message to write to log
[string]$message,
# Category of the message
[string]$category="INFO"
)
# getting offset seconds to start time
$_offset = ((Get-Date) - $script:_starttime).ToString("mm\:ss")
switch ($category) {
"INFO" { $_prestring = "INFO - "
$_color = "Green" }
"WARNING" { $_prestring = "WARNING - "
$_color = "Yellow" }
"ERROR" { $_prestring = "ERROR - "
$_color = "Red" }
"IMPORTANT" { $_prestring = "IMPORTANT - "
$_color = "Blue" }
}
$_runlog_row = "" | Select-Object "Log"
$_runlog_row.Log = [string]$_offset + " - " + [string]$_prestring + [string]$message
$script:_runlog += $_runlog_row
Write-Host $_runlog_row.Log -ForegroundColor $_color
if ($WriteLogfile -and $script:_logfile) {
$_runlog_row.Log | Out-File -FilePath $script:_logfile -Append
}
}
function CheckInstalledModules {
[CmdletBinding()]
param (
# Module Name
[string]$ModuleName,
# Minimum Module Version
[version]$ModuleVersion
)
$_module = Get-Module -ListAvailable -Name $ModuleName
if (-not ($_module)) {
WriteRunLog -message "Module $ModuleName is not installed. Please install the module and run the script again." -category "ERROR"
WriteRunLog -message "Usage this command to install the module:" -category "ERROR"
WriteRunLog -message " Install-Module -Name $ModuleName -Force" -category "ERROR"
exit
}
if ($ModuleVersion -and ($_module | Where-Object {$_.Version -gt $ModuleVersion}).Count -eq 0) {
WriteRunLog -message "Module $ModuleName is installed but the version is lower than required. Please update the module and run the script again." -category "ERROR"
WriteRunLog -message "Usage this command to update the module:" -category "ERROR"
WriteRunLog -message " Update-Module -Name $ModuleName" -category "ERROR"
exit
}
else {
WriteRunLog -message "Module $ModuleName is installed and the version is correct."
}
}
function AskToContinue {
[CmdletBinding()]
param (
# Message to ask for confirmation
[string]$message
)
WriteRunLog -message $message -category "IMPORTANT"
$_answer = Read-Host "Do you want to continue? (Y/N)"
if ($_answer -ne "Y" -and $_answer -ne "y") {
WriteRunLog -message "Script execution aborted by user" -category "ERROR"
exit
}
}
##############################################################################################################
# Main Script
##############################################################################################################
# creating variable for log file
$script:_runlog = @()
$script:_starttime = Get-Date
WriteRunLog -message "Starting script Azure-NVMe-Conversion.ps1"
WriteRunLog -message "Script started at $script:_starttime"
$script:_logfile = "Azure-NVMe-Conversion-$($VMName)-$((Get-Date).ToString('yyyyMMdd-HHmmss')).log"
if ($WriteLogfile) {
WriteRunLog -message "Log file will be written to $script:_logfile"
}
#
WriteRunLog -message "Script parameters:"
foreach ($key in $MyInvocation.BoundParameters.keys)
{
$value = (get-variable $key).Value
WriteRunLog -message " $key -> $value"
}
# Check if breaking change warning is enabled
$_breakingchangewarning = Get-AzConfig -DisplayBreakingChangeWarning
if ($_breakingchangewarning.Value -eq $true) {
Update-AzConfig -DisplayBreakingChangeWarning $false
}
# Check module versions
CheckInstalledModules -ModuleName "Az" -ModuleVersion "11.0"
# Getting Azure Context
try {
$_AzureContext = Get-AzContext
if (!$_AzureContext) {
WriteRunLog -message "Azure Context not found" -category "ERROR"
WriteRunLog -message "Please login to Azure using Connect-AzAccount" -category "ERROR"
exit
}
WriteRunLog -message "Connected to Azure subscription name: $($_AzureContext.Subscription.Name)"
WriteRunLog -message "Connected to Azure subscription ID: $($_AzureContext.Subscription.Id)"
} catch {
WriteRunLog -message "Error getting Azure Context" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Get VM
try {
$_VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
if (-not $_VM) {
WriteRunLog -message "VM $VMName not found in Resource Group $ResourceGroupName" -category "ERROR"
exit
}
WriteRunLog -message "VM $VMName found in Resource Group $ResourceGroupName"
} catch {
WriteRunLog -message "Error getting VM $VMName" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# storing original VM Size
$script:_original_vm_size = $_VM.HardwareProfile.VmSize
# Get VM Power State
try {
$_vminfo = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
# Check if VM is running
if (($_vminfo.Statuses[1].Code -ne "PowerState/running") -and ($NewControllerType -eq "NVMe")) {
WriteRunLog -message "VM $VMName is not running. Please start the VM before running this script." -category "ERROR"
exit
}
} catch {
WriteRunLog -message "Error getting VM status" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Check if VM is running Linux or Windows
if ($_VM.StorageProfile.OsDisk.OsType -eq "Windows") {
$_os = "Windows"
WriteRunLog -message "VM $VMName is running Windows"
if ($_vm.StorageProfile.ImageReference.Publisher -eq "MicrosoftWindowsServer") {
# Check Windows Version of OS
$_osversion = $_VM.StorageProfile.ImageReference.Sku
WriteRunLog -message "Windows Version: $_osversion"
$_osversion_number = $_osversion -replace "[^0-9]", ""
if (-not $IgnoreWindowsVersionCheck) {
if ($_osversion_number -lt 2019) {
WriteRunLog -message "Windows Version is lower than 2019. NVMe controller is only supported on Windows 2019 and higher" -category "ERROR"
exit
}
else {
WriteRunLog -message "Detected Windows Version: $($_osversion_number)"
}
}
else {
WriteRunLog -message "Ignoring Windows Version Check"
WriteRunLog -message "Please make sure that the Windows Server 2019 or higher or Windows 10 1809 or higher is installed on the VM"
}
}
}
else {
$_os = "Linux"
WriteRunLog -message "VM $VMName is running Linux"
}
# Check if VM is running SCSI or NVMe
if ($_VM.StorageProfile.DiskControllerType -eq "SCSI") {
WriteRunLog -message "VM $VMName is running SCSI"
if ($NewControllerType -eq "SCSI") {
WriteRunLog -message "VM $VMName is already running SCSI. No action required."
WriteRunLog -message "If you want to convert to NVMe, please specify -NewControllerType NVMe"
exit
}
}
else {
WriteRunLog -message "VM $VMName is running NVMe"
if ($NewControllerType -eq "NVMe") {
WriteRunLog -message "VM $VMName is already running NVMe. No action required."
WriteRunLog -message "If you want to convert to SCSI, please specify -NewControllerType SCSI"
exit
}
}
# check if VM is running a Gen1 or Gen2 image
try {
$_vm_osdisk = Get-AzDisk -Name $_vm.StorageProfile.OsDisk.Name
if ($_vm_osdisk.HyperVGeneration -eq 'V1') {
WriteRunLog -message "VM $VMName is running a Generation 1 image" -category "ERROR"
WriteRunLog -message "NVMe controller are only supported on Generation 2 images" -category "ERROR"
}
}
catch {
WriteRunLog -message "Error getting VM Generation" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
### trusted launch is supported now
##if ($_VM.SecurityProfile.SecurityType -eq "TrustedLaunch" -and $VMSize.StartsWith("Standard_M")) {
## WriteRunLog -message "VM $VMName is running with Trusted Launch enabled" -category "ERROR"
## WriteRunLog -message "Trusted Launch is not supported with M-Series VMs" -category "ERROR"
## exit
##}
##else {
## if ($_VM.SecurityProfile.SecurityType -eq "TrustedLaunch") {
## WriteRunLog -message "VM $VMName is running Trusted Launch"
## }
## else {
## WriteRunLog -message "VM $VMName is not running Trusted Launch"
## }
##}
# getting authentication token for REST API calls
try {
$access_token = (Get-AzAccessToken).Token
WriteRunLog -message "Authentication token received"
} catch {
WriteRunLog -message "Error getting authentication token" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
if (-not $IgnoreSKUCheck) {
WriteRunLog -message "Getting available SKU resources"
WriteRunLog -message "This might take a while ..."
$_VMSKUs = Get-AzComputeResourceSku | Where-Object { $_.Locations -contains $_vm.Location -and $_.ResourceType.Contains("virtualMachines") }
$_VMSKU = $_VMSKUs | Where-Object { $_.Name -eq $VMSize }
# Check if VM SKU is available in the VM's zone
if ($_VM.Zones -and $_VM.Zones.Count -gt 0) {
$vmZone = $_VM.Zones[0]
if (-not ($_VMSKU.LocationInfo | Where-Object { $_.Zones -contains $vmZone })) {
WriteRunLog -message "VM SKU $VMSize is not available in zone $vmZone" -category "ERROR"
exit
}
else {
WriteRunLog -message "VM SKU $VMSize is available in zone $vmZone"
}
}
# Check if both VMs have or do not have a resource disk
$originalVmHasResourceDisk = ($_VMSKUs | Where-Object { $_.Name -eq $script:_original_vm_size }).Capabilities | Where-Object { $_.Name -eq "EphemeralOSDiskSupported" -and $_.Value -eq "True" }
$newVmHasResourceDisk = ($_VMSKU.Capabilities | Where-Object { $_.Name -eq "EphemeralOSDiskSupported" -and $_.Value -eq "True" })
if (($originalVmHasResourceDisk -and -not $newVmHasResourceDisk) -or (-not $originalVmHasResourceDisk -and $newVmHasResourceDisk)) {
WriteRunLog -message "Mismatch in resource disk support between original VM size ($script:_original_vm_size) and new VM size ($VMSize)." -category "ERROR"
exit
}
else {
WriteRunLog -message "Resource disk support matches between original VM size and new VM size."
}
if ($_VMSKU) {
WriteRunLog -message "Found VM SKU - Checking for Capabilities"
$_supported_controller = ($_VMSKU.Capabilities | Where-Object { $_.Name -eq "DiskControllerTypes" }).Value
if ([string]::IsNullOrEmpty($_supported_controller) -and $NewControllerType -eq "NVMe") {
WriteRunLog -message "VM SKU doesn't have supported capabilities" -category "ERROR"
exit
}
else {
WriteRunLog -message "VM SKU has supported capabilities"
if ($NewControllerType -eq "NVMe") {
# NVMe destination
if ($_supported_controller.Contains("NVMe") ) {
WriteRunLog -message "VM supports NVMe"
}
else {
WriteRunLog -message "VM doesn't support NVMe" -category "ERROR"
exit
}
}
else {
# SCSI is supported by all VM types
WriteRunLog -message "VM supports SCSI"
}
}
}
else {
WriteRunLog -category "ERROR" -message ("VM SKU doesn't exist, please check your input: " + $VMSize )
exit
}
}
# generate URL for OS disk update
$osdisk_url = "https://management.azure.com/subscriptions/$($_AzureContext.Subscription.Id)/resourceGroups/$ResourceGroupName/providers/Microsoft.Compute/disks/$($_vm_osdisk.Name)?api-version=2023-04-02"
# auth header for web request
$auth_header = @{
'Content-Type' = 'application/json'
'Authorization' = 'Bearer ' + $access_token
}
# body for SCSI/NVMe enabled OS Disk
$body_nvmescsi = @'
{
"properties": {
"supportedCapabilities": {
"diskControllerTypes":"SCSI, NVMe"
}
}
}
'@
# body for SCSI enabled OS Disk
$body_scsi = @'
{
"properties": {
"supportedCapabilities": {
"diskControllerTypes":"SCSI"
}
}
}
'@
# Windows Check script for NVMe
$Check_Windows_Script = @'
$start = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\stornvme -Name Start).Start
if ($start -eq 0) {
Write-Host "Start:OK"
}
else {
Write-Host "Start:ERROR"
}
$startoverride = Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\stornvme\StartOverride -ErrorAction SilentlyContinue
if ($startoverride) {
Write-Host "StartOverride:ERROR"
}
else {
Write-Host "StartOverride:OK"
}
'@
# Pre-Checks completed
WriteRunLog -message "Pre-Checks completed"
# running preparation for operating systems
if ($_os -eq "Windows") {
if ($NewControllerType -eq "NVMe") {
WriteRunLog -message "Starting OS section"
try {
if ($FixOperatingSystemSettings) {
WriteRunLog -message "Fixing operating system settings"
WriteRunLog -message "Running command to set stornvme to boot"
WriteRunLog -message " sc.exe config stornvme start=boot"
$RunCommandResult = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName -CommandId 'RunPowerShellScript' -ScriptString 'Start-Process -FilePath "C:\Windows\System32\sc.exe" -ArgumentList "config stornvme start=boot"'
}
else {
WriteRunLog -message "Collecting details from OS"
$_error = 0
$_okay = 0
$_scriptoutput = ""
$RunCommandResult = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VMName -CommandId 'RunPowerShellScript' -ScriptString $Check_Windows_Script
$_result = ($RunCommandResult.Value | ForEach-Object { $_.Message }) -split "`n"
foreach ($_line in $_result) {
WriteRunLog -message (" Script output: " + $_line)
if ($_line.Contains("OK") -or $_line.Contains("ERROR")) {
$_scriptoutput += $_line + "`n"
if ($_line.Contains("Start:")) {
if ($_line.Contains("ERROR")) {
WriteRunLog -message "Start is not set to boot in the operating system" -category "ERROR"
$_error++
}
else {
WriteRunLog -message "Start is set to boot in the operating system" -category "INFO"
$_okay++
}
}
if ($_line.Contains("StartOverride:")) {
if ($_line.Contains("ERROR")) {
WriteRunLog -message "StartOverride is set in the operating system" -category "ERROR"
$_error++
}
else {
WriteRunLog -message "StartOverride does not exist" -category "INFO"
$_okay++
}
}
}
}
WriteRunLog -message "Windows OS Check result:"
WriteRunLog -message "Errors: $_error - OK: $_okay"
if ($_error -gt 0) {
WriteRunLog -message "Operating system does not seem to be ready, it might not after the conversion" -category "WARNING"
WriteRunLog -message "Please check the operating system settings" -category "WARNING"
WriteRunLog -message "If you want to continue, please use the -FixOperatingSystemSettings switch" -category "IMPORTANT"
WriteRunLog -message "alternative: you can run 'sc.exe config stornvme start=boot' in the operating system and continue or stop the script" -category "IMPORTANT"
AskToContinue -message "Do you want to continue?"
}
}
} catch {
WriteRunLog -message "Error running preparation for Windows OS" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
}
else {
WriteRunLog -message "No preparation required for SCSI"
}
}
else {
WriteRunLog -message "Entering Linux OS section"
try {
# Define the bash script
$linux_check_script = @'
#!/bin/bash
# Set default values
fix=false
distro=""
# Function to display usage
usage() {
echo "Usage: $0 [-fix]"
exit 1
}
# Parse command line arguments
while [ $# -gt 0 ]; do
case "$1" in
-fix)
fix=true
;;
*)
usage
;;
esac
shift
done
# Determine the Linux distribution
if [ -f /etc/os-release ]; then
source /etc/os-release
distro="$ID"
elif [ -f /etc/debian_version ]; then
distro="debian"
elif [ -f /etc/SuSE-release ]; then
distro="suse"
elif [ -f /etc/redhat-release ]; then
distro="redhat"
elif [ -f /etc/centos-release ]; then
distro="centos"
elif [ -f /etc/rocky-release ]; then
distro="rocky"
else
echo "[ERROR] Unsupported distribution."
exit 1
fi
echo "[INFO] Operating system detected: $distro"
# Function to check if NVMe driver is in initrd/initramfs
check_nvme_driver() {
echo "[INFO] Checking if NVMe driver is included in initrd/initramfs..."
case "$distro" in
ubuntu|debian)
if lsinitramfs /boot/initrd.img-* | grep -q nvme; then
echo "[INFO] NVMe driver found in initrd/initramfs."
else
echo "[WARNING] NVMe driver not found in initrd/initramfs."
if $fix; then
echo "[INFO] Adding NVMe driver to initrd/initramfs..."
update-initramfs -u -k all
if lsinitramfs /boot/initrd.img-* | grep -q nvme; then
echo "[INFO] NVMe driver added successfully."
else
echo "[ERROR] Failed to add NVMe driver to initrd/initramfs."
fi
fi
fi
;;
redhat|centos|rocky|suse|sles|ol)
if lsinitrd | grep -q nvme; then
echo "[INFO] NVMe driver found in initrd/initramfs."
else
echo "[WARNING] NVMe driver not found in initrd/initramfs."
if $fix; then
echo "[INFO] Adding NVMe driver to initrd/initramfs..."
mkdir -p /etc/dracut.conf.d
echo 'add_drivers+=" nvme nvme-core "' | sudo tee /etc/dracut.conf.d/nvme.conf > /dev/null
sudo dracut -f
if lsinitrd | grep -q nvme; then
echo "[INFO] NVMe driver added successfully."
else
echo "[ERROR] Failed to add NVMe driver to initrd/initramfs."
fi
fi
fi
;;
*)
echo "[ERROR] Unsupported distribution for NVMe driver check."
return 1
;;
esac
}
# Function to check nvme_core.io_timeout parameter
check_nvme_timeout() {
echo "[INFO] Checking nvme_core.io_timeout parameter..."
if grep -q "nvme_core.io_timeout=240" /etc/default/grub /etc/grub.conf /boot/grub/grub.cfg; then
echo "[INFO] nvme_core.io_timeout is set to 240."
else
echo "[WARNING] nvme_core.io_timeout is not set to 240."
if $fix; then
echo "[INFO] Setting nvme_core.io_timeout to 240…"
case "$distro" in
ubuntu|debian)
sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="nvme_core.io_timeout=240 /g' /etc/default/grub
update-grub
;;
redhat|centos|rocky|suse|sles)
if [ -f /etc/default/grub ]; then
sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="nvme_core.io_timeout=240 /g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub
elif [ -f /etc/default/grub.conf ]; then
sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="nvme_core.io_timeout=240 /g' /etc/default/grub.conf
grub2-mkconfig -o /boot/grub2/grub.cfg
else
echo "[ERROR] No grub config found."
exit 1
fi
;;
ol)
if [ -f /etc/default/grub ]; then
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="nvme_core.io_timeout=240 /g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub
elif [ -f /etc/default/grub.conf ]; then
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="nvme_core.io_timeout=240 /g' /etc/default/grub.conf
grub2-mkconfig -o /boot/grub2/grub.cfg
else
echo "[ERROR] No grub config found."
exit 1
fi
;;
*)
echo "[ERROR] Unsupported distribution for nvme_core.io_timeout fix."
return 1
;;
esac
if grep -q "nvme_core.io_timeout=240" /etc/default/grub /etc/grub.conf /boot/grub/grub.cfg; then
echo "[INFO] nvme_core.io_timeout set successfully."
else
echo "[ERROR] Failed to set nvme_core.io_timeout."
fi
fi
fi
}
# Function to check /etc/fstab for deprecated device names
check_fstab() {
echo "[INFO] Checking /etc/fstab for deprecated device names..."
if grep -Eq '/dev/sd[a-z][0-9]*|/dev/disk/azure/scsi[0-9]*/lun[0-9]*' /etc/fstab; then
if $fix; then
echo "[WARNING] /etc/fstab contains deprecated device names."
echo "[INFO] Replacing deprecated device names in /etc/fstab with UUIDs..."
# Create a backup of the fstab file
cp /etc/fstab /etc/fstab.bak
# Use sed to replace device names with UUIDs
while read -r line; do
if [[ "$line" =~ ^[^#] ]]; then
device=$(echo "$line" | awk '{print $1}')
if [[ "$device" =~ ^/dev/sd[a-z][0-9]*$ ]]; then
uuid=$(blkid "$device" | awk -F\" '/UUID=/ {print $2}')
if [ -n "$uuid" ]; then
newline=$(echo "$line" | sed "s|$device|UUID=$uuid|g")
echo "[INFO] Replaced $device with UUID=$uuid"
echo "$newline" >> /etc/fstab.new
else
echo "[WARNING] Could not find UUID for $device. Skipping."
echo "$line" >> /etc/fstab.new
fi
elif [[ "$device" =~ ^/dev/disk/azure/scsi[0-9]*/lun[0-9]*$ ]]; then
uuid=$(blkid "$device" | awk -F\" '/UUID=/ {print $2}')
if [ -n "$uuid" ]; then
newline=$(echo "$line" | sed "s|$device|UUID=$uuid|g")
echo "[INFO] Replaced $device with UUID=$uuid"
echo "$newline" >> /etc/fstab.new
else
echo "[WARNING] Could not find UUID for $device. Skipping."
echo "$line" >> /etc/fstab.new
fi
else
echo "$line" >> /etc/fstab.new
fi
else
echo "$line" >> /etc/fstab.new
fi
done < /etc/fstab
# Replace the old fstab with the new fstab
mv /etc/fstab.new /etc/fstab
echo "[INFO] /etc/fstab updated with UUIDs. Original fstab backed up to /etc/fstab.bak"
else
echo "[ERROR] /etc/fstab contains device names causing issues switching to NVMe"
fi
else
echo "[INFO] /etc/fstab does not contain deprecated device names."
fi
}
# Run the checks
check_nvme_driver
check_nvme_timeout
check_fstab
exit 0
'@
$linux_fix_script = $linux_check_script.Replace("fix=false","fix=true")
if ($NewControllerType -eq "NVMe") {
if ($FixOperatingSystemSettings) {
# Invoke the Run Command
$RunCommandResult = Invoke-AzVMRunCommand -ResourceGroupName $resourceGroupName -Name $vmName -CommandId 'RunShellScript' -ScriptString $linux_fix_script
}
else {
# Invoke the Run Command
$RunCommandResult = Invoke-AzVMRunCommand -ResourceGroupName $resourceGroupName -Name $vmName -CommandId 'RunShellScript' -ScriptString $linux_check_script
}
$_result = ($RunCommandResult.Value | ForEach-Object { $_.Message }) -split "`n"
$_scriptoutput = ""
$_error=0
$_info=0
$_warning=0
foreach ($_line in $_result) {
if ($_line.Contains("[INFO]") -or $_line.Contains("[ERROR]") -or $_line.Contains("[WARNING]")) {
$_scriptoutput += $_line + "`n"
if ($_line.Contains("[ERROR]")) {
$_error++
}
if ($_line.Contains("[INFO]")) {
$_info++
}
if ($_line.Contains("[WARNING]")) {
$_warning++
}
}
WriteRunLog -message (" Script output: " + $_line)
}
WriteRunLog -message "Errors: $_error - Warnings: $_warning - Info: $_info"
if ($_error -gt 0) {
WriteRunLog -message "Operating system does not seem to be ready, it might not after the conversion" -category "WARNING"
WriteRunLog -message "Please check the operating system settings" -category "WARNING"
WriteRunLog -message "If you want to continue, please use the -FixOperatingSystemSettings switch" -category "IMPORTANT"
WriteRunLog -message "alternative: you can enable NVMe driver manually" -category "IMPORTANT"
AskToContinue -message "Do you want to continue?"
}
}
else {
WriteRunLog -message "No preparation required for SCSI."
}
} catch {
WriteRunLog -message "Error running preparation for Linux OS" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
}
# Shutting down VM
WriteRunLog -message "Shutting down VM $VMName"
try {
$_stopvm = Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Force
WriteRunLog -message "VM $VMName stopped"
} catch {
WriteRunLog -message "Error stopping VM $VMName" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Checking status of VM
WriteRunLog -message "Checking if VM is stopped and deallocated"
$_vminfo = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
if ($_vminfo.Statuses[1].Code -ne "PowerState/deallocated") {
WriteRunLog -message "VM is not deallocated. Please deallocate the VM before running this script."
WriteRunLog -message "giving it another try"
$_stopvm = Stop-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Force
$_vminfo = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Status
if ($_vminfo.Statuses[1].Code -ne "PowerState/deallocated") {
WriteRunLog -message "VM is not deallocated. Please check why the VM is not deallocated." -category "ERROR"
exit
}
}
# Enabling NVMe capabilities on OS disk
WriteRunLog -message "Setting OS Disk capabilities for $($_vm_osdisk.Name) to new Disk Controller Type to $NewControllerType"
try {
WriteRunLog -message "generated URL for OS disk update:"
WriteRunLog -message $osdisk_url
if ($NewControllerType -eq "NVMe") {
$_response = Invoke-RestMethod -Uri $osdisk_url -Method PATCH -Headers $auth_header -Body $body_nvmescsi
}
else {
$_response = Invoke-RestMethod -Uri $osdisk_url -Method PATCH -Headers $auth_header -Body $body_scsi
}
WriteRunLog -message "OS Disk updated"
} catch {
WriteRunLog -message "Error updating OS Disk" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Setting new VM Size and storage controller
WriteRunLog -message "Setting new VM Size from $($_VM.HardwareProfile.VmSize) to $VMSize and Controller to $NewControllerType"
try {
$_VM.HardwareProfile.VmSize = $VMSize
$_VM.StorageProfile.DiskControllerType = $NewControllerType
} catch {
WriteRunLog -message "Error updating VM Size" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Update VM
WriteRunLog -message "Updating VM $VMName"
try {
$_updatevm = Update-AzVM -ResourceGroupName $ResourceGroupName -VM $_VM
if ($_updatevm.StatusCode -eq "OK") {
WriteRunLog -message "VM $VMName updated"
}
else {
WriteRunLog -message "Error updating VM $VMName" -category "ERROR"
exit
}
} catch {
WriteRunLog -message "Error updating VM $VMName" -category "ERROR"
WriteRunLog $_.Exception.Message "ERROR"
exit
}
# Start VM
if ($StartVM) {
WriteRunLog -message "Start after update enabled for VM $VMName"
try {
# waiting for 30 seconds before starting the VM
WriteRunLog -message "Waiting for 30 seconds before starting the VM"
Start-Sleep -Seconds 30
# starting the VM
WriteRunLog -message "Starting VM $VMName"
$_startvm = Start-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
if ($_startvm.Status -eq "Succeeded") {
WriteRunLog -message "VM $VMName started"
}
else {
WriteRunLog -message "Error starting VM $VMName" -category "ERROR"
if ($NewControllerType -eq "NVMe") {
WriteRunLog -message "If you have any issues after the conversion you can revert the changes by running the script with the old settings"
WriteRunLog -message "Here is the command to revert the changes:" -category "IMPORTANT"
WriteRunLog -message " .\Azure-NVMe-Conversion.ps1 -ResourceGroupName $ResourceGroupName -VMName $VMName -NewControllerType SCSI -VMSize $script:_original_vm_size -StartVM"
}
exit
}
} catch {
WriteRunLog -message "Error starting VM $VMName" -category "ERROR"
if ($NewControllerType -eq "NVMe") {
WriteRunLog -message "If you have any issues after the conversion you can revert the changes by running the script with the old settings"
WriteRunLog -message "Here is the command to revert the changes:" -category "IMPORTANT"
WriteRunLog -message " .\Azure-NVMe-Conversion.ps1 -ResourceGroupName $ResourceGroupName -VMName $VMName -NewControllerType SCSI -VMSize $script:_original_vm_size -StartVM"
}
WriteRunLog $_.Exception.Message "ERROR"
exit
}
}
else {
WriteRunLog -message "VM $VMName is stopped. Please start the VM manually."
WriteRunLog -message "If the VM should be started automatically use -StartVM switch"
}
# Check if breaking change warning was enabled before
if ($_breakingchangewarning.Value -eq $true) {
WriteRunLog -message "Breaking Change Warning was enabled before script execution. Enabling it again."
Update-AzConfig -DisplayBreakingChangeWarning $true
}
# Info for next steps
if ($StartVM) {
WriteRunLog -message "As the virtual machine got started using the script you can check the operating system now"
}
else {
WriteRunLog -message "Please start the virtual machine manually and check the operating system" -category "IMPORTANT"
WriteRunLog -message "You can also use -StartVM switch to start the VM automatically"
}
if ($NewControllerType -eq "NVMe") {
WriteRunLog -message "If you have any issues after the conversion you can revert the changes by running the script with the old settings"
WriteRunLog -message "Here is the command to revert the changes:" -category "IMPORTANT"
WriteRunLog -message " .\Azure-NVMe-Conversion.ps1 -ResourceGroupName $ResourceGroupName -VMName $VMName -NewControllerType SCSI -VMSize $script:_original_vm_size -StartVM"
}
# Done
WriteRunLog -message "Script ended at $(Get-Date)"
WriteRunLog -message "Exiting"