{"id":20555169,"url":"https://github.com/angela-d/office365-licensing","last_synced_at":"2026-06-06T11:31:04.250Z","repository":{"id":217077631,"uuid":"743049696","full_name":"angela-d/office365-licensing","owner":"angela-d","description":"Automate Office 365 license assignments based on security groups in Active Directory using Microsoft Graph","archived":false,"fork":false,"pushed_at":"2024-12-04T16:12:35.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-06T06:33:44.992Z","etag":null,"topics":["active-directory","microsoft-graph","microsoft-graph-api","office365","office365-licensing","powershell","powershell-script"],"latest_commit_sha":null,"homepage":"","language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/angela-d.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-14T06:50:48.000Z","updated_at":"2024-12-04T16:12:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"5848abc9-b406-417f-baee-84194687ab0e","html_url":"https://github.com/angela-d/office365-licensing","commit_stats":null,"previous_names":["angela-d/office365-licensing"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/angela-d/office365-licensing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angela-d%2Foffice365-licensing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angela-d%2Foffice365-licensing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angela-d%2Foffice365-licensing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angela-d%2Foffice365-licensing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/angela-d","download_url":"https://codeload.github.com/angela-d/office365-licensing/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angela-d%2Foffice365-licensing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33981122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["active-directory","microsoft-graph","microsoft-graph-api","office365","office365-licensing","powershell","powershell-script"],"created_at":"2024-11-16T03:16:51.713Z","updated_at":"2026-06-06T11:31:04.228Z","avatar_url":"https://github.com/angela-d.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Onsite Active Directory / Office 365 License Integration\nPowershell scripts using Microsoft Graph to automate Office 365 license assignments based on security groups in Active Directory.\n\n## Pre-requisites\n- Already syncing your onsite Active Directory to Office 365 using Azure / Entra Active Directory Sync / Entra Cloud Sync\n- Configure the two attached Powershell scripts with your environment's config\n\n## Features of the Scripts\n- Exclude organizational units / OUs from O365 license assignment\n- Apply licenses based on user's security group\n- License management / failover to different license when primary seats are exhausted\n- Override exclusions; if a user is in an excluded OU but part of a special override group, they get assigned a license whereas peers in the same OU do not (unless they're also members of the special security group)\n- Easily mass remove/delete unwanted or old / obsolete licenses -- anything not designated by the script gets replaced (so run in debug mode, first!)\n- Failure detection; if syncing activity isn't detected within 3 days, notify an admin\n\n### Config / Setup\n***\n**readyfor365.ps1**\n***\n***\nThis script is phase one and should run before **process_licenses.ps1**\n\nIt generates a .csv of users to process and their designated license.\n\n\nConfig options for readyfor365.ps1:\n\n\n- **$exportedFor365** = Absolute (full) path to where the CSV for the preceeding script will go; if using nested directories, make sure you create them; this script will not do it for you.\n- **$licenseOne**, **$licenseTwo**, **$licenseThree** = One of your license assignments' license SKU\n- **$OUtoSearch** = Organizational unit of where the user accounts that need licenses are\n- **$domain** = Your O365 domain\n- **$excludeOUs** = OUs you want to exclude from being processed \u0026 subsequently assigned licenses\n- **$licenseGroups** = ALL of your security groups that get any type of license\n- **$overrideGroups** = Users in these group(s) get a license, even if they belong to an exluded OU; separate multiple group names by a pipe: |\n- **$exportList** = Must match for *process_license.ps1* - don't touch\n\nOptional / only seen when running via command line - name \"one, two, etc\" accordingly to match your environment but avoid touching the variables unless you also change those in your code:\n```powershell\nWrite-Output \"One licenses to issue: $totalOne\"\nWrite-Output \"Two licenses to issue: $totalTwo\"\nWrite-Output \"Three licenses to issue: $totalThree\"\nWrite-Output \"Special One licenses to issue: $totalSpecialOne\"\nWrite-Output \"Special One licenses to issue: $totalSpecialTwo\"\n```\n\nConfig options for process_license.ps1:\n- **Install the certificate you'll be using** to the *Local Machine* where this script will be running\n- **Make note of the SN** mmc.exe \u003e File \u003e Add/Remove Snap-In \u003e Certificates \u003e Add \u003e Computer Account \u003e Certificates \u003e Personal \u003e Certificates \u003e locate your cert \u003e Open it \u003e Details \u003e Subject; this goes in the **$certSN** variable\n- **$To** = Email address for notifications if there's licensing problems; like a helpdesk email\n- **$From** = Sender of notifications for licensing problems\n- **$SMTPServer** = SMTP host of your mail server\n- **$SMTPPort** = SMTP port of your mail server\n- **$licenseOne**, **$licenseTwo**, **$licenseThree**, **$licenseSpecial** = Leave these blank; they're just initializing the variables\n- **$licenseOneTotalSeats**, **$licenseTwoTotalSeats**, **$licenseThreeTotalSeats**, **$licenseSpecialSeats** = Seat values alloted to your origanization; see How-To section on how to obtain\n- **$failoverLicense** = License to assign if desired is at capacity\n- `function consumedSeats` and `function whichLicense` - Set user-readable names for your licenses in this conditional lookup -- does not need to match Microsoft's naming convention\n- **$debug** = Set to `1` when you **do not want to make changes to O365**!  `0` will make immediate changes to your O365 tenant!\n\nObtain the following from [entra.microsoft.com](https://entra.microsoft.com):\n- **$tenantID** = Tenant ID of your MS Office ([direct link to obtain this stuff](https://entra.microsoft.com/#home))\n- **$appID** = same as above\n- **$csvPath** = this must match **$exportedFor365** from the *readyfor365.ps1* script\n- **$resultFile** = Absolute (full) path to log textual output from the process; is written over on each run.\n- **$newlyLicensedFile** = Absolute (full) path to log textual output from the process for every new license assignee; is written over on each run.\n\n\n\n***\n**process_license.ps1**\n***\n***\nThis script is phase two and should only run after readyfor365.ps1 has completed.\n\n\n\n\n***\n\n### How To\n\nOnce you've got your Graph credentials, you can login via terminal.\nIt's recommended to do so for testing \u0026 debugging purposes and to make sure it works before setting your script loose.\n\nBy default, Powershell may try utilizing antiquated TLS.. bypass such by running the following in your Powershell ISE:\n```powershell\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n```\n\nIf this is your first time using graph, install the module:\n```powershell\ninstall-module Microsoft.Graph.Users\n```\n\nImport it:\n```powershell\nImport-Module Microsoft.Graph.Users\n```\n\n- Now, copy/paste your graph variables from your **process_licenses.ps1** script, from `$tenantID` down to `$thumbprint`\n\n- Run the following to wrap it all together:\n    ```powershell\n    Write-Host \"Connecting to Graph App ID: $appID on Tenant ID: $tenantID with thumbprint: $thumbprint\"\n    Connect-MgGraph -ClientID $appID -TenantId $tenantID -CertificateThumbprint $thumbprint -NoWelcome\n    Write-Output \"==================\"\n    Write-Output \"Connected to Graph!\"\n    Write-Output \"==================`n`n\"\n    ```\n    - Once you're connected to Graph via ISE/terminal, you can run sample queries or get licensing info:\n\n- Get a list of available SKUs to put in readyfor365.ps1:\n    ```powershell\n    Get-MgSubscribedSku | Select SkuPartNumber, SkuId\n    ```\n\n- Set your `$debug` variable to `1` and test your implemenation\n- Once everything is working smoothly, automate to run headless via Task Scheduler\n\n**NOTE!!!** \n- If you make changes to your api privileges from the Azure / Entra portal, Graph will inherit from your last session and not pick up the changes until you disconnect; to do so:\n\n    ```powershell\n    Disconnect-MgGraph -ErrorAction SilentlyContinue\n    ```\n\n### Overview\n\n- readyfor365.ps1 = Does not make changes to Active Directory or Office 365; reads AD and prepares a .csv based on what's configured within the script\n- process_licese.ps1 = Depending on debug value, will adjust/remove licenses based on configuration within the script.\n\n### Credits\nHuge thank you to [Salaudeen Rajack](https://www.sharepointdiary.com/2023/04/how-to-connect-to-microsoft-graph-api-from-powershell.html) for the well-written article on Microsoft Graph.\n\nSalaudeen's concise writeup made the transition from MSOnline cmdlet's to Microsoft Graph far easier.\n\nMicrosoft's depreciation table for MSOnline: [https://learn.microsoft.com/en-us/powershell/microsoftgraph/azuread-msoline-cmdlet-map?view=graph-powershell-1.0](https://learn.microsoft.com/en-us/powershell/microsoftgraph/azuread-msoline-cmdlet-map?view=graph-powershell-1.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangela-d%2Foffice365-licensing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fangela-d%2Foffice365-licensing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangela-d%2Foffice365-licensing/lists"}