ConvertTo-SPOT.ps1

$asciiArt = @'
 ____ _ __ ______ _ ____ ____ ___ _____
| _ \ / \\ \ / / ___| | |_ ___ / ___|| _ \ / _ \_ _|
| |_) / _ \\ V / | _ | __/ _ \ \___ \| |_) | | | || |
| __/ ___ \| || |_| | | || (_) | ___) | __/| |_| || |
|_| /_/ \_\_| \____| \__\___/ |____/|_| \___/ |_|
 
Designed and managed by Aamir
 
'@

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

Function ConvertTo-SPOT {
    Param (
        # Set variables to your specifics
        $resourceGroup,
        $vmName,
        $subscriptionName,
        [switch]$force
    )
    $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)

        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-Warning 'Found below VM extensions, make sure you get them installed after conversion.'
        foreach ($currExtension in $extensionExist) {
            Write-Warning "|-- $($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 (($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
            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)"
        }
    }
}