> ## Documentation Index
> Fetch the complete documentation index at: https://help.nops.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Microsoft Azure - Setup Script (Azure CLI)

> Set up Azure Savings Analysis with one Azure CLI script: what it does, what it needs, how to run it, how to run it again, and how to fix anything it can't do for you.

## Overview

The setup script is the automated way to do [Run the Setup](https://help.nops.io/azure/integration-setup.md#3-run-the-setup) (steps **A** to **D**) for Azure Savings Analysis. You copy it from the nOps wizard and paste it into Azure Cloud Shell. It sets up the same things you would set up by hand in the Azure portal, with the same names and the same access for nOps. Nothing more.

Choose it in the wizard at **Run Setup → Run an Azure CLI script**. The other choice, **Step by step**, is the portal walkthrough. Both end at the same place, and you can switch between them at any time.

> **Use the script if**
>
> You (or someone on your team) can run Azure CLI commands, and you'd rather not click through four portal steps.

> **Use Step by step if**
>
> Your organization only allows changes through the Azure portal, or a change ticket has to list each portal action.

**In short, the script:**

* Shows what it will do, and what your account is allowed to do, before it changes anything.
* Does every step your account can do, and skips the ones it can't (it doesn't fail on them).
* Ends with a numbered list of what is left. For each item it says who can do it, where in the Azure portal, and the exact command with your values filled in.
* Is safe to run again, on any day and by anyone. It checks each step, keeps what is already there, and never creates anything twice.
* Grants nOps only the access the manual setup asks for.
* Covers Savings Analysis only. [Commitment Management](https://help.nops.io/azure/commitment-management-setup.md) has its own setup.

> [!NOTE]
>
> **Before you start:** finish **Get Started** and **Grant Admin Consent** in the nOps wizard. The script needs the **nOps.io** enterprise application to exist in your tenant, and consent is what adds it.

***

## What the script sets up

These are the same four parts as the manual setup.

| Step | What the script does | Where | Why nOps needs it | Access nOps gets |
| - | - | - | - | - |
| **1** | Gives nOps.io **Reader** | Your management group (default: the tenant root group, which covers every subscription) | To see your subscriptions and their resources | Read-only |
| **2** | Creates resource group `nops-focus-exports-rg`, storage account `nopsfocus` + the first 15 characters of your tenant ID (no dashes), and container `focus-exports`. Gives nOps.io **Storage Blob Data Owner** on that storage account only | Your export subscription | So Azure has a place to write your cost files, and nOps can read them | Data access to that one storage account. Nothing else in the subscription |
| **3** | Gives nOps.io **Billing account reader** (MCA), **Billing profile reader** (MCA billing profiles) or **EnrollmentReader** (EA) | Your billing account, each billing profile you connect, or your EA enrollment | To find your billing account and check that the export runs | Read-only |
| **4** | Creates the daily FOCUS cost export `nops-clara-focus-export`, writing to the storage account from step 2. Starts it once now, and once for each of the last three months | Your billing account, each billing profile, or your EA enrollment | So Azure writes your cost data every day | None (the export belongs to you) |

The export uses its own Azure identity to write files. When the export is created, Azure gives that identity **Storage Blob Data Contributor** on the `focus-exports` container. The script checks for it and, only if it is missing (for example on an export made by hand whose access was later removed), assigns it on the export storage account, as the Azure portal does. This identity belongs to the export in your tenant. It is not nOps.

### Exactly the same access as the manual setup

The script gives nOps.io the same three roles at the same scopes as [Step by step](https://help.nops.io/azure/integration-setup.md#3-run-the-setup):

| Role for nOps.io | Scope | Manual step |
| - | - | - |
| Reader | Management group | B |
| Storage Blob Data Owner | The export storage account only | D |
| Billing account reader / Billing profile reader / EnrollmentReader | Billing account / each billing profile / EA enrollment | A |

It never assigns Owner, Contributor, User Access Administrator, any billing owner or contributor role, or any role at subscription or tenant scope. nOps tests this on every change to the script. For every role ID and what it allows, see [Permissions & Resources](https://help.nops.io/azure/permissions-and-resources.md).

### What the script never does

* It never deletes anything, and never changes a resource it didn't create.
* It never replaces an export. If an export named `nops-clara-focus-export` already writes somewhere else, the script tells you and leaves it alone.
* It never creates a second storage account, export or role assignment for something that already exists.
* It never stores or sends your Azure credentials. It runs as you, in your own Azure CLI session.
* It never reads your cost data or resources itself, and never sends them to nOps.

***

## How it works

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart LR
  subgraph You["Your side"]
    U["You, in Azure Cloud Shell<br/>(signed in as yourself)"]
  end
  subgraph Tenant["Your Azure tenant"]
    MG["Management group<br/>Reader for nOps.io"]
    subgraph Sub["Export subscription"]
      SA["Storage account nopsfocus…<br/>container focus-exports"]
    end
    BA["Billing account / profiles / enrollment<br/>billing reader role for nOps.io"]
    EX["Daily FOCUS export<br/>nops-clara-focus-export"]
    SP["nOps.io enterprise app<br/>(added by admin consent)"]
  end
  subgraph N["nOps"]
    W["Setup page in nOps<br/>(live checklist)"]
    R["Progress endpoint<br/>(step results only)"]
  end

  U -- "1. Reader" --> MG
  U -- "2. Create, give read access" --> SA
  U -- "3. Billing reader role" --> BA
  U -- "4. Create export" --> EX
  EX -- "writes cost files daily" --> SA
  U -. "step results (done / needs / error code)" .-> R
  R -.-> W
  SP -- "reads subscriptions" --> MG
  SP -- "reads cost files" --> SA
  SP -- "checks the export" --> BA
  W -- "checks your tenant every few seconds<br/>as nOps.io, read-only" --> SP
```

1. **You run the script** in your own Azure CLI session. Every change is made by your account, so Azure's activity log shows you made it.
2. **The script reports each step's result to nOps**: done, skipped, needs another role, or the Azure error code with a one-line message. That's all it sends. It never waits on nOps; if nOps can't be reached, setup carries on.
3. **nOps checks your tenant on its own**, as the nOps.io application, with the read access the script just gave it. The setup page in nOps ticks each part off as Azure shows it.
4. **When every required part is in place, nOps runs Verify for you** and turns Savings Analysis on.

### One run, step by step

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
  participant You
  participant Script as Setup script (your Azure CLI)
  participant Azure
  participant nOps

  Script->>You: What this script does (the plan)
  Script->>Azure: Checks: signed in, right tenant, sign-in still valid, CLI version, nOps.io present
  Script->>Azure: What can your account do here? (read-only checks)
  Script->>You: [yes] / [needs] per step. Start the setup now? [Y/n]
  loop Steps 1 to 4
    Script->>Azure: Is it already there?
    alt Already there
      Script->>You: [ok] … (already in place, kept)
    else Your account can do it
      Script->>Azure: Create or assign
      Script->>You: [done] …
    else Your account can't
      Script->>You: [needs] … (added to the to-do list)
    end
    Script-->>nOps: Step result (best effort)
  end
  Script->>You: Summary: kept, done, and how to finish the rest
  nOps->>Azure: Checks the tenant as nOps.io
  nOps->>nOps: All required parts found: runs Verify
```

***

## Before you start

### Who should run it

The script does what the signed-in account is allowed to do. One person with all the roles below can finish in one run. Otherwise, run it as one person, then have the next person run the **same script**. See [Splitting the work between people](#splitting-the-work-between-people).

| Steps | Role needed | Where |
| - | - | - |
| 1. Reader | **Owner** or **User Access Administrator** | The management group (for the tenant root group, a Global Administrator can [give themselves access](https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin)) |
| 2. Storage account | **Owner** (or **Contributor** plus **User Access Administrator**) | The export subscription |
| 3–4. Billing role and export | **Billing account owner** (MCA), **Billing profile owner** (MCA billing profiles) or **Enterprise Administrator** (EA) | The billing account, each billing profile, or the enrollment |

The person who does step 4 also needs to be able to write to the export storage account (for example, Owner on the resource group `nops-focus-exports-rg`), because the export's identity gets access to it.

### Where to run it

| Where | What you need |
| - | - |
| **Azure Cloud Shell (Bash)**, recommended | Nothing to install. Open [shell.azure.com/bash](https://shell.azure.com/bash). You're already signed in. |
| macOS or Linux terminal | [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) 2.37 or later, signed in with `az login --tenant <your tenant ID>` |
| Windows | [Git for Windows](https://git-scm.com/download/win) (Git Bash) and the Azure CLI 2.37 or later. Save the script and run `bash nops-azure-setup.sh` |

* **First time in Cloud Shell:** choose **Bash**, then **No storage account required** and any subscription. Nothing is created in your tenant, and the script uses its own export subscription whatever you pick.
* **After pasting, press Enter.** Cloud Shell and most terminals hold a multi-line paste until you do. The script then shows what it will do and asks before it changes anything.
* Pasting works in Cloud Shell, macOS Terminal (zsh) and Linux shells. In PowerShell it doesn't: save the file and run it with `bash` instead.

The script checks the Azure CLI version, the tenant you're signed in to, and that your sign-in hasn't expired, before it does anything.

### What to choose in nOps first

* **Billing account type** (Get Started): Microsoft Customer Agreement (MCA) or Enterprise Agreement (EA). For EA, enter the enrollment number.
* **Billing account or billing profiles** (MCA only): we recommend the billing account. Choose billing profiles only if your organization gives access per billing profile; then list each profile. See [billing scopes](#billing-account-billing-profiles-and-ea).
* **Where it goes** (optional, under **Change** in Run Setup):
  * **Management group ID**: leave it blank to use the tenant root group.
  * **Export subscription ID**: leave it blank and the script picks one when it runs (see [How the export subscription is picked](#how-the-export-subscription-is-picked)).
  * **Billing account ID** (MCA): leave it blank and the script finds it. Enter it only if you can see more than one billing account.

***

## Run it

**1. Open the script in nOps**

In the nOps wizard, go to **Run Setup** and choose **Run an Azure CLI script**. Check **Where it goes**, and use **Change** if you want a specific management group or export subscription.

**2. Optional: run the preview**

Click **Copy preview** and paste it into Cloud Shell. The preview runs every check and lists exactly what the real script would change. It changes nothing and sends nothing to nOps. Use it for a change review, or to see what your account can and can't do.

**3. Copy the script**

Click **Copy setup script** (or **Download**). You can also expand **Read the script** to see every line first. The script has your tenant's values filled in. From now on, the nOps setup page watches your tenant.

**4. Paste it into Azure Cloud Shell**

Open [Azure Cloud Shell](https://shell.azure.com/bash) (Bash), paste, and press Enter. The script prints what it will do, what your account can do, and then asks **Start the setup now? \[Y/n]**. Nothing has changed up to this point.

**5. Read the summary**

The script ends with a summary of what was already in place, what this run did, and, if anything is left, a numbered list of how to finish it. See [Reading the output](#reading-the-output).

**6. Finish anything left, then run it again**

Do the items in the list yourself, or send the list to the person it names. Then run the same script again: it keeps everything and only does what's left. Repeat until the summary says **Setup is done**.

**7. nOps finishes on its own**

The setup page in nOps ticks off each part as Azure shows it (usually within a few minutes; Azure can take up to about 10 minutes to apply new access). When everything required is found, nOps runs Verify and turns on Savings Analysis. You can also click **Already set up? Verify now**.

***

## Reading the output

Every line starts with a tag:

| Tag | Meaning |
| - | - |
| `[yes]` | Before the steps: your account can do this step |
| `[needs]` | Your account doesn't have the role for this. Skipped, and added to the to-do list at the end |
| `[?]` | Azure didn't say whether your account can do this. The script tries it |
| `[done]` | Created or assigned in this run |
| `[ok] … (already in place, kept)` | Found from an earlier run, or set up by hand. Left exactly as it is |
| `[fail]` | Azure returned an error. The Azure message is shown just above it, and the item is added to the to-do list |
| `[note]` | Information only, nothing to do |
| `[would]` | Preview only: what the setup script would change |

### The summary

The summary has up to four parts:

1. **Already in place … kept as is**: everything the script found and left alone.
2. **Done in this run**: everything this run created or assigned.
3. **Not done**: what was skipped (your account doesn't have the role) or failed (Azure returned an error).
4. **To finish setup, do these**: one numbered item per thing left. Each item says:
   * **What** to do, with the names of your resources.
   * **Who can do it**: the Azure or billing role needed.
   * **Easiest**: ask that person to run the same script.
   * **In the Azure portal**: the menu path.
   * **With the Azure CLI**: the exact command, with your tenant's values filled in.

Example, from a run by an Azure administrator who isn't a billing owner:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Summary
  Done in this run (4):
   - Reader for nOps.io on management group 3f2a9c1e-…
   - Storage account nopsfocus3f2a9c1e5b7d4e8
   - Container focus-exports
   - Storage Blob Data Owner for nOps.io on nopsfocus3f2a9c1e5b7d4e8
  Not done: admin@contoso.com doesn't have the role (2):
   - Billing account reader for nOps.io on your billing account: needs Billing account owner
   - FOCUS export at your billing account: needs Billing account owner

To finish setup, do these (or ask the person named to):

 1. Give nOps.io Billing account reader on your billing account
   Who can do it: Billing account owner.
   Easiest: ask them to run this same script. It keeps what is already done and only does what is left.
   Or in the Azure portal: Cost Management + Billing > Billing scopes > your billing account > Access control (IAM) > Add > role Billing account reader > User, group, or service principal: nOps.io > Add.
   Or with the Azure CLI:
     az rest --method post --url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/…/createBillingRoleAssignment?api-version=2024-04-01" --body '{…}'

 2. Create the daily FOCUS export at your billing account
   Who can do it: Billing account owner who can also write to storage account nopsfocus3f2a9c1e5b7d4e8 …
   …

  Then run this script again, today or any other day. It checks every step, keeps what is already in place, and only does what is left. Nothing is created twice.
```

The script exits with code `0` when setup is done or only needs someone else, and `1` when Azure returned an error.

***

## Running it again

Expect to run the script more than once. A common pattern:

* **Day 1**: an Azure administrator runs it. Reader and the storage account are done. The billing steps are listed for a billing owner.
* **Between runs**: someone does some items by hand in the portal, or the billing owner gets the script.
* **Day 2**: the billing owner runs the same script. It finds everything from day 1 and from the manual work, keeps it, and only does the billing role and the export.
* **Day 3**, or any later run: nothing is left. The script changes nothing and says so.

### How it avoids creating things twice

The script checks Azure for each item before it creates anything:

| Item | How it checks | On a re-run |
| - | - | - |
| Reader, Storage Blob Data Owner, export identity access | Lists nOps.io's (or the export's) role assignments at that scope, including ones inherited from above. A role on something smaller (Reader on one subscription only) doesn't count, because nOps needs it on the whole scope. The export's own access also counts when Azure gave it on the container | Kept. A role is never assigned twice |
| Resource group | Looks it up by its fixed name `nops-focus-exports-rg` | Kept |
| Storage account | Looks it up by its fixed name. Storage account names are unique across all of Azure, so there can only ever be one | Kept, in whatever subscription it is in |
| Container | Checks for `focus-exports` in that account | Kept |
| Billing reader role | Lists nOps.io's billing role assignments at that scope | Kept |
| Billing profile ID | Looks up the profile, whichever of its two IDs you entered (the **Billing profile ID** from Properties, or its Azure name, often a GUID) | The role and export always go under the Azure name, where nOps checks them. Entering the other form next time finds the same export |
| FOCUS export | Lists the FOCUS exports at the billing scope. Any export already writing to the storage account counts, whatever its name | Kept. Never a second export into the same folder |
| Export runs | Checks the export's run history | Not run again once the export has delivered files. Started again if it never delivered, and this month's run is started again if its latest run failed (for example, its storage access was missing until this run) |
| Resource providers | Checks whether each is registered | Registered only if it isn't |

Every name is fixed: there are no random names or timestamps. A re-run can't leave behind duplicates that sit unused.

### What a re-run never does

* It doesn't undo or overwrite anything you did by hand. If you created a role assignment or the export in the portal, it's found and kept.
* It doesn't need a fresh copy of the script. A copy more than 3 days old still sets everything up; it just stops reporting progress to nOps (the script says so once). Copy it again from nOps to see each step's result on the setup page.
* It doesn't start a new storage account in another subscription. If your Azure CLI is now set to a different subscription, the script still finds the existing account and uses it.

### Splitting the work between people

1. Run the script as the person with Azure roles (or the preview, to see who's needed).
2. Send the **same script** (or just the to-do list from its summary) to the person each item names, for example your billing account owner.
3. They run it. It keeps what's done and does their part.

***

## Billing account, billing profiles and EA

| Your setup | What the script does |
| - | - |
| **MCA, billing account** (recommended) | One **Billing account reader** assignment and one export at the billing account. Covers every billing profile, including ones you add later. If you can see one MCA billing account, the script finds it. If you can see several, it stops before the billing steps and asks you to enter the right one under **Billing account ID** in nOps |
| **MCA, billing profiles** | For **each** profile you list in nOps: **Billing profile reader**, and its own export writing to its own folder (`nops-clara-` + the profile ID in lowercase letters and digits), so the exports never overwrite each other. Profiles you don't list aren't included. Enter the **Billing profile ID** from the profile's **Properties** page; the script looks up the profile's Azure name itself. A profile Azure doesn't know is skipped, with how to fix it, and the others are still set up. A Billing account owner can do all profiles; a Billing profile owner only their own |
| **EA** | **EnrollmentReader** and one export at the enrollment. Azure only lets an app get an EA billing role through the API, so if your account can't do it, the to-do list gives the exact command for an Enterprise Administrator (there's no portal path) |

If you choose MCA but your tenant's billing account is an Enterprise Agreement, the script says so and tells you to switch the billing account type in nOps.

### How the export subscription is picked

If you leave **Export subscription ID** blank, the script picks the first that applies:

1. The subscription where the export storage account already is (from an earlier run).
2. Your Azure CLI's current subscription, if your account can create resources there.
3. The first subscription where your account can create resources.

It only picks a subscription under the management group you connected, because that's where nOps has Reader: a storage account anywhere else would be invisible to nOps. If the storage account from an earlier run is outside the group, the script keeps it and adds a to-do: give nOps.io **Reader** on that storage account only.

If your account can't create resources in any subscription under the group, it uses one it can see there and lists the storage step for someone with Owner there. It prints the subscription it chose. To use another one, run `az account set --subscription <ID>` and run the script again (before the storage account exists), or enter it under **Change** in nOps. After setup, nOps finds the storage account by its name and saves the subscription.

***

## What the script sends to nOps

Only step results, so the setup page can show progress and why something failed:

* The step name and its result (started, done, needs another role, skipped, failed).
* The Azure error code, such as `AuthorizationFailed`, and a one-line message.
* A run ID and the script version.

It sends no credentials, tokens, resource contents, cost data or personal data. The script authenticates to nOps with a single-purpose token that can only post step results for your integration, expires after 3 days, and is stored by nOps only as a hash. To turn reporting off, set `REPORT_URL=""` at the top of the script. The preview never sends anything.

***

## Troubleshooting

### Before any change is made

> **The Azure CLI isn't installed here**
>
> Run it in [Azure Cloud Shell](https://shell.azure.com/bash), or [install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) and sign in with `az login --tenant <your tenant ID>`.

> **This Azure CLI is version … the script needs 2.37 or later**
>
> Run `az upgrade`, or use Azure Cloud Shell (always current). Older versions name things differently, and nOps.io would look missing.

> **You're not signed in / You're signed in to tenant …, but nOps is connected to …**
>
> Run `az login --tenant <the tenant ID the script shows>`, then run the script again. In Cloud Shell, you may need to switch directory first (Cloud Shell → Settings → Switch directory).

> **Your Azure sign-in … has expired or needs to be renewed**
>
> Your sign-in timed out, or your organization requires multi-factor authentication again. Run `az login --tenant <your tenant ID>` and run the script again.

> **nOps.io isn't in this tenant yet**
>
> Admin consent hasn't been granted in this tenant, or the nOps.io enterprise application was deleted. Finish **Grant Admin Consent** in the nOps wizard (a Global Administrator or Privileged Role Administrator), then run the script again.

> **… can't see subscription … / can't see a subscription in tenant …**
>
> Your account has no access to the export subscription. Sign in as someone who has, or enter a subscription you can see under **Change** in nOps.

> **Couldn't read management group …**
>
> A note, not an error: your account can't see that management group, so assigning Reader there will probably need someone else. Check the ID under **Change** in nOps (leave it blank for the tenant root group).

### During the steps

> **\[needs] … needs Owner or User Access Administrator / needs Billing account owner**
>
> Your account doesn't have that role. The step is skipped and listed at the end with who can do it, the portal path and the exact command. Ask that person to do it, or to run the same script.

> **AuthorizationFailed / your account needs …**
>
> Azure refused the change. Same fix as above. If you do hold the role, it may have been granted in the last few minutes: wait 5 to 10 minutes and run the script again.

> **an Azure Policy in your tenant blocked it (RequestDisallowedByPolicy)**
>
> A policy in your tenant doesn't allow the resource as the script creates it (for example, a required tag, a location restriction, or storage settings). The Azure error above the line names the policy. Ask your cloud team to allow it or exempt the `nops-focus-exports-rg` resource group, then run the script again.

> **Storage account name … is already used outside subscription …**
>
> The storage account from an earlier setup exists in another subscription. Storage account names are unique across Azure, so it can't be created again. In the Azure portal, search **Storage accounts** for the name and note its subscription. In nOps, enter that subscription under **Change**, copy the script again and run it. If you can't see it, it's in a subscription your account can't access: ask an Azure administrator.

> **MissingSubscriptionRegistration**
>
> The export subscription hasn't finished registering a resource provider. The script registers them and waits, but registration can need **Contributor** on the subscription. Run the `az provider register …` command from the to-do list (or Subscriptions → your subscription → Resource providers → Register), wait a few minutes, and run the script again.

> **PrincipalNotFound / Azure hasn't finished creating the identity**
>
> Azure is still creating nOps.io's identity (right after consent) or the export's identity. The script already retries for about a minute. Wait a few minutes and run it again.

> **You can see … Microsoft Customer Agreement billing accounts**
>
> Your account can see more than one MCA billing account, so the script doesn't guess. In the Azure portal, open **Cost Management + Billing → Billing scopes**, open the account nOps should analyze, and copy its ID from **Properties**. Enter it under **Billing account ID** in nOps, copy the script again and run it.

> **This tenant's billing account is an Enterprise Agreement**
>
> nOps is set to MCA, but your billing account is EA. In nOps, go back to **Get Started**, choose **Enterprise Agreement**, enter your enrollment number, copy the script again and run it.

> **nOps sets up Microsoft Customer Agreement and Enterprise Agreement billing accounts …**
>
> Your account only sees another kind of billing account (for example pay-as-you-go, or a CSP partner's). That isn't supported by Savings Analysis. Contact nOps support.

> **No Microsoft Customer Agreement billing account is visible to …**
>
> Your account has no billing role. Steps 3 and 4 need a billing owner: ask them to run the same script.

> **An export named nops-clara-focus-export at … writes to another storage account**
>
> An export with nOps's name already exists and writes somewhere else. The script never changes exports it didn't create. In **Cost Management → Exports** (at that billing scope), either edit it to write to the nOps storage account, container `focus-exports`, or delete it, then run the script again.

> **An export named nops-clara-focus-export at … exports …, not FOCUS cost**
>
> An export with nOps's name already exists but exports another kind of cost data. The script never changes exports it didn't create. If nobody needs it, delete it in **Cost Management → Exports** (at that billing scope), then run the script again to create the FOCUS export.

> **FOCUS export … writes to container …, not focus-exports**
>
> A FOCUS export already delivers to the nOps storage account, but into another container, which nOps doesn't read. Edit it in **Cost Management → Exports** to use container `focus-exports`, then run the script again.

> **FOCUS export … is turned off**
>
> The export exists but its schedule is inactive, so no new cost files arrive. Turn it back on in **Cost Management → Exports** (schedule Active, daily), then run the script again.

> **Couldn't read the exports at …, so nothing was changed there**
>
> The script couldn't list the existing exports, so it didn't create one (it could have replaced someone else's). Usually this means your account has no billing access there. Ask a billing owner to run the script, or follow the portal steps in the to-do list.

> **Billing profile … wasn't found in billing account …**
>
> Azure has no billing profile with that ID in your billing account, so the script skipped it and set up the others. Check the ID in the Azure portal (**Cost Management + Billing → Billing scopes →** your billing account **→ Billing profiles →** the profile **→ Properties**), correct it under **Billing profiles** in nOps, copy the script again and run it.

> **Runs of … already queued**
>
> A note: runs from an earlier run of the script are still in progress. Azure finishes them on its own.

> **Couldn't start … for (a month). Run this script again later to retry**
>
> Azure didn't accept the run request (usually because the export's storage access is still applying). The export runs daily on its own. Running the script again later retries the history runs.

> **Couldn't send progress to nOps**
>
> A note, followed by the reason:
>
> * **this copy of the script is more than 3 days old**: copy it again from nOps.
> * **this machine couldn't reach nOps**: a firewall or proxy blocks the machine from reaching nOps. Azure Cloud Shell can always reach it.
> * **this copy has sent too many reports**: copy it again from nOps.
>
> Either way setup carries on, and nOps still checks your tenant directly, so the setup page finishes on its own. Only the per-step results are missing from it.

> **TooManyRequests / ServiceUnavailable**
>
> Azure was busy. The script retries each call up to four times. If it still fails, run the script again a few minutes later: nothing already done is repeated.

### On the nOps setup page

> **An item shows as waiting**
>
> Azure can take up to about 10 minutes to apply new access. nOps keeps checking for 30 minutes after you copy the script; click **Check now** to check again after that.

> **An item shows as not found after the script said done**
>
> Wait 10 minutes and click **Check now**. If it's still missing, the line under the item shows what the script reported. Run the script again: it repairs what's missing and keeps the rest.

> **Verify fails after the script finished**
>
> Open the failing part in Verify. You can switch to **Step by step** at any time: those steps show the same checks with portal instructions. Then run Verify again.

***

## Undoing the setup

The script never removes anything. To remove what it set up, as someone with the same roles:

* **Reader**: Management groups → your management group → Access control (IAM) → Role assignments → remove **nOps.io**.
* **Storage**: delete the resource group `nops-focus-exports-rg` (this deletes the storage account, the export files and its role assignments).
* **Billing role**: Cost Management + Billing → your billing scope → Access control (IAM) → remove **nOps.io**.
* **Export**: Cost Management → Exports (at your billing scope) → delete `nops-clara-focus-export`. The export's files stay in the storage account until you delete them or the resource group.
* To remove all nOps access at once, delete the **nOps.io** enterprise application in Microsoft Entra ID → Enterprise applications.

***

## FAQs

> **Can we read the script before running it?**
>
> Yes. In the nOps wizard, expand **Read the script**, or click **Download** and open the file. You can also run the **preview**, which makes no changes and lists exactly what would change.

> **Does the script give nOps more access than the manual setup?**
>
> No. It assigns nOps.io exactly the three roles the manual steps ask for, at the same scopes: Reader on the management group, Storage Blob Data Owner on the one storage account, and the billing reader role. nOps tests this on every change to the script.

> **Does nOps get our credentials?**
>
> No. The script runs in your Azure CLI session, as you. nOps only receives step results (done, needs a role, or an error code).

> **Can we run it in a pipeline instead of by hand?**
>
> Yes. When there's no terminal it doesn't ask for confirmation. You can also set `NOPS_SETUP_YES=1` to skip the question. Run the preview first and review its output in your change process.

> **What if our naming or tagging policy requires other names?**
>
> The names are fixed because nOps looks for them. The resource group and storage account are tagged `managed_by=nOps` and `purpose=nops-focus-export`. If a policy blocks them, the script shows the policy and leaves the rest of the setup in place; ask your cloud team for an exemption for `nops-focus-exports-rg`.

> **We changed the management group or export subscription after a first run**
>
> The script sets up the new one. It doesn't remove access from the old one; remove it by hand if you no longer want it (see [Undoing the setup](#undoing-the-setup)). The storage account can only exist once: if it already exists, keep using its subscription.

> **Does the script set up Commitment Management?**
>
> No. Commitment Management is a separate step with its own permissions, done after Savings Analysis is active. See [Commitment Management setup](https://help.nops.io/azure/commitment-management-setup.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.