{"id":196172,"date":"2023-08-31T16:10:28","date_gmt":"2023-08-31T21:10:28","guid":{"rendered":"https:\/\/itblog.ldlnet.net\/?p=196172"},"modified":"2024-05-10T08:34:44","modified_gmt":"2024-05-10T13:34:44","slug":"get-m365licenseinfo-ps1","status":"publish","type":"post","link":"https:\/\/itblog.ldlnet.net\/index.php\/2023\/08\/31\/get-m365licenseinfo-ps1\/","title":{"rendered":"Get-M365LicenseInfo.ps1"},"content":{"rendered":"\n<p>One of my clients needed a script that would pull all the License and Add-Ons information from PowerShell and put it into a CSV that could be filtered by license or add-on. So I created this script to do just that. <br>It had a few revisions, but I do have final one here for usage. As always, if you have suggestions on making it better, please feel free to contact me. I am working on updating my repository on GitHub @LanceL218 and will have these scripts up there soon as well!<\/p>\n\n\n<pre class=\"lang:PowerShell nums:True\" title=\"Get-M365LicenseInfo.ps1\">\n<#\n\n.SYNOPSIS\n\nGet an M365 License Report of all members of a Commercial or Government Tenant M365 License Distribution. It will tell you what licneses they have based on if they have a mailbox enabled on their account.\nThe report will be in CSV Format and will output to a predetermined directory based on the Parameters set in the script.\n\n \n\n.NOTES\n\n    Name: Get-M365LicenseInfo\n\n    Author: Lance Lingerfelt\n\n    Version: 1.0\n\n    Modify Date: 2023-08-24\n\n    Parameter Values:\n\n    $ReportsPath sets the path to a default directory and will create the directory if needed later in the script. Setting a value is NOT mandatory but script will setup a directory of C:\\LDLNETScripts\\Reports\n    $TenantEXO must use one of the following values: O365USGovGCCHigh or O365USGovDoD or O365Default (for commercial)\n    $TenantAAD must use one of the following values: AzureUSGovernment or AzureCloud (for commercial)\n\n.EXAMPLE\n\nConnect to a Government Cloud Tenant in the Script\n\n    .\\Get-M365LicenseInfo.ps1 -TenantEXO O365USGovGCCHigh -TenantAAD AzureUSGovernment\n\nConnect to Default Commerical Tenant in the Script\n\n    .\\Get-M365LicenseInfo.ps1 -TenantEXO O365Default -TenantAAD AzureCloud\n#>\n\n[CmdletBinding(SupportsShouldProcess = $true)]\n\nParam(\n\n    [Parameter(Mandatory = $false)]\n\n    [string] $ReportsPath = \"C:\\LDLNETScripts\\Reports\",\n\n    [Parameter(Mandatory = $true)]\n\n    [ValidateSet(\"O365USGovGCCHigh\",\"O365USGovDoD\", \"O365Default\")]\n\n    $TenantEXO,\n    \n    [Parameter(Mandatory = $true)]\n\n    [ValidateSet(\"AzureUSGovernment\",\"AzureCloud\")]\n\n    $TenantAAD\n\n)\n\n# ================================================\n#               DO NOT MODIFY BEGIN\n# ================================================\n\n$ErrorActionPreference = 'SilentlyContinue'\n\n$Date = Get-Date -Format \"MM\/dd\/yyyy\"\n\n# Set Logging Configuration\n$Log = [PSCustomObject]@{\n    Path = \"C:\\MatlockScripts\\Logs\\Get-M365LicenseInfo\"\n    Name = \"$($Date).log\"\n}\n\n# ================================================\n#                DO NOT MODIFY END\n# ================================================\n\n# ================================================\n#                   SCRIPT BEGIN\n# ================================================\n\n# Create New Logger Instance if Enabled\nif ($PSCmdlet.ShouldProcess(\"Create New Logger Instance\", $Log.Path)) {\n    # Import Logger Module\n    try {\n        if ( -not (Get-Module -Name PoShLog -ListAvailable) ) {\n            Install-Module -Name PoShLog -Scope CurrentUser -Force\n        }\n        else {\n            Import-Module -Name PoShLog -Force\n        }\n    }\n    catch {\n        Write-Host -Object \"Unable to import logger module. Error: $($_.Exception.Message)\"\n        exit 1\n    }\n\n    # Create New Logger Instance. Verbose logging level. Log to file and console. Start Logger.\n    New-Logger | `\n        Set-MinimumLevel -Value Verbose | `\n        Add-SinkFile -Path \"$($Log.Path)\\$($Log.Name)\" -OutputTemplate `\n        '{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}' -RollingInterval Day | `\n        Add-SinkConsole | `\n        Start-Logger\n    \n    # Log Start of Script\n    Write-VerboseLog \"Start of Script.\"\n}\n\nif ($PSCmdlet.ShouldProcess(\"Create New Exchange Online Instance\", $Log.Path)) {\n    # Import ExchangeOnlineManagement Module\n    try {\n        if ( -not (Get-Module -Name ExchangeOnlineManagement -ListAvailable) ) {\n            Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force\n        }\n        else {\n            Import-Module -Name ExchangeOnlineManagement -Force\n        }\n    }\n    catch {\n        Write-Host -Object \"Unable to import ExchangeOnlineManagement module. Error: $($_.Exception.Message)\"\n        exit 1\n    }\n\n    if ($PSCmdlet.ShouldProcess(\"Create New AzureAD Instance\", $Log.Path)) {\n        # Import AzureAD Module\n        try {\n            if ( -not (Get-Module -Name AzureAD -ListAvailable) ) {\n                Install-Module -Name AzureAD -Scope CurrentUser -Force\n            }\n            else {\n                Import-Module -Name AzureAD -Force\n            }\n        }\n        catch {\n            Write-Host -Object \"Unable to import AzureAD module. Error: $($_.Exception.Message)\"\n            exit 1\n        }\n\n    }\n\n    # Connect to EXO via ExchangeOnlineManagement Module (GCCHigh)\n    Write-VerboseLog \"Connecting Exchange Online\"\n    #Connect-ExchangeOnline -ShowBanner:$false -ExchangeEnvironmentName O365USGovGCCHigh\n    Connect-ExchangeOnline -ShowBanner:$false -ExchangeEnvironmentName $TenantEXO\n\n    # Connect to AzureAD via AzureAD Module (GCCHigh)\n    Write-VerboseLog \"Connecting AzureAD\"\n    #Connect-AzureAD -AzureEnvironmentName AzureUSGovernment\n    Connect-AzureAD -AzureEnvironmentName $TenantAAD\n\n    # Get all users with a mailbox\n    Write-VerboseLog \"Getting User Mailbox List\"\n    $users = Get-Mailbox -resultsize Unlimited \n\n    # Initialize an array to store the results\n    $licenseInfo = @()\n\n    # Loop through each user to get their M365 license information\n    Write-VerboseLog \"Getting License Info For Each Mailbox User\"\n    foreach ($user in $users) {\n        $userPrincipalName = $user.UserPrincipalName\n        Write-Host \"Checking [$userPrincipalName]\" -ForegroundColor Green\n\n        # Retrieve the user's M365 license information\n        $userLicense = Get-AzureADUserLicenseDetail -ObjectId $user.ExternalDirectoryObjectId\n\n        foreach ($License in $userLicense) {\n\n            foreach ($SKU in $License) {\n                Write-Host \"Found License [$($SKU.SkuPartNumber)]\" -ForegroundColor Cyan\n                $licenseInfo += [PSCustomObject]@{\n                    DisplayName = $user.DisplayName\n                    UPN         = $userPrincipalName\n                    License     = $SKU.SkuPartNumber\n                    Services    = \"\"\n                }\n                foreach ($ServicePlan in $SKU) {\n                    foreach ($Service in $ServicePlan.ServicePlans) {\n                        Write-Host \"Found Service [$($Service.ServicePlanName)]\" -ForegroundColor White\n                        $licenseInfo += [PSCustomObject]@{\n                            DisplayName = $user.DisplayName\n                            UPN         = $userPrincipalName\n                            License     = \"\"\n                            Services    = $Service.ServicePlanName \n                        }\n                    }\n                }\n            }\n        }\n    } \n}\n \n#Create Report Path if not there\n\nif (Test-Path $ReportsPath) {\n    #Do Nothing \n}\nelse {\n    New-Item -Type Directory -Path $ReportsPath\n}\n\n#Export the Results   \n\n$licenseInfo | Export-Csv $ReportsPath\\M365_User_Licenses_$(Get-Date -Format yyyyMMddThhmmss).csv -notypeinformation\n\n# Disconnect from Microsoft 365 PowerShell session\nDisconnect-AzureAD -Confirm:$False\nDisconnect-ExchangeOnline -Confirm:$False\n\nWrite-VerboseLog \"End of Script\"\n# ================================================\n#                   SCRIPT END\n# ================================================\n<\/pre>\n\n\n\n<p>It turns out a report that looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1808\" height=\"470\" src=\"https:\/\/itblog.ldlnet.net\/wp-content\/uploads\/2023\/08\/image-1.png\" alt=\"\" class=\"wp-image-196173\"\/><\/figure>\n\n\n\n<p>Doing the output in this manner allowed me to be able to do filtering on the License and on the Add-Ons. I think that is a better solution for folks needing to know who has what. What I would like to do next with the script is a switch parameter that would rename the SKU and Add-Ons to more palatable names so that you could give it to a C-Suite to review and they not be confused, but that would be A LOT more code. It&#8217;s something to do though!<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-text-align-center\">THANKS FOR VIEWING! <br>TIME TO START GETTING READY FOR MS GRAPH POWERSHELL!<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">About Lance Lingerfelt<\/h2>\n\n\n\n<div class=\"wp-block-media-text is-stacked-on-mobile\" style=\"grid-template-columns:22% auto\"><figure class=\"wp-block-media-text__media\"><img loading=\"lazy\" decoding=\"async\" width=\"468\" height=\"412\" src=\"https:\/\/itblog.ldlnet.net\/wp-content\/uploads\/2024\/03\/ProfLDL1.jpg\" alt=\"Lance Lingerfelt Profile Photo\" class=\"wp-image-196223 size-full\"\/><\/figure><div class=\"wp-block-media-text__content\">\n<p class=\"has-small-font-size\">Lance Lingerfelt is an M365 Specialist and Evangelist with over 20 years of experience in the Information Technology field. Having worked in enterprise environments to small businesses, he is able to adapt and provide the best IT Training and Consultation possible. With a focus on AI, the M365 Stack, and Healthcare, he continues to give back to the community with training, public speaking events, and this blog.<\/p>\n<\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>One of my clients needed a script that would pull all the License and Add-Ons information from PowerShell and put it into<\/p>\n<p class=\"link-more\"><a class=\"myButt \" href=\"https:\/\/itblog.ldlnet.net\/index.php\/2023\/08\/31\/get-m365licenseinfo-ps1\/\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":147,"comment_status":"closed","ping_status":"closed","sticky":true,"template":"","format":"standard","meta":{"footnotes":""},"categories":[265,194,3,266,1],"tags":[311,15,313,8,315,13],"class_list":["post-196172","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft365","category-office365","category-powershell","category-security-and-compliance","category-uncategorized","tag-array","tag-cmdlet","tag-exprot-to-csv","tag-powershell","tag-powershell-script","tag-script","odd"],"_links":{"self":[{"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/posts\/196172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/comments?post=196172"}],"version-history":[{"count":4,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/posts\/196172\/revisions"}],"predecessor-version":[{"id":196302,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/posts\/196172\/revisions\/196302"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/media\/147"}],"wp:attachment":[{"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/media?parent=196172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/categories?post=196172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itblog.ldlnet.net\/index.php\/wp-json\/wp\/v2\/tags?post=196172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}