From eae9fed83c15a11c8af0c99b971a057bb472963f Mon Sep 17 00:00:00 2001 From: AdminDroid <49208841+admindroid-community@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:01:31 +0530 Subject: [PATCH] M365 License Management and Reporting using MS Graph M365 License Management and Reporting using MS Graph --- .../LicenseFriendlyName.txt | 239 +++++++++ .../ManageM365Licenses.ps1 | 475 ++++++++++++++++++ .../ManageM365Licenses.zip | Bin 7855 -> 0 bytes 3 files changed, 714 insertions(+) create mode 100644 Manage Microsoft 365 Licenses using MS Graph/LicenseFriendlyName.txt create mode 100644 Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.ps1 delete mode 100644 Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.zip diff --git a/Manage Microsoft 365 Licenses using MS Graph/LicenseFriendlyName.txt b/Manage Microsoft 365 Licenses using MS Graph/LicenseFriendlyName.txt new file mode 100644 index 0000000..edddefa --- /dev/null +++ b/Manage Microsoft 365 Licenses using MS Graph/LicenseFriendlyName.txt @@ -0,0 +1,239 @@ +AAD_BASIC= Azure Active Directory Basic + AAD_PREMIUM= Azure Active Directory Premium + AAD_PREMIUM_P1= Azure Active Directory Premium P1 + AAD_PREMIUM_P2= Azure Active Directory Premium P2 + ADALLOM_O365= Office 365 Advanced Security Management + ADALLOM_STANDALONE= Microsoft Cloud App Security + ADALLOM_S_O365= POWER BI STANDALONE + ADALLOM_S_STANDALONE= Microsoft Cloud App Security + ATA= Azure Advanced Threat Protection for Users + ATP_ENTERPRISE= Exchange Online Advanced Threat Protection + ATP_ENTERPRISE_FACULTY= Exchange Online Advanced Threat Protection + BI_AZURE_P0= Power BI (free) + BI_AZURE_P1= Power BI Reporting and Analytics + BI_AZURE_P2= Power BI Pro + CCIBOTS_PRIVPREV_VIRAL= Dynamics 365 AI for Customer Service Virtual Agents Viral SKU + CRMINSTANCE= Microsoft Dynamics CRM Online Additional Production Instance (Government Pricing) + CRMIUR= CRM for Partners + CRMPLAN1= Microsoft Dynamics CRM Online Essential (Government Pricing) + CRMPLAN2= Dynamics CRM Online Plan 2 + CRMSTANDARD= CRM Online + CRMSTORAGE= Microsoft Dynamics CRM Online Additional Storage + CRMTESTINSTANCE= CRM Test Instance + DESKLESS= Microsoft StaffHub + DESKLESSPACK= Office 365 (Plan K1) + DESKLESSPACK_GOV= Microsoft Office 365 (Plan K1) for Government + DESKLESSPACK_YAMMER= Office 365 Enterprise K1 with Yammer + DESKLESSWOFFPACK= Office 365 (Plan K2) + DESKLESSWOFFPACK_GOV= Microsoft Office 365 (Plan K2) for Government + DEVELOPERPACK= Office 365 Enterprise E3 Developer + DEVELOPERPACK_E5= Microsoft 365 E5 Developer(without Windows and Audio Conferencing) + DMENTERPRISE= Microsoft Dynamics Marketing Online Enterprise + DYN365_ENTERPRISE_CUSTOMER_SERVICE= Dynamics 365 for Customer Service Enterprise Edition + DYN365_ENTERPRISE_P1_IW= Dynamics 365 P1 Trial for Information Workers + DYN365_ENTERPRISE_PLAN1= Dynamics 365 Plan 1 Enterprise Edition + DYN365_ENTERPRISE_SALES= Dynamics 365 for Sales Enterprise Edition + DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE= Dynamics 365 for Sales and Customer Service Enterprise Edition + DYN365_ENTERPRISE_TEAM_MEMBERS= Dynamics 365 for Team Members Enterprise Edition + DYN365_FINANCIALS_BUSINESS_SKU= Dynamics 365 for Financials Business Edition + DYN365_MARKETING_USER= Dynamics 365 for Marketing USL + DYN365_MARKETING_APP= Dynamics 365 Marketing + DYN365_SALES_INSIGHTS= Dynamics 365 AI for Sales + D365_SALES_PRO= Dynamics 365 for Sales Professional + Dynamics_365_for_Operations= Dynamics 365 Unf Ops Plan Ent Edition + ECAL_SERVICES= ECAL + EMS= Enterprise Mobility + Security E3 + EMSPREMIUM= Enterprise Mobility + Security E5 + ENTERPRISEPACK= Office 365 Enterprise E3 + ENTERPRISEPACKLRG= Office 365 Enterprise E3 LRG + ENTERPRISEPACKWITHOUTPROPLUS= Office 365 Enterprise E3 without ProPlus Add-on + ENTERPRISEPACK_B_PILOT= Office 365 (Enterprise Preview) + ENTERPRISEPACK_FACULTY= Office 365 (Plan A3) for Faculty + ENTERPRISEPACK_GOV= Microsoft Office 365 (Plan G3) for Government + ENTERPRISEPACK_STUDENT= Office 365 (Plan A3) for Students + ENTERPRISEPREMIUM= Enterprise E5 (with Audio Conferencing) + ENTERPRISEPREMIUM_NOPSTNCONF= Enterprise E5 (without Audio Conferencing) + ENTERPRISEWITHSCAL= Office 365 Enterprise E4 + ENTERPRISEWITHSCAL_FACULTY= Office 365 (Plan A4) for Faculty + ENTERPRISEWITHSCAL_GOV= Microsoft Office 365 (Plan G4) for Government + ENTERPRISEWITHSCAL_STUDENT= Office 365 (Plan A4) for Students + EOP_ENTERPRISE= Exchange Online Protection + EOP_ENTERPRISE_FACULTY= Exchange Online Protection for Faculty + EQUIVIO_ANALYTICS= Office 365 Advanced Compliance + EQUIVIO_ANALYTICS_FACULTY= Office 365 Advanced Compliance for Faculty + ESKLESSWOFFPACK_GOV= Microsoft Office 365 (Plan K2) for Government + EXCHANGEARCHIVE= Exchange Online Archiving + EXCHANGEARCHIVE_ADDON= Exchange Online Archiving for Exchange Online + EXCHANGEDESKLESS= Exchange Online Kiosk + EXCHANGEENTERPRISE= Exchange Online Plan 2 + EXCHANGEENTERPRISE_FACULTY= Exch Online Plan 2 for Faculty + EXCHANGEENTERPRISE_GOV= Microsoft Office 365 Exchange Online (Plan 2) only for Government + EXCHANGEESSENTIALS= Exchange Online Essentials + EXCHANGESTANDARD= Office 365 Exchange Online Only + EXCHANGESTANDARD_GOV= Microsoft Office 365 Exchange Online (Plan 1) only for Government + EXCHANGESTANDARD_STUDENT= Exchange Online (Plan 1) for Students + EXCHANGETELCO= Exchange Online POP + EXCHANGE_ANALYTICS= Microsoft MyAnalytics + EXCHANGE_L_STANDARD= Exchange Online (Plan 1) + EXCHANGE_S_ARCHIVE_ADDON_GOV= Exchange Online Archiving + EXCHANGE_S_DESKLESS= Exchange Online Kiosk + EXCHANGE_S_DESKLESS_GOV= Exchange Kiosk + EXCHANGE_S_ENTERPRISE= Exchange Online (Plan 2) Ent + EXCHANGE_S_ENTERPRISE_GOV= Exchange Plan 2G + EXCHANGE_S_ESSENTIALS= Exchange Online Essentials + EXCHANGE_S_FOUNDATION= Exchange Foundation for certain SKUs + EXCHANGE_S_STANDARD= Exchange Online (Plan 2) + EXCHANGE_S_STANDARD_MIDMARKET= Exchange Online (Plan 1) + FLOW_FREE= Microsoft Flow (Free) + FLOW_O365_P2= Flow for Office 365 + FLOW_O365_P3= Flow for Office 365 + FLOW_P1= Microsoft Flow Plan 1 + FLOW_P2= Microsoft Flow Plan 2 + FORMS_PLAN_E3= Microsoft Forms (Plan E3) + FORMS_PLAN_E5= Microsoft Forms (Plan E5) + INFOPROTECTION_P2= Azure Information Protection Premium P2 + INTUNE_A= Windows Intune Plan A + INTUNE_A_VL= Intune (Volume License) + INTUNE_O365= Mobile Device Management for Office 365 + INTUNE_STORAGE= Intune Extra Storage + IT_ACADEMY_AD= Microsoft Imagine Academy + LITEPACK= Office 365 (Plan P1) + LITEPACK_P2= Office 365 Small Business Premium + LOCKBOX= Customer Lockbox + LOCKBOX_ENTERPRISE= Customer Lockbox + MCOCAP= Command Area Phone + MCOEV= Skype for Business Cloud PBX + MCOIMP= Skype for Business Online (Plan 1) + MCOLITE= Lync Online (Plan 1) + MCOMEETADV= PSTN conferencing + MCOPLUSCAL= Skype for Business Plus CAL + MCOPSTN1= Skype for Business Pstn Domestic Calling + MCOPSTN2= Skype for Business Pstn Domestic and International Calling + MCOSTANDARD= Skype for Business Online Standalone Plan 2 + MCOSTANDARD_GOV= Lync Plan 2G + MCOSTANDARD_MIDMARKET= Lync Online (Plan 1) + MCVOICECONF= Lync Online (Plan 3) + MDM_SALES_COLLABORATION= Microsoft Dynamics Marketing Sales Collaboration + MEE_FACULTY= Minecraft Education Edition Faculty + MEE_STUDENT= Minecraft Education Edition Student + MEETING_ROOM= Meeting Room + MFA_PREMIUM= Azure Multi-Factor Authentication + MICROSOFT_BUSINESS_CENTER= Microsoft Business Center + MICROSOFT_REMOTE_ASSIST= Dynamics 365 Remote Assist + MIDSIZEPACK= Office 365 Midsize Business + MINECRAFT_EDUCATION_EDITION= Minecraft Education Edition Faculty + MS-AZR-0145P= Azure + MS_TEAMS_IW= Microsoft Teams + NBPOSTS= Microsoft Social Engagement Additional 10k Posts (minimum 100 licenses) (Government Pricing) + NBPROFESSIONALFORCRM= Microsoft Social Listening Professional + O365_BUSINESS= Microsoft 365 Apps for business + O365_BUSINESS_ESSENTIALS= Microsoft 365 Business Basic + O365_BUSINESS_PREMIUM= Microsoft 365 Business Standard + OFFICE365_MULTIGEO= Multi-Geo Capabilities in Office 365 + OFFICESUBSCRIPTION= Microsoft 365 Apps for enterprise + OFFICESUBSCRIPTION_FACULTY= Office 365 ProPlus for Faculty + OFFICESUBSCRIPTION_GOV= Office ProPlus + OFFICESUBSCRIPTION_STUDENT= Office ProPlus Student Benefit + OFFICE_FORMS_PLAN_2= Microsoft Forms (Plan 2) + OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ= Office ProPlus + ONEDRIVESTANDARD= OneDrive + PAM_ENTERPRISE = Exchange Primary Active Manager + PLANNERSTANDALONE= Planner Standalone + POWERAPPS_INDIVIDUAL_USER= Microsoft PowerApps and Logic flows + POWERAPPS_O365_P2= PowerApps + POWERAPPS_O365_P3= PowerApps for Office 365 + POWERAPPS_VIRAL= PowerApps (Free) + POWERFLOW_P1= Microsoft PowerApps Plan 1 + POWERFLOW_P2= Microsoft PowerApps Plan 2 + POWER_BI_ADDON= Office 365 Power BI Addon + POWER_BI_INDIVIDUAL_USE= Power BI Individual User + POWER_BI_INDIVIDUAL_USER= Power BI for Office 365 Individual + POWER_BI_PRO= Power BI Pro + POWER_BI_STANDALONE= Power BI Standalone + POWER_BI_STANDARD= Power-BI Standard + PREMIUM_ADMINDROID= AdminDroid Office 365 Reporter + PROJECTCLIENT= Project Professional + PROJECTESSENTIALS= Project Lite + PROJECTONLINE_PLAN_1= Project Online (Plan 1) + PROJECTONLINE_PLAN_1_FACULTY= Project Online for Faculty Plan 1 + PROJECTONLINE_PLAN_1_STUDENT= Project Online for Students Plan 1 + PROJECTONLINE_PLAN_2= Project Online and PRO + PROJECTONLINE_PLAN_2_FACULTY= Project Online for Faculty Plan 2 + PROJECTONLINE_PLAN_2_STUDENT= Project Online for Students Plan 2 + PROJECTPREMIUM= Project Online Premium + PROJECTPROFESSIONAL= Project Online Pro + PROJECTWORKMANAGEMENT= Office 365 Planner Preview + PROJECT_CLIENT_SUBSCRIPTION= Project Pro for Office 365 + PROJECT_ESSENTIALS= Project Lite + PROJECT_MADEIRA_PREVIEW_IW_SKU= Dynamics 365 for Financials for IWs + PROJECT_ONLINE_PRO= Project Online Plan 3 + RIGHTSMANAGEMENT= Azure Rights Management Premium + RIGHTSMANAGEMENT_ADHOC= Windows Azure Rights Management + RIGHTSMANAGEMENT_STANDARD_FACULTY= Azure Rights Management for faculty + RIGHTSMANAGEMENT_STANDARD_STUDENT= Information Rights Management for Students + RMS_S_ENTERPRISE= Azure Active Directory Rights Management + RMS_S_ENTERPRISE_GOV= Windows Azure Active Directory Rights Management + RMS_S_PREMIUM= Azure Information Protection Plan 1 + RMS_S_PREMIUM2= Azure Information Protection Premium P2 + SCHOOL_DATA_SYNC_P1= School Data Sync (Plan 1) + SHAREPOINTDESKLESS= SharePoint Online Kiosk + SHAREPOINTDESKLESS_GOV= SharePoint Online Kiosk + SHAREPOINTENTERPRISE= SharePoint Online (Plan 2) + SHAREPOINTENTERPRISE_EDU= SharePoint Plan 2 for EDU + SHAREPOINTENTERPRISE_GOV= SharePoint Plan 2G + SHAREPOINTENTERPRISE_MIDMARKET= SharePoint Online (Plan 1) + SHAREPOINTLITE= SharePoint Online (Plan 1) + SHAREPOINTPARTNER= SharePoint Online Partner Access + SHAREPOINTSTANDARD= SharePoint Online Plan 1 + SHAREPOINTSTANDARD_EDU= SharePoint Plan 1 for EDU + SHAREPOINTSTORAGE= SharePoint Online Storage + SHAREPOINTWAC= Office Online + SHAREPOINTWAC_EDU= Office Online for Education + SHAREPOINTWAC_GOV= Office Online for Government + SHAREPOINT_PROJECT= SharePoint Online (Plan 2) Project + SHAREPOINT_PROJECT_EDU= Project Online Service for Education + SMB_APPS= Business Apps (free) + SMB_BUSINESS= Office 365 Business + SMB_BUSINESS_ESSENTIALS= Office 365 Business Essentials + SMB_BUSINESS_PREMIUM= Office 365 Business Premium + SPZA IW= Microsoft PowerApps Plan 2 Trial + SPB= Microsoft 365 Business + SPE_E3= Secure Productive Enterprise E3 + SQL_IS_SSIM= Power BI Information Services + STANDARDPACK= Office 365 (Plan E1) + STANDARDPACK_FACULTY= Office 365 (Plan A1) for Faculty + STANDARDPACK_GOV= Microsoft Office 365 (Plan G1) for Government + STANDARDPACK_STUDENT= Office 365 (Plan A1) for Students + STANDARDWOFFPACK= Office 365 (Plan E2) + STANDARDWOFFPACKPACK_FACULTY= Office 365 (Plan A2) for Faculty + STANDARDWOFFPACKPACK_STUDENT= Office 365 (Plan A2) for Students + STANDARDWOFFPACK_FACULTY= Office 365 Education E1 for Faculty + STANDARDWOFFPACK_GOV= Microsoft Office 365 (Plan G2) for Government + STANDARDWOFFPACK_IW_FACULTY= Office 365 Education for Faculty + STANDARDWOFFPACK_IW_STUDENT= Office 365 Education for Students + STANDARDWOFFPACK_STUDENT= Microsoft Office 365 (Plan A2) for Students + STANDARD_B_PILOT= Office 365 (Small Business Preview) + STREAM= Microsoft Stream + STREAM_O365_E3= Microsoft Stream for O365 E3 SKU + STREAM_O365_E5= Microsoft Stream for O365 E5 SKU + SWAY= Sway + TEAMS1= Microsoft Teams + TEAMS_COMMERCIAL_TRIAL= Microsoft Teams Commercial Cloud Trial + THREAT_INTELLIGENCE= Office 365 Threat Intelligence + VIDEO_INTEROP = Skype Meeting Video Interop for Skype for Business + VISIOCLIENT= Visio Online Plan 2 + VISIOONLINE_PLAN1= Visio Online Plan 1 + VISIO_CLIENT_SUBSCRIPTION= Visio Pro for Office 365 + WACONEDRIVEENTERPRISE= OneDrive for Business (Plan 2) + WACONEDRIVESTANDARD= OneDrive for Business with Office Online + WACSHAREPOINTSTD= Office Online STD + WHITEBOARD_PLAN3= White Board (Plan 3) + WIN_DEF_ATP= Windows Defender Advanced Threat Protection + WIN10_PRO_ENT_SUB= Windows 10 Enterprise E3 + WIN10_VDA_E3= Windows E3 + WIN10_VDA_E5= Windows E5 + WINDOWS_STORE= Windows Store + YAMMER_EDU= Yammer for Academic + YAMMER_ENTERPRISE= Yammer for the Starship Enterprise + YAMMER_ENTERPRISE_STANDALONE= Yammer Enterprise + YAMMER_MIDSIZE= Yammer \ No newline at end of file diff --git a/Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.ps1 b/Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.ps1 new file mode 100644 index 0000000..95c3fe2 --- /dev/null +++ b/Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.ps1 @@ -0,0 +1,475 @@ +<# +============================================================================================= +Name: Manage Microsoft 365 licenses using MS Graph PowerShell +Description: This script can perform 10+ Office 365 reporting and management activities +website: o365reports.com +Script by: O365Reports Team + +Script Highlights : +~~~~~~~~~~~~~~~~~ + +1. The script uses MS Graph PowerShell module. +2. Generates 5 Office 365 license reports. +3. Allows you to perform 6 license management actions that include adding or removing licenses in bulk. +4. License Name is shown with its friendly name like ‘Office 365 Enterprise E3’ rather than ‘ENTERPRISEPACK’. +5. Automatically installs MS Graph PowerShell module (if not installed already) upon your confirmation. +6. The script can be executed with an MFA enabled account too. +7. Exports the report result to CSV. +8. Exports license assignment and removal log file. + + +For detailed Script execution: https://o365reports.com/2022/09/08/manage-365-licenses-using-ms-graph-powershell +============================================================================================ +#> +Param +( + [Parameter(Mandatory = $false)] + [string]$LicenseName, + [string]$LicenseUsageLocation, + [int]$Action, + [switch]$MultipleActionsMode +) + +function Connect_MgGraph { + $MsGraphBetaModule = Get-Module Microsoft.Graph.Beta -ListAvailable + if($MsGraphBetaModule -eq $null) + { + Write-host "Important: Microsoft Graph Beta module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." + $confirm = Read-Host Are you sure you want to install Microsoft Graph Beta module? [Y] Yes [N] No + if($confirm -match "[yY]") + { + Write-host "Installing Microsoft Graph Beta module..." + Install-Module Microsoft.Graph.Beta -Scope CurrentUser -AllowClobber + Write-host "Microsoft Graph Beta module is installed in the machine successfully" -ForegroundColor Magenta + } + else + { + Write-host "Exiting. `nNote: Microsoft Graph Beta module must be available in your system to run the script" -ForegroundColor Red + Exit + } + } + Write-Progress "Importing Required Modules..." + Import-Module -Name Microsoft.Graph.Beta.Identity.DirectoryManagement + Import-Module -Name Microsoft.Graph.Beta.Users + Import-Module -Name Microsoft.Graph.Beta.Users.Actions + Write-Progress "Connecting MgGraph Module..." + Connect-MgGraph -Scopes "Directory.ReadWrite.All" +} +Function Open_OutputFile { + #Open output file after execution + if ((Test-Path -Path $OutputCSVName) -eq "True") { + if ($ActionFlag -eq "Report") { + Write-Host Detailed license report is available in: -NoNewline -Foregroundcolor Yellow; Write-Host $OutputCSVName + Write-Host The report has $ProcessedCount records. + } + elseif ($ActionFlag -eq "Mgmt") { + Write-Host License assignment/removal log file is available in: -NoNewline -Foregroundcolor Yellow; Write-Host $OutputCSVName + } + $Prompt = New-Object -ComObject wscript.shell + $UserInput = $Prompt.popup("Do you want to open output file?", 0, "Open Output File", 4) + If ($UserInput -eq 6) { + Invoke-Item "$OutputCSVName" + } + } + else { + Write-Host No records found + } + Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green + Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n + Write-Progress -Activity Export CSV -Completed +} + +#Get user's details +Function Get_UserInfo { + $global:DisplayName = $_.DisplayName + $global:UPN = $_.UserPrincipalName + $global:Licenses = $_.AssignedLicenses.SkuId + $SigninStatus = $_.AccountEnabled + if ($SigninStatus -eq $False) { + $global:SigninStatus = "Disabled" + } + else { + $global:SigninStatus = "Enabled" + } + $global:Department = $_.Department + $global:JobTitle = $_.JobTitle + if ($Department -eq $null) { + $global:Department = "-" + } + if ($JobTitle -eq $null) { + $global:JobTitle = "-" + } +} + +Function Get_License_FriendlyName { + $FriendlyName = @() + $LicensePlan = @() + #Convert license plan to friendly name + foreach ($License in $Licenses) { + $LicenseItem = $SkuIdHash[$License] + $EasyName = $FriendlyNameHash[$LicenseItem] + if (!($EasyName)) { + $NamePrint = $LicenseItem + } + else { + $NamePrint = $EasyName + } + $FriendlyName = $FriendlyName + $NamePrint + $LicensePlan = $LicensePlan + $LicenseItem + } + $global:LicensePlans = $LicensePlan -join "," + $global:FriendlyNames = $FriendlyName -join "," +} + +Function Set_UsageLocation { + if ($LicenseUsageLocation -ne "") { + "Assigning Usage Location $LicenseUsageLocation to $UPN" | Out-File $OutputCSVName -Append + Update-MgBetaUser -UserId $UPN -UsageLocation $LicenseUsageLocation + } + else { + "Usage location is mandatory to assign license. Please set Usage location for $UPN" | Out-File $OutputCSVName -Append + } +} + +Function Assign_Licenses { + "Assigning $LicenseNames license to $UPN" | Out-File $OutputCSVName -Append + Set-MgBetaUserLicense -UserId $UPN -AddLicenses @{SkuId = $SkuPartNumberHash[$LicenseNames] } -RemoveLicenses @() | Out-Null + if ($?) { + "License assigned successfully" | Out-File $OutputCSVName -Append + } + else { + "License assignment failed" | Out-file $OutputCSVName -Append + } +} + +Function Remove_Licenses { + $SkuPartNumber = @() + foreach ($Temp in $License) { + $SkuPartNumber += $SkuIdHash[$Temp] + } + $SkuPartNumber = $SkuPartNumber -join (",") + Write-Progress -Activity "`n Removing $SkuPartNumber license from $UPN "`n" Processed users: $ProcessedCount" + "Removing $SkuPartNumber license from $UPN" | Out-File $OutputCSVName -Append + Set-MgBetaUserLicense -UserId $UPN -RemoveLicenses @($License) -AddLicenses @() | Out-Null + if ($?) { + "License removed successfully" | Out-File $OutputCSVName -Append + } + else { + "License removal failed" | Out-file $OutputCSVName -Append + } +} + +Function main() { + Disconnect-MgGraph -ErrorAction SilentlyContinue|Out-Null + Connect_MgGraph + Write-Host "`nNote: If you encounter module related conflicts, run the script in a fresh PowerShell window." -ForegroundColor Yellow + $Result = "" + $Results = @() + $FriendlyNameHash = Get-Content -Raw -Path .\LicenseFriendlyName.txt -ErrorAction Stop | ConvertFrom-StringData + $SkuPartNumberHash = @{} + $SkuIdHash = @{} + Get-MgBetaSubscribedSku -All | Select-Object SkuPartNumber, SkuId | ForEach-Object { + $SkuPartNumberHash.add(($_.SkuPartNumber), ($_.SkuId)) + $SkuIdHash.add(($_.SkuId), ($_.SkuPartNumber)) + } + + Do { + if ($Action -eq "") { + Write-Host "" + Write-host `nOffice 365 License Reporting -ForegroundColor Yellow + Write-Host " 1.Get all licensed users" -ForegroundColor Cyan + Write-Host " 2.Get all unlicensed users" -ForegroundColor Cyan + Write-Host " 3.Get users with specific license type" -ForegroundColor Cyan + Write-Host " 4.Get all disabled users with licenses" -ForegroundColor Cyan + Write-Host " 5.Office 365 license usage report" -ForegroundColor Cyan + Write-Host `nOffice 365 License Management -ForegroundColor Yellow + Write-Host " 6.Bulk:Assign a license to users (input CSV)" -ForegroundColor Cyan + Write-Host " 7.Bulk:Assign multiple licenses to users (input CSV)" -ForegroundColor Cyan + Write-Host " 8.Remove all license from a user" -ForegroundColor Cyan + Write-Host " 9.Bulk:Remove all licenses from users (input CSV)" -ForegroundColor Cyan + Write-Host " 10.Remove specific license from all users" -ForegroundColor Cyan + Write-Host " 11.Remove all license from disabled users" -ForegroundColor Cyan + Write-Host " 0.Exit" -ForegroundColor Cyan + Write-Host "" + $GetAction = Read-Host 'Please choose the action to continue' + } + else { + $GetAction = $Action + } + + Switch ($GetAction) { + 1 { + $OutputCSVName = ".\O365UserLicenseReport_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" + Write-Host Generating licensed users report... + $ProcessedCount = 0 + Get-MgBetaUser -All | Where-Object {($_.AssignedLicenses.Count) -ne 0 } | ForEach-Object { + $ProcessedCount++ + Get_UserInfo + Write-Progress -Activity "`n Processed users count: $ProcessedCount "`n" Currently Processing: $DisplayName" + Get_License_FriendlyName + $Result = @{'Display Name' = $Displayname; 'UPN' = $UPN; 'License Plan' = $LicensePlans; 'License Plan Friendly Name' = $FriendlyNames; 'Account Status' = $SigninStatus; 'Department' = $Department; 'Job Title' = $JobTitle } + $Results = New-Object PSObject -Property $Result + $Results | select-object 'Display Name', 'UPN', 'License Plan', 'License Plan Friendly Name', 'Account Status', 'Department', 'Job Title' | Export-Csv -Path $OutputCSVName -Notype -Append + } + $ActionFlag = "Report" + Open_OutputFile + } + + 2 { + $OutputCSVName = ".\O365UnlicenedUserReport_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" + Write-Host Generating Unlicensed users report... + $ProcessedCount = 0 + Get-MgBetaUser -All | Where-Object {($_.AssignedLicenses.Count) -eq 0 } | ForEach-Object { + $ProcessedCount++ + Get_UserInfo + Write-Progress -Activity "`n Processed users count: $ProcessedCount "`n" Currently Processing: $DisplayName" + $Result = @{'Display Name' = $Displayname; 'UPN' = $UPN; 'Department' = $Department; 'Signin Status' = $SigninStatus; 'Job Title' = $JobTitle } + $Results = New-Object PSObject -Property $Result + $Results | select-object 'Display Name', 'UPN', 'Department', 'Job Title', 'Signin Status' | Export-Csv -Path $OutputCSVName -Notype -Append + } + $ActionFlag = "Report" + Open_OutputFile + } + + 3 { + $OutputCSVName = "./O365UsersWithSpecificLicenseReport__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" + if ($LicenseName -eq "") { + $LicenseName = Read-Host "Enter the license SKU(Eg:Enterprisepack)" + } + Write-Host Getting users with $LicenseName license... + $ProcessedCount = 0 + if ($SkuPartNumberHash.Keys -icontains $LicenseName) { + Get-MgBetaUser -All | Where-Object{(($_.AssignedLicenses).SkuId) -eq $SkuPartNumberHash[$LicenseName]} | ForEach-Object { + $ProcessedCount++ + Get_UserInfo + Write-Progress -Activity "`n Processed users count: $ProcessedCount "`n" Currently Processing: $DisplayName" + Get_License_FriendlyName + $Result = @{'Display Name' = $Displayname; 'UPN' = $UPN; 'License Plan' = $LicensePlans; 'License Plan_Friendly Name' = $FriendlyNames; 'Account Status' = $SigninStatus; 'Department' = $Department; 'Job Title' = $JobTitle } + $Results = New-Object PSObject -Property $Result + $Results | select-object 'Display Name', 'UPN', 'License Plan', 'License Plan_Friendly Name', 'Account Status', 'Department', 'Job Title' | Export-Csv -Path $OutputCSVName -Notype -Append + } + } + else { + Write-Host $LicenseName is not used in your organization. Please check the license name or run the License Usage Report to know the licenses in your org -ForegroundColor Red + } + #Clearing license name for next iteration + $LicenseName = "" + $ActionFlag = "Report" + Open_OutputFile + } + + 4 { + $OutputCSVName = "./O365DiabledUsersWithLicense__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" + $ProcessedCount = 0 + Write-Host Finding disabled users still licensed in Office 365... + Get-MgBetaUser -All | Where-Object { ($_.AccountEnabled -eq $false) -and (($_.AssignedLicenses).Count -ne 0) } | ForEach-Object { + $ProcessedCount++ + Get_UserInfo + Write-Progress -Activity "`n Processed users count: $ProcessedCount "`n" Currently Processing: $DisplayName" + Get_License_FriendlyName + $Result = @{'Display Name' = $Displayname; 'UPN' = $UPN; 'License Plan' = $LicensePlans; 'License Plan_Friendly Name' = $FriendlyNames; 'Department' = $Department; 'Job Title' = $JobTitle } + $Results = New-Object PSObject -Property $Result + $Results | select-object 'Display Name', 'UPN', 'License Plan', 'License Plan_Friendly Name', 'Department', 'Job Title' | Export-Csv -Path $OutputCSVName -Notype -Append + } + $ActionFlag = "Report" + Open_OutputFile + } + + 5 { + $OutputCSVName = "./Office365LicenseUsageReport__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" + Write-Host Generating Office 365 license usage report... + $ProcessedCount = 0 + Get-MgBetaSubscribedSku | ForEach-Object { + $ProcessedCount++ + $AccountSkuID = $_.SkuID + $LicensePlan = $_.SkuPartNumber + $ActiveUnits = $_.PrepaidUnits.Enabled + $ConsumedUnits = $_.ConsumedUnits + Write-Progress -Activity "`n Retrieving license info "`n" Currently Processing: $LicensePlan" + $EasyName = $FriendlyNameHash[$LicensePlan] + if (!($EasyName)) + { $FriendlyName = $LicensePlan } + else + { $FriendlyName = $EasyName } + $Result = @{'AccountSkuId' = $AccountSkuID;'AccountSkuPartNumber' = $LicensePlan; 'License Plan_Friendly Name' = $FriendlyName; 'Active Units' = $ActiveUnits; 'Consumed Units' = $ConsumedUnits } + $Results = New-Object PSObject -Property $Result + $Results | select-object 'AccountSkuId','AccountSkuPartNumber', 'License Plan_Friendly Name', 'Active Units', 'Consumed Units' | Export-Csv -Path $OutputCSVName -Notype -Append + } + $ActionFlag = "Report" + Open_OutputFile + } + + 6 { + $OutputCSVName = "./Office365LicenseAssignment_Log__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" + $UserNamesFile = Read-Host "Enter the CSV file containing user names(Eg:D:/UserNames.txt)" + #We have an input file, read it into memory + $UserNames = @() + $UserNames = Import-Csv -Header "UPN" $UserNamesFile + $ProcessedCount = 0 + $LicenseNames = Read-Host "Enter the license name(Eg:Enterprisepack)" + Write-Host Assigning license to users... + if ($SkuPartNumberHash.Keys -icontains $LicenseNames) { + foreach ($Item in $UserNames) { + $ProcessedCount++ + $UPN = $Item.UPN + Write-Progress -Activity "`n Assigning $LicenseNames license to $UPN "`n" Processed users: $ProcessedCount" + $UsageLocation = (Get-MgBetaUser -UserId $UPN).UsageLocation + if ($UsageLocation -eq $null) { + Set_UsageLocation + } + else { + Assign_Licenses + } + } + } + else { + Write-Host $LicenseNames is not used in your organization. Please check the license name or run the License Usage Report to know the licenses in your org -ForegroundColor Red + } + #Clearing license name and input file location for next iteration + $LicenseNames = "" + $UserNamesFile = "" + $ActionFlag = "Mgmt" + Open_OutputFile + } + + 7 { + $OutputCSVName = "./Office365LicenseAssignment_Log__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" + $UserNamesFile = Read-Host "Enter the CSV file containing user names(Eg:D:/UserNames.txt)" + #We have an input file, read it into memory + $UserNames = @() + $UserNames = Import-Csv -Header "UPN" $UserNamesFile + $Flag = "" + $ProcessedCount = 0 + $License = Read-Host "Enter the license names(Eg:LicensePlan1,LicensePlan2)" + $License = $License.Replace(' ', '') + $License = $License.split(",") + foreach ($LicenseName in $License) { + if ($SkuPartNumberHash.Keys -inotcontains $LicenseName) { + $Flag = "Terminate" + Write-Host $LicenseName is not used in your organization. Please check the license name or run the License Usage Report to know the licenses in your org -ForegroundColor Red + } + } + if ($Flag -eq "Terminate") { + Write-Host Please re-run the script with appropriate license name -ForegroundColor Yellow + } + else { + Write-Host Assigning licenses to Office 365 users... + foreach ($Item in $UserNames) { + $UPN = $Item.UPN + $ProcessedCount++ + $UsageLocation = (Get-MgBetaUser -UserId $UPN).UsageLocation + if ($UsageLocation -eq $null) { + Set_UsageLocation + } + else { + Write-Progress -Activity "`n Assigning licenses to $UPN "`n" Processed users: $ProcessedCount" + foreach ($LicenseNames in $License) { + Assign_Licenses + } + } + } + } + #Clearing license names and input file location for next iteration + $LicenseNames = "" + $UserNamesFile = "" + $ActionFlag = "Mgmt" + Open_OutputFile + } + + + 8 { + $Identity = Read-Host Enter User UPN + $UserInfo = Get-MgBetaUser -UserId $Identity + #Checking whether the user is available + if ($UserInfo -eq $null) { + Write-Host User $Identity does not exist. Please check the user name. -ForegroundColor Red + } + else { + $Licenses = $UserInfo.AssignedLicenses.SkuId + $SkuPartNumber = @() + if ($Licenses.count -eq 0) { + Write-Host No license assigned to the user $Identity. + } + else { + foreach ($Temp in $Licenses) { + $SkuPartNumber += $SkuIdHash[$Temp] + } + $SkuPartNumber = $SkuPartNumber -join (",") + Write-Host Removing $SkuPartNumber license from $Identity + Set-MgBetaUserLicense -UserId $Identity -RemoveLicenses @($Licenses) -AddLicenses @() | Out-Null + Write-Host Action completed -ForegroundColor Green + } + } + } + + 9 { + $OutputCSVName = "./Office365LicenseRemoval_Log__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" + $UserNamesFile = Read-Host "Enter the CSV file containing user names(Eg:D:/UserNames.txt)" + #We have an input file, read it into memory + $UserNames = @() + $UserNames = Import-Csv -Header "UPN" $UserNamesFile + $ProcessedCount = 0 + foreach ($Item in $UserNames) { + $UPN = $Item.UPN + $ProcessedCount++ + $License = (Get-MgBetaUser -UserId $UPN).AssignedLicenses.SkuId + if ($License.count -eq 0) { + "No License Assigned to this user $UPN" | Out-File $OutputCSVName -Append + } + else { + Remove_Licenses + } + } + $ActionFlag = "Mgmt" + Open_OutputFile + } + + 10 { + $OutputCSVName = "./O365LicenseRemoval_Log__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" + $Licenses = Read-Host "Enter the license name(Eg:LicensePlan)" + $License = $SkuPartNumberHash[$Licenses] + $ProcessedCount = 0 + if ($SkuPartNumberHash.Values -icontains $License) { + Get-MgBetaUser -All | Where-Object { ($_.AssignedLicenses).SkuId -eq $License } | ForEach-Object { + $ProcessedCount++ + $UPN = $_.UserPrincipalName + Remove_Licenses + } + } + else { + Write-Host $License not used in your organization. Please check the license name or run the License Usage Report to know the licenses in your org -ForegroundColor Red + } + $ActionFlag = "Mgmt" + Open_OutputFile + } + + 11 { + $OutputCSVName = "./O365LicenseRemoval_Log__$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" + Write-Host Removing license from disabled users... + $ProcessedCount = 0 + Get-MgBetaUser -All | Where-Object { ($_.AccountEnabled -eq $false) -and (($_.AssignedLicenses).Count -ne 0) } | ForEach-Object { + $ProcessedCount++ + $UPN = $_.UserPrincipalName + $License = $_.AssignedLicenses.SkuId + Remove_Licenses + } + $ActionFlag = "Mgmt" + Open_OutputFile + } + } + if ($Action -ne "") { + exit + } + if ($MultipleActionsMode.ispresent) { + Start-Sleep -Seconds 2 + } + else { + Exit + } + } + While ($GetAction -ne 0) + Disconnect-MgGraph + Write-Host "Disconnected active Microsoft Graph session" + Clear-Host +} +. main \ No newline at end of file diff --git a/Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.zip b/Manage Microsoft 365 Licenses using MS Graph/ManageM365Licenses.zip deleted file mode 100644 index 042d28fa7af713f24dfcfa54f581fbb5e0903ca2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7855 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 YD_2*7NBH|~VE^jI&`?mKq