ConvertTo-SPOT.ps1

Function ConvertTo-SPOT {
    Param (
        # Set variables to your specifics
        $resourceGroup,
        $vmName,
        $subscriptionName,
        [switch]$force
    )
    $asciiArt = @'
 ____ _ __ ______ _ ____ ____ ___ _____
| _ \ / \\ \ / / ___| | |_ ___ / ___|| _ \ / _ \_ _|
| |_) / _ \\ V / | _ | __/ _ \ \___ \| |_) | | | || |
| __/ ___ \| || |_| | | || (_) | ___) | __/| |_| || |
|_| /_/ \_\_| \____| \__\___/ |____/|_| \___/ |_|
 
Designed and managed by Aamir
 
'@

    Write-Output "`e[5;32m$asciiArt`e[0m";

    $WarningPreference = 'SilentlyContinue'
    $randomNumber = Get-Random -Maximum 1000

    $context = Set-AzContext $subscriptionName
    Write-Output "`e[36mSetting context to $($subscriptionName)"
    Try {
        # Get the details of the VM to be moved to the Availability Set
        $originalVM = Get-AzVM `
            -ResourceGroupName $resourceGroup `
            -Name $vmName -ErrorAction Stop
    }
    Catch {
        Write-Output "`e[31mIncorrect details provided. Please check the resource name."
        Write-Output "`e[31mMentioned VM $($vmName) not found."
        Start-Sleep -Seconds 10
    }
    if ($originalVM) {
        Write-Output "`e[32mFound below VM in $($subscriptionName)"
        $originalVM.Id
    }

    try {
        # Check whether already logged-in
        # $token = (Get-AzAccessToken).token
        # $login = (az login --federated-token $token --tenant 'a33c6ac4-a52e-45c5-af07-b972df9bd004')

        # $login = (az login --identity) # for managed identity

        $setAzContext = (az account set --name $subscriptionName)
        $updateresource = (az resource update --resource-group $resourceGroup --name $vmName --resource-type virtualMachines --namespace Microsoft.Compute --set properties.storageProfile.osDisk.deleteOption=detach)
        if (((Get-AzVM -ResourceGroupName $resourceGroup -Name $vmName).SecurityProfile.SecurityType) -eq 'TrustedLaunch') {
            Write-Error 'Unfortunately, You cannot convert [TrustedLaunch] SecurityProfile VM to SPOT - these are not simple configuration items you can just change, these determine what hardware the server is bound to, how encryption is configured etc. SPOT conversion is only applicable for [STANDARD] SecurityProfile VMs'
            break;
        }
        else {
            New-AzVM `
                -ResourceGroupName $resourceGroup `
                -Location $originalVM.Location `
                -VM $originalVM `
                -DisableBginfoExtension -WhatIf

            Write-Warning 'VM eligible for conversion. Please use -Force with same command, If you are fine with the above results.'
        }
    }
    catch {
        Write-Output "`e[31mYou need to login. Or incorrect resource details provided."
        Start-Sleep -Seconds 10
        if ($LASTEXITCODE -ne 0) { exit 1 }
    }
    # checking for dependencies
    $vm = Get-AzVM -ResourceGroupName $resourceGroup -VMName $vmName
    $extensionExist = ($vm.Extensions | Select-Object Publisher, VirtualMachineExtensionType, TypeHandlerVersion)
    if ($extensionExist) {
        Write-Output "`e[36mFound below VM extensions, make sure you get them installed after conversion."
        foreach ($currExtension in $extensionExist) {
            Write-Output "`e[33m|-- $($currExtension.VirtualMachineExtensionType)"
        }
        Write-Output "`e[36mMake sure you note the installed extensions, that you can configure later on the converted VM."
        ('-' * 75)
        Write-Output "`e[36mPlease re-run the automation with the -force switch to initiate the conversion, due to extension dependencies."
        ('-' * 75)
    }

    $isSPOT = (Get-AzVM -ResourceGroupName $resourceGroup -VMName $vmName | Select-Object Name, Location, ProvisioningState, Priority, @{Name = 'maxPrice'; Expression = { $_.BillingProfile.MaxPrice } })
    $isSPOT

    if (((Get-AzVM -ResourceGroupName $resourceGroup -Name $vmName).SecurityProfile.SecurityType) -eq 'TrustedLaunch') {
        Write-Error 'Unfortunately, You cannot convert [TrustedLaunch] SecurityProfile VM to SPOT - these are not simple configuration items you can just change, these determine what hardware the server is bound to, how encryption is configured etc. SPOT conversion is only applicable for [STANDARD] SecurityProfile VMs'
    }
    else {
        if (($isSPOT.Priority -eq $null) -and ($isSPOT.ProvisioningState -eq 'Succeeded')) {
            if ($force) {
                Write-Output "`e[37mInitiating the conversion..."
                # Create the basic configuration for the replacement VM.
                $newVM = New-AzVMConfig `
                    -VMName $originalVM.Name `
                    -VMSize $originalVM.HardwareProfile.VmSize `
                    -Priority 'Spot' -MaxPrice -1 # You can set the maxPrice to -1 to indicate that the Azure Spot VM/VMSS should not be evicted for price reasons. Also, the default max price is -1 if it is not provided by you.
                Write-Verbose $newVM
                # Taking backup of OSdisks
                $osDiskid = $originalVM.StorageProfile.OsDisk.ManagedDisk.Id
                $osDiskName = $originalVM.StorageProfile.OsDisk.Name
                # Create Disk Snapshot
                $snapshot = New-AzSnapshotConfig -SourceUri $osDiskid -Location $originalVM.Location -CreateOption copy
                $newsnap = New-AzSnapshot `
                    -Snapshot $snapshot `
                    -SnapshotName "$($osDiskName)_$($randomNumber)" `
                    -ResourceGroupName $resourceGroup
                Write-Output "`e[32m`tStep 1 : Taking OS disk backup / snapshot."
                Write-Verbose $newsnap
                # Confgure OS Disk
                $attachOSDisk = Set-AzVMOSDisk `
                    -VM $newVM -CreateOption Attach `
                    -ManagedDiskId $originalVM.StorageProfile.OsDisk.ManagedDisk.Id `
                    -Name $originalVM.StorageProfile.OsDisk.Name
                if ($attachOSDisk) {
                    Write-Verbose $attachOSDisk
                    Write-Output "`e[32m`tStep 2 : OS Disk marked for attachment."
                }

                if ($originalVM.OSProfile.WindowsConfiguration) {
                    $newVM.StorageProfile.OsDisk.OsType = 'Windows'
                    Write-Output "`e[32m`tStep 3 : Identified WINDOWS host OS profile."
                }
                else {
                    $newVM.StorageProfile.OsDisk.OsType = 'Linux'
                    Write-Output "`e[32m`tStep 3 : Identified LINUX host OS profile."
                }
                if (($originalVM.StorageProfile.DataDisks).count -gt 0) {
                    foreach ($disk in $originalVM.StorageProfile.DataDisks) {
                        # Taking backup of disks
                        # Get Current VM Data Disk metadata.
                        $dataDiskid = $disk.ManagedDisk.Id
                        $dataDiskName = ($disk.Name).ToLower()

                        # Create Disk Snapshot
                        $snapshot = New-AzSnapshotConfig -SourceUri $dataDiskid -Location $originalVM.Location -CreateOption copy
                        $newsnap = New-AzSnapshot `
                            -Snapshot $snapshot `
                            -SnapshotName "$($dataDiskName)_$($randomNumber)" `
                            -ResourceGroupName $resourceGroup
                        Write-Verbose $newsnap
                        Write-Output "`e[32m`tStep 4 : Taking backup of Data disk - $($dataDiskName)."
                    }
                }
                else { Write-Output "`e[32m`tStep 4 : No Data disk found for backup.[SKIPED]" }
                # Add NIC(s) and keep the same NIC as primary
                Write-Output "`e[32m`tStep 5 : Configuring NIC for the VM."
                foreach ($nic in $originalVM.NetworkProfile.NetworkInterfaces) {
                    if ($nic.Primary -eq 'True') {
                        $addExistingNIC = Add-AzVMNetworkInterface `
                            -VM $newVM `
                            -Id $nic.Id -Primary
                    }
                    else {
                        $addExistingNIC = Add-AzVMNetworkInterface `
                            -VM $newVM `
                            -Id $nic.Id
                    }
                }
                if ($originalVM.AvailabilitySetReference.Id) {
                    #$newVM.AvailabilitySetReference=$originalVM.AvailabilitySetReference.Id
                    Write-Output "`e[31mWarning: VM $originalVM.Name is in an availability set. Spot VMs cannot run in availability sets."
                }

                # Remove the original VM
                # Detach all dependent resources
                Write-Output "`e[37mDetaching attached resources for reusabelity."
                if (((Get-AzVM -ResourceGroupName $resourceGroup -Name $vmName).StorageProfile.Osdisk.DeleteOption) -eq 'Delete') {
                    $vmConfig = Get-AzVM -ResourceGroupName $resourceGroup -Name $vmName
                    $vmConfig.StorageProfile.OsDisk.DeleteOption = 'Detach'
                    $vmConfig.StorageProfile.DataDisks | ForEach-Object { $_.DeleteOption = 'Detach' }
                    $vmConfig.NetworkProfile.NetworkInterfaces | ForEach-Object { $_.DeleteOption = 'Detach' }
                    $vmConfig | Update-AzVM
                }

                Write-Output "`e[37mOverwriting existing VM with SPOT conversion - $($originalVM.Name)."
                $removeExistingVM = Remove-AzVM -ResourceGroupName $resourceGroup -Name $vmName -Force
                Write-Verbose $removeExistingVM

                # # Creating disks from snapshots
                # $osDisk = New-AzDisk -DiskName $osDiskName -Disk `
                # (New-AzDiskConfig -Location $originalVM.Location -CreateOption Copy `
                # -SourceResourceId $newsnap.Id) `
                # -ResourceGroupName $resourceGroup
                # Recreate the VM
                Write-Output "`e[32m`tStep 6 : Converting to NEW SPOT VM."
                $NewVMCreation = New-AzVM `
                    -ResourceGroupName $resourceGroup `
                    -Location $originalVM.Location `
                    -VM $newVM `
                    -DisableBginfoExtension
                if ($NewVMCreation) {
                    Write-Verbose $NewVMCreation
                    Write-Output "`e[32mSUCCESS : Completed conversion succesfully."
                }
                foreach ($attachDisk in $originalVM.StorageProfile.DataDisks) {
                    $dataDiskid = ($attachDisk.ManagedDisk.Id).ToLower()
                    $dataDiskName = ($attachDisk.Name).ToLower()
                    Write-Output "`e[32m`tStep 7 : Attaching $($dataDiskName)"
                    $vm = Get-AzVM -Name $vmName -ResourceGroupName $resourceGroup
                    $vm = Add-AzVMDataDisk -VM $vm `
                        -Name $dataDiskName `
                        -ManagedDiskId $dataDiskid `
                        -Lun $attachDisk.Lun `
                        -CreateOption Attach
                    Write-Output " --- Adding data disk $($dataDiskName) to $($vmName)"
                    Update-AzVM -ResourceGroupName $resourceGroup -VM $vm
                }
            }
        }
        else {
        ('-' * 45)
            Write-Output " `e[35 $($vmName) is already running as SPOT."
        ('-' * 45)
        }
    }
    if ($NewVMCreation -and $extensionExist) {
        Write-Output 'To install extensions on the updated VM run the below command'
        foreach ($currentItemName in $extensionExist) {
            ('-' * 25)
            Write-Output "Set-AzVMExtension -Publisher $($currentItemName.Publisher) -ExtensionType $($currentItemName.Publisher) -Name $($currentItemName.VirtualMachineExtensionType) -ResourceGroupName $($resourceGroup) -VMName $($vmName)"
        }
    }
}