param (
[Parameter(Mandatory=$true)][string]$tenantId
)
Connect-AzAccount -Tenant $tenantId
$subscriptions = @(Get-AzSubscription -TenantId $tenantId)
foreach ($subscription in $subscriptions)
{
Set-AzContext -Subscription $subscription -Tenant $tenantId | Out-null
$vaults = Get-AzRecoveryServicesVault
[System.Collections.ArrayList]$vms = @(Get-AzVM)
foreach ($vault in $vaults)
{
Set-AzRecoveryServicesVaultContext -Vault $vault
$containers = Get-AzRecoveryServicesBackupContainer -ContainerType "AzureVM" -Status "Registered"
$vaultVMs=@()
foreach ($container in $containers) # Fetch all backed-up VMs in the iterated vault
{
$vaultVMs += Get-AzRecoveryServicesBackupItem -Container $container -WorkloadType "AzureVM" | Select-Object VirtualMachineId
}
foreach ($vaultVM in $vaultVMs) # Remove the backed-up VMs from the array containing all the VMs in the iterated subscription
{
$indexsToRemove = @()
for ($i=$vms.Count-1; $i -ge 0; $i--)
{
if ($vaultVM.VirtualMachineId -eq $vms[$i].id)
{
$indexsToRemove += $i
}
}
foreach ($indexToRemove in $indexsToRemove) {
$vms.RemoveAt($indexToRemove)
}
}
}
if($vms.Count -gt 0)
{
Write-Host "### SUBSCRIPTION $($subscription.Name) ###"
}
foreach ($vm in $vms) # Print out the VM names which are not backed up in the iterated subscription
{
Write-Host "$($vm.name) is not backed up"
}
if($vms.Count -gt 0)
{
Write-Host ""
}
}
I have made some improvements to the script in Abhilash's anwswer, which makes it more robust, but also more suitable to the use case I am working on. These improvements are:
- The scope is now all subscriptions, and all resource groups, under a specific tenant. This tenant's ID is a script parameter.
- Removed a redundant call to
Get-AzureRmRecoveryServicesBackupContainer
- Whenever a match is found between the array containing all the VMs (
$vms
) and the array containing the VMs which are backed-up inside the recovery vaults ($vaultVMs
), the first array is updated, thus avoiding redundant iterations when iterating $vms