CLI scripts for Microsoft 365 (Entra ID + Microsoft Graph) that run entirely from Azure Cloud Shell using az rest. Automate user creation/deletion, license assignment/removal (including plan‑level disables), and export the tenant/user license inventory you need to fill bulk CSVs.
- An active Microsoft 365 tenant and permissions to manage users and licenses (e.g., User Administrator, License Administrator). The APIs used here require delegated permissions such as
User.ReadWrite.AllandLicenseAssignment.ReadWrite.Allwhen acting viaaz rest. - Azure Cloud Shell (Bash) with Azure CLI signed in:
az login. All REST calls go throughaz rest(no third‑party CLIs).
Tip: This repo uses Microsoft Graph v1.0 endpoints for users, licenses, and SKUs. Keep usage consistent with
https://graph.microsoft.com/v1.0.
-
Discovery (one‑CSV export)
Exports tenant SKUs, SKU service plans, all users and theirusageLocation, and each user’s assigned SKUs & plan statuses into one CSV (discovery_all.csv). This gives admins authoritative values forAddSkus,RemoveSkus,DisablePlans, andUsageLocationbefore running bulk changes. Uses:GET /subscribedSkusfor inventory and service plans.GET /users?$select=userPrincipalName,usageLocation(paged).GET /users/{id}/licenseDetails(per user).
-
Bulk Users & Licensing (CSV‑driven)
Processes actions per row: create, delete, assign, remove. Accepts SKU part numbers or GUIDs, supports plan‑level disables when assigning a license, and enforcesusageLocationbefore licensing (Graph requirement). Uses:POST /usersto create users.PATCH /usersto setusageLocation.POST /users/{id|UPN}/assignLicenseto add/remove SKUs +disabledPlans.
M365-CLI/
├─ discovery_one_csv.sh # Read-only export to discovery_all.csv (one file)
├─ m365_csv.sh # CSV-driven create/delete/assign/remove + plan disables
├─ m365_users.csv # Sample operational CSV (actions to perform)
└─ README.md # This file
bash discovery_one_csv.sh
# -> discovery_all.csv with sections:
# TENANT_SKU, SKU_PLAN, USER, USER_LICENSE_PLAN- Why: You’ll use
skuPartNumber/skuIdandservicePlanName/servicePlanIdfrom this export to populate your operational CSV.usageLocationis included because Graph requires it for license assignment.
Start from a template:
bash m365_csv.sh --init-csv --csv m365_users.csvThis template includes:
Action,FirstName,LastName,Alias,UserPrincipalName,Password,UsageLocation,AddSkus,RemoveSkus,DisablePlans,SkipLicense
AddSkus/RemoveSkus: comma‑separatedskuPartNumberorskuId(seediscovery_all.csvrowsTENANT_SKU).DisablePlans: eitherPLAN_A,PLAN_Bfor a single SKU orSKU:PLAN_A,PLAN_B;SKU2:PLAN_Xfor multiple SKUs (plan names or GUIDs; seeSKU_PLANrows).UsageLocation: 2‑letter ISO (required before licensing).
# Process the entire CSV (create/delete/assign/remove by row)
bash m365_csv.sh --csv m365_users.csv process
# Or a single action subset:
bash m365_csv.sh --csv m365_users.csv assignUnder the hood, the script calls Graph v1.0:
POST /users,PATCH /users, andPOST /users/{id}/assignLicense, all viaaz rest.
Purpose: Export everything you need to fill the operational CSV into one file: discovery_all.csv.
Sections in the CSV:
TENANT_SKU:skuPartNumber,skuId, enabled/consumed counts.SKU_PLAN: per-SKUservicePlanNameandservicePlanId.USER:userPrincipalName,usageLocation.USER_LICENSE_PLAN: user’s assignedskuPartNumber/skuIdand each plan’s provisioning status.
Run:
bash discovery_one_csv.shPurpose: Create/delete users and add/remove licenses with optional plan disables, driven by a CSV.
Key behaviors:
- Creates users (
POST /users) withdisplayName,mailNickname,userPrincipalName, andpasswordProfile. - Ensures
usageLocationis set (PATCH /users) before any license change (Graph requirement). - Adds/removes licenses and can disable selected plans in the same call (
/assignLicense). - Resolves
skuPartNumber → skuIdandservicePlanName → servicePlanIdfrom your tenant inventory (/subscribedSkus).
Generate template:
bash m365_csv.sh --init-csv --csv m365_users.csvList SKUs and plans:
bash m365_csv.sh --list-skus
bash m365_csv.sh --list-plans M365_BUSINESS_PREMIUM # or a skuId GUIDProcess CSV:
# All rows
bash m365_csv.sh --csv m365_users.csv process
# Only one action
bash m365_csv.sh --csv m365_users.csv assign
bash m365_csv.sh --csv m365_users.csv remove
bash m365_csv.sh --csv m365_users.csv create
bash m365_csv.sh --csv m365_users.csv delete
# Skip licensing globally for this run
bash m365_csv.sh --csv m365_users.csv --skip-license process- Cloud Shell friendly: Calls only
az rest; avoids third‑party tools. Keep your session authenticated withaz login. - Graph paging: User export follows
@odata.nextLinkuntil complete; replication delays for just‑created users are possible per Graph docs. - Licensing rules: Microsoft requires
usageLocationon the user before license assignment; errors like “invalid usage location” are resolved by setting it first.
- Script seems to “close” the shell: Avoid
set -eand unhandledexiton REST failures; these scripts catch errors and continue so the Cloud Shell session stays open. - SKU not found / plan name unknown: Re‑run
discovery_one_csv.shand confirm the exactskuPartNumberandservicePlanNamefrom your tenant (/subscribedSkus). - License assignment fails: Verify
usageLocation(seeUSERrows indiscovery_all.csv) and that the SKU has available enabled units.
- Open an issue or a PR with your scenario and logs (omit real UPNs if sensitive).
- Keep scripts Bash‑only with
az restto stay Cloud Shell compatible. This aligns with the lightweight approach shown in your Azure-CLI repo.
- Microsoft Graph REST API v1.0 overview (endpoint patterns and guidance).
- List tenant SKUs / service plans:
GET /subscribedSkus. - List users with
usageLocation:GET /users(paging,$select). - Per‑user license details:
GET /users/{id}/licenseDetails. - Create and update users:
POST /users,PATCH /users. - Assign/remove licenses + disable service plans:
POST /users/{id}/assignLicense.