Please confirm:
Problem Statement
When running deployment/Invoke-CippMigration.ps1, the script completes its destructive phase (deleting the function apps, app service plans, App Insights, and file shares) before it deploys the new web app. If Azure has no App Service capacity for the B2 SKU in your region at that moment, the deployment fails and you are left with a deleted instance and no rollback, since rebuilding the old setup requires the same App Service Plan that won't deploy.
The script does check before deleting, but the check is Test-AzResourceGroupDeployment (line 411), which only validates the template. It never attempts an allocation, so it can't surface a capacity shortage. From my run:
6:06:14 AM - Template is valid.
6:06:32 AM - No available instances to satisfy this request. App Service is
attempting to increase capacity. (Code: Conflict)
ARM called the template valid 18 seconds before failing to allocate.
There's also a gap in the ordering: validation happens at line 411, deployment at line 659, and every destructive operation runs in between. So even if that check did test capacity, the instance still wouldn't be protected.
What I'd like: a quick throwaway App Service Plan create and delete using the target SKU, run immediately before the deletion phase starts. If it fails, the script stops while everything is still standing.
Benefits for MSPs
Losing CIPP mid-migration isn't a total loss of tenant access, since GDAP still works through the M365 Admin Center tenant switcher, Lighthouse, and PowerShell. But everything CIPP actually adds goes away: standards enforcement, alerting, bulk operations across tenants, and the automation people rely on for repeated tasks. Those have no fallback, and techs go back to per-tenant clicking for the duration.
The bigger issue is that there's no way out. Every other failure in the script fails safely and leaves the instance running. This one deletes the old infrastructure and then can't build the new one, and rebuilding the old setup needs the same App Service Plan that just failed to deploy. So you're stuck waiting on Azure with nothing to roll back to.
A pre-flight check would turn that into "stop, nothing was touched, try again later." It also saves the live troubleshooting. I had to read through the script and template mid-outage to work out whether a re-run was safe and to find a SKU with capacity, which is not where you want to be doing that reading.
It also makes the migration realistic to schedule in a normal maintenance window. Right now there's a failure mode that can blow well past whatever you told your team, with no back-out.
Value or Importance
I'd call this important rather than nice-to-have, because the operation is destructive and has no rollback. Every other failure mode in the script fails safely. This one doesn't.
My outage ran about 75 minutes against a 45 minute planned window, and the only reason it wasn't longer is that I'd read the script closely enough to know a re-run was safe and could test SKUs manually to find one with capacity. Someone who follows the docs and hits this same wall has a deleted instance and no obvious next step.
The rest of the script is solid. The re-run logic in particular handled the partial state perfectly, detecting the NG tag, skipping the deletions it had already done, and finishing in about 40 seconds. This is just about closing the window before that point.
PowerShell Commands (Optional)
This is what I ran manually to diagnose it, and roughly what I'd suggest the script do before the deletion phase:
Probe capacity for the target SKU before deleting anything
New-AzResourceGroup -Name 'RG-CAPTEST' -Location 'centralus'
New-AzAppServicePlan -ResourceGroupName 'RG-CAPTEST' -Name 'captest-plan' `
-Location 'centralus' -Tier 'Basic' -WorkerSize 'Medium' -NumberofWorkers 1 -Linux
Remove-AzResourceGroup -Name 'RG-CAPTEST' -Force
Basic B1 / B2 / B3 Linux -> Conflict
Standard S2 Linux -> Conflict
PremiumV3 P0v3 Linux -> SUCCESS
PremiumV3 P1v3 Linux -> SUCCESS
I recovered by editing the SKU in cipp-migration.json to P0v3 / Premium0V3 and re-running the script, which completed normally. I'll scale the plan back to B2 once Basic capacity returns to the region.
A temp resource group is the cleanest version of that probe, but a temp-named plan in the existing resource group would work too and avoids creating and deleting a resource group on every run. Worth leaving to whoever implements it rather than prescribing.
Please confirm:
Problem Statement
When running deployment/Invoke-CippMigration.ps1, the script completes its destructive phase (deleting the function apps, app service plans, App Insights, and file shares) before it deploys the new web app. If Azure has no App Service capacity for the B2 SKU in your region at that moment, the deployment fails and you are left with a deleted instance and no rollback, since rebuilding the old setup requires the same App Service Plan that won't deploy.
The script does check before deleting, but the check is Test-AzResourceGroupDeployment (line 411), which only validates the template. It never attempts an allocation, so it can't surface a capacity shortage. From my run:
6:06:14 AM - Template is valid.
6:06:32 AM - No available instances to satisfy this request. App Service is
attempting to increase capacity. (Code: Conflict)
ARM called the template valid 18 seconds before failing to allocate.
There's also a gap in the ordering: validation happens at line 411, deployment at line 659, and every destructive operation runs in between. So even if that check did test capacity, the instance still wouldn't be protected.
What I'd like: a quick throwaway App Service Plan create and delete using the target SKU, run immediately before the deletion phase starts. If it fails, the script stops while everything is still standing.
Benefits for MSPs
Losing CIPP mid-migration isn't a total loss of tenant access, since GDAP still works through the M365 Admin Center tenant switcher, Lighthouse, and PowerShell. But everything CIPP actually adds goes away: standards enforcement, alerting, bulk operations across tenants, and the automation people rely on for repeated tasks. Those have no fallback, and techs go back to per-tenant clicking for the duration.
The bigger issue is that there's no way out. Every other failure in the script fails safely and leaves the instance running. This one deletes the old infrastructure and then can't build the new one, and rebuilding the old setup needs the same App Service Plan that just failed to deploy. So you're stuck waiting on Azure with nothing to roll back to.
A pre-flight check would turn that into "stop, nothing was touched, try again later." It also saves the live troubleshooting. I had to read through the script and template mid-outage to work out whether a re-run was safe and to find a SKU with capacity, which is not where you want to be doing that reading.
It also makes the migration realistic to schedule in a normal maintenance window. Right now there's a failure mode that can blow well past whatever you told your team, with no back-out.
Value or Importance
I'd call this important rather than nice-to-have, because the operation is destructive and has no rollback. Every other failure mode in the script fails safely. This one doesn't.
My outage ran about 75 minutes against a 45 minute planned window, and the only reason it wasn't longer is that I'd read the script closely enough to know a re-run was safe and could test SKUs manually to find one with capacity. Someone who follows the docs and hits this same wall has a deleted instance and no obvious next step.
The rest of the script is solid. The re-run logic in particular handled the partial state perfectly, detecting the NG tag, skipping the deletions it had already done, and finishing in about 40 seconds. This is just about closing the window before that point.
PowerShell Commands (Optional)
This is what I ran manually to diagnose it, and roughly what I'd suggest the script do before the deletion phase:
Probe capacity for the target SKU before deleting anything
Basic B1 / B2 / B3 Linux -> Conflict
Standard S2 Linux -> Conflict
PremiumV3 P0v3 Linux -> SUCCESS
PremiumV3 P1v3 Linux -> SUCCESS
I recovered by editing the SKU in cipp-migration.json to P0v3 / Premium0V3 and re-running the script, which completed normally. I'll scale the plan back to B2 once Basic capacity returns to the region.
A temp resource group is the cleanest version of that probe, but a temp-named plan in the existing resource group would work too and avoids creating and deleting a resource group on every run. Worth leaving to whoever implements it rather than prescribing.