So, in the world of efficiency and automation, clickety-clack in the Azure Portal is not where you want to be - no matter how much of a mouse ninja you are.

You want to be able to quickly do things via repeatable scripts and whatnot so that we take the human out of the equation as far as possible.

You can do pretty much everything (and sometimes more) via the Azure CLI - some parts have convenient methods and others fall back to the basics, but still lets you do it.

Unless I'm missing something, you cannot easily create new scopes in an Azure AD Application via Azure CLI. You can create AppRoles via --app-roles, but there's a --scopesmissing.

Instead, they at least provide you with

Generic Update Arguments
  --add  : Add an object to a list of objects by specifying a path and key                      value pairs.  Example: --add property.listProperty <key=value,
           string or JSON string>.
      
  --set  : Update an object by specifying a property path and value to set.
                                   Example: --set property1.property2=<value>.

What this enables you to do is directly manipulate the manifest.

At first thought, --add seemed like the way to go; I want to add a scope. But, since oauth2Permissions (which is the property we want to update) already exists because every Azure AD Application will get a default scope when created through Azure CLI we can't do it that way.

So, we use --set to update the existing property.

Initially, I figured "hey, just add the json for the new scope and set that!". Not so fast, silly rabbit. We are, after all, performing raw updates and they need to be spoon fed everything.

The error Azure AD will return if you try to do this is:

Property  value cannot be deleted or updated unless it is disabled first.

This means that the existing scope(s) have isEnabled=true set in the manifest and to delete them (which is what you would do if overwriting them with your new one) you would need to disable them first.

Let's pull out the existing scopes first:

az ad app show --id 66c317a3-16f4-4509-a775-66dcc58e0809 --query="oauth2Permissions" > scopes.json

Open the scopes.json and add your new scope

[
  {
    "adminConsentDescription": "Allow the application to access testingcli on behalf of the signed-in user.",
    "adminConsentDisplayName": "Access testingcli",
    "id": "01a74263-82a0-4ee1-b166-d71a793d1142",
    "isEnabled": true,
    "type": "User",
    "userConsentDescription": "Allow the application to access testingcli on your behalf.",
    "userConsentDisplayName": "Access testingcli",
    "value": "user_impersonation"
  },
    {
    "adminConsentDescription": "Allow stuff for the signed-in user.",
    "adminConsentDisplayName": "Access this new thing",
    "id": "860768a6-0f0d-45ea-a40f-c3f9c461b79f",
    "isEnabled": true,
    "type": "User",
    "userConsentDescription": "Allow stuff on your behalf.",
    "userConsentDisplayName": "Access this new thing",
    "value": "app.scope.test"
  }
]

Remember to generate a new UUID for the id property in your new scope.
Also, make sure value is unique for your application.

Now, lets update the scopes

az ad app update --id 66c317a3-16f4-4509-a775-66dcc58e0809 --set oauth2Permissions=@scopes.json

Just to make sure everything looks good, we can use the Azure Portal (or just look it up using az ad app show ..., but the portal is prettier)

New scope in place!

We can now use this to create a script to provision our application with all scopes, permissions, roles etc consistently every time if we so choose.