Clean up resources
This guide provides instructions for cleaning up resources that may be left behind if a process in Schoolyear AVD fails.
Clean up Entra ID devices
Section titled “Clean up Entra ID devices”Use the following script to clean up Entra ID devices created by Schoolyear AVD.
This script might take a long time to run (over 15 minutes), depending on the number of devices in your directory.
To run this script, you need the Device.ReadWrite.All
permission.
Connect-MgGraph -Scopes "Device.ReadWrite.All"$avdPrefix = "syvm"
$staleThresholdInDays = 15$staleDate = (Get-Date).AddDays(-$staleThresholdInDays)
Write-Output "Searching for stale devices with prefix '$avdPrefix' inactive since $staleDate..."
$devices = Get-MgDevice -Filter "startsWith(displayName, '$avdPrefix')" -Property "Id,DisplayName,ApproximateLastSignInDateTime" -All
if ($null -eq $devices) { Write-Output "No devices found with the prefix '$avdPrefix'." return}
Write-Output "Found $($devices.Count) devices with the prefix. Checking for staleness..."
foreach ($device in $devices) { # Check if the last sign-in timestamp is older than the stale date if ($null -ne $device.ApproximateLastSignInDateTime -and $device.ApproximateLastSignInDateTime -lt $staleDate) { Write-Output "DELETING stale device: $($device.DisplayName) (ID: $($device.Id)). Last sign-in: $($device.ApproximateLastSignInDateTime)"
# To run the script safely first, comment out the line below and run it to see what WOULD be deleted. # Use the -WhatIf parameter for an extra layer of safety during testing. Remove-MgDevice -DeviceId $device.Id }}
Write-Output "Script finished."
Clean up old DNS records
Section titled “Clean up old DNS records”If a deletion job in Schoolyear AVD fails, it might leave an orphaned DNS record in your DNS zone. Use the following script to clean up these orphaned records.
Use the -WhatIf
flag to perform a dry run and see which records would be deleted without actually deleting them.
<#.SYNOPSIS Cleans up orphaned 'A' records in an Azure DNS zone.
.DESCRIPTION This script queries 'A' DNS records in a specified Azure DNS zone and filters for records named with a UUID. It then compares these records against resource groups in the current subscription that match the pattern 'syexam-<uuid>'. If a DNS record's UUID does not have a corresponding resource group, the script will delete the DNS record set.
.PARAMETER DnsZoneName The name of the DNS zone to query. This parameter is mandatory.
.PARAMETER DnsZoneResourceGroupName The name of the resource group that contains the DNS zone. This parameter is mandatory.
.PARAMETER SubscriptionId The ID of the subscription where the resources are located. This parameter is mandatory.
.PARAMETER WhatIf A switch parameter to show what would be deleted without actually performing the deletion. This is highly recommended for the first run.
.EXAMPLE .\Clean-OrphanDnsRecords.ps1 -DnsZoneName "yourdomain.com" -DnsZoneResourceGroupName "MyDns-RG" -SubscriptionId "00000000-0000-0000-0000-000000000000" -WhatIf
This command runs the script in a simulation mode. It will connect to your Azure subscription, analyze the DNS records and resource groups, and output a list of DNS records that would be deleted, but it will not make any changes.
.EXAMPLE .\Clean-OrphanDnsRecords.ps1 -DnsZoneName "yourdomain.com" -DnsZoneResourceGroupName "MyDns-RG" -SubscriptionId "00000000-0000-0000-0000-000000000000"
This command executes the cleanup operation. After identifying the orphaned records, it will prompt you for a final confirmation before deleting them permanently.#>param( [Parameter(Mandatory=$true)] [string]$DnsZoneName,
[Parameter(Mandatory=$true)] [string]$DnsZoneResourceGroupName,
[Parameter(Mandatory=$true)] [string]$SubscriptionId,
[Switch]$WhatIf)
try { #region SETUP AND CONNECTION Write-Host "Connecting to Azure..." -ForegroundColor Cyan Connect-AzAccount -ErrorAction Stop Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop Write-Host "Successfully connected. Operating in subscription '$SubscriptionId'." -ForegroundColor Green
# Regex to validate a UUID string. $uuidRegex = "[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}" #endregion
#region GATHER RESOURCE GROUP UUIDs Write-Host "Fetching all resource groups to find existing UUIDs..." -ForegroundColor Cyan $resourceGroups = Get-AzResourceGroup
# Use a HashSet for efficient 'Contains' lookups, ignoring case. $rgUuids = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($rg in $resourceGroups) { # Check if the RG name matches the pattern "syexam-<uuid>" if ($rg.ResourceGroupName -match "^syexam-($uuidRegex)$") { # $matches[1] contains the captured UUID part from the regex. [void]$rgUuids.Add($matches[1]) } }
if ($rgUuids.Count -eq 0) { Write-Warning "No resource groups found matching the 'syexam-<uuid>' pattern." } else { Write-Host "Found $($rgUuids.Count) resource groups with the required 'syexam-<uuid>' name format." -ForegroundColor Green } #endregion
#region GATHER AND ANALYZE DNS RECORDS Write-Host "Fetching 'A' records from DNS zone '$DnsZoneName'..." -ForegroundColor Cyan $dnsRecords = Get-AzDnsRecordSet -ResourceGroupName $DnsZoneResourceGroupName -ZoneName $DnsZoneName -RecordType A -ErrorAction Stop
Write-Host "Identifying orphaned DNS records..." -ForegroundColor Cyan $recordsToDelete = @() foreach ($record in $dnsRecords) { # Check if the DNS record's name is a UUID. if ($record.Name -match "^($uuidRegex)$") { $dnsUuid = $matches[1]
# If the UUID from the DNS record is NOT in our set of resource group UUIDs, it's an orphan. if (-not $rgUuids.Contains($dnsUuid)) { $recordsToDelete += $record } } } #endregion
#region DELETE ORPHANED RECORDS if ($recordsToDelete.Count -eq 0) { Write-Host "Scan complete. No orphaned DNS 'A' records found." -ForegroundColor Green } else { Write-Warning "Found $($recordsToDelete.Count) orphaned DNS records to delete:" $recordsToDelete.Name | ForEach-Object { Write-Warning "- $_.$DnsZoneName" }
if ($WhatIf) { Write-Host "`nRunning in -WhatIf mode. No actual changes will be made." -ForegroundColor Yellow } else { # Prompt for confirmation before deleting. $confirmation = Read-Host "`nAre you sure you want to permanently delete these $($recordsToDelete.Count) DNS records? (y/n)" if ($confirmation -eq 'y') { Write-Host "Proceeding with deletion..." -ForegroundColor Cyan foreach ($record in $recordsToDelete) { try { Write-Host "Deleting DNS record set: $($record.Name)..." Remove-AzDnsRecordSet -Name $record.Name -RecordType $record.RecordType -ZoneName $DnsZoneName -ResourceGroupName $DnsZoneResourceGroupName -Confirm:$false -ErrorAction Stop Write-Host "Successfully deleted $($record.Name)." -ForegroundColor Green } catch { Write-Error "Failed to delete DNS record '$($record.Name)'. Error: $_" } } Write-Host "Cleanup complete." -ForegroundColor Green } else { Write-Host "Deletion cancelled by user." -ForegroundColor Yellow } } } #endregion}catch { Write-Error "An unexpected error occurred: $_" # Exit with a non-zero status code to indicate failure. exit 1}
Clean up Key Vault access
Section titled “Clean up Key Vault access”If a deletion job in Schoolyear AVD fails, it might leave an orphaned IAM role assignment in your Key Vault. Follow these steps to remove these orphaned assignments:
-
In the Azure portal, navigate to the Key Vault for your AVD implementation.
-
Go to Access Control (IAM) > Role Assignments.
-
Filter the list of assignments with the following settings:
- Type:
Unknown
- Role:
Key Vault Secrets User
- Scope:
This resource
- Type:
-
Select and delete all role assignments that match this filter.