getOffice365ActiveUserLicenseDetail.ps1
<#PSScriptInfo .VERSION 1.0 .GUID d45e01bb-a56f-440f-ac68-10a981668487 .AUTHOR June Castillote .COMPANYNAME lazyexchangeadmin.com .COPYRIGHT june.castillote@gmail.com .TAGS Office365 GraphAPI OutlookMail OutlookREST Rest API .LICENSEURI https://raw.githubusercontent.com/junecastillote/getOffice365ActiveUserLicenseDetail/master/LICENSE .PROJECTURI https://github.com/junecastillote/getOffice365ActiveUserLicenseDetail .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Get Active Users' Assigned Licenses using MS Graph API and Outlook Mail REST API, capable of sending the resulting CSV by email. #> <# .SYNOPSIS Get Active Users' Assigned Licenses using MS Graph API and Outlook Mail REST API, capable of sending the resulting CSV by email. .EXAMPLE PS C:\> .\getOffice365ActiveUserLicenseDetail.ps1 -appID <appID> -appKey <appKey> -tenantID <tenantID> This example will query the License assignment data from Office 365 and save the resuting CSV file in the ".\Report" folder. .EXAMPLE PS C:\> .\getOffice365ActiveUserLicenseDetail.ps1 -appID <appID> -appKey <appKey> -tenantID <tenantID> -ReportPath C:\Temp This example will query the License assignment data from Office 365 and save the resuting CSV file in the "C:\Temp" folder. .EXAMPLE PS C:\> .\getOffice365ActiveUserLicenseDetail.ps1 -appID <appID> -appKey <appKey> -tenantID <tenantID> -ReportPath C:\Temp -sendEmail $true -From sender@domain.com -To recipient@domain.com This example will query the License assignment data from Office 365 and save the resuting CSV file in the "C:\Temp" folder. Then send the summary report by email with the CSV file attached. #> Param( [CmdletBinding()] [Parameter(Mandatory=$true,Position=0)] [string] $appID, [Parameter(Mandatory=$true,Position=1)] [string] $appKey, [Parameter(Mandatory=$true,Position=2)] [string] $tenantID, [Parameter()] [boolean] $sendEmail=$false, [Parameter()] [string] $From, [Parameter()] [string[]] $To, [Parameter()] [string] $ReportPath, [Parameter()] [string] $LogPath ) #Region FUNCTIONS #.......................................................... Function WriteError { param ( [Parameter(Mandatory=$true,Position=0)] [string]$Message ) Write-Host (get-date -Format "dd-MMM-yyyy hh:mm:ss tt") ": ERROR: $Message" -ForegroundColor RED } Function WriteInfo { param ( [Parameter(Mandatory=$true,Position=0)] [string]$Message ) Write-Host (get-date -Format "dd-MMM-yyyy hh:mm:ss tt") ": INFO: $Message" -ForegroundColor Yellow } #Function to get current system timezone (for PS versions below 5) Function Get-TimeZoneInfo { $tzName = ([System.TimeZone]::CurrentTimeZone).StandardName $tzInfo = [System.TimeZoneInfo]::FindSystemTimeZoneById($tzName) Return $tzInfo } #Function to stop transcribing Function Stop-TxnLogging { $txnLog="" Do { try { Stop-Transcript | Out-Null } catch [System.InvalidOperationException]{ $txnLog="stopped" } } While ($txnLog -ne "stopped") } #Function to Start transcribing Function Start-TxnLogging { param ( [Parameter(Mandatory=$true,Position=0)] [string]$LogFile ) Stop-TxnLogging Start-Transcript $LogFile -Append } #Function to get Script Version and ProjectURI for PS vesions below 5.1 Function Get-ScriptInfo { param ( [Parameter(Mandatory=$true,Position=0)] [string]$Path ) $scriptFile = Get-Content $Path $props = @{ Version = "" ProjectURI = "" } $scriptInfo = New-Object PSObject -Property $props # Get Version foreach ($line in $scriptFile) { if ($line -like ".VERSION*") { $scriptInfo.Version = $line.Split(" ")[1] BREAK } } # Get ProjectURI foreach ($line in $scriptFile) { if ($line -like ".PROJECTURI*") { $scriptInfo.ProjectURI = $line.Split(" ")[1] BREAK } } Remove-Variable scriptFile Return $scriptInfo } #.......................................................... #EndRegion FUNCTIONS Stop-TxnLogging [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $script_root = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition $fileSuffix = (Get-Date -Format "dd-MMM-yyyy") if ($PSVersionTable.psversion.Major -lt 5) { $scriptInfo = Get-ScriptInfo -Path $MyInvocation.MyCommand.Definition } else { $scriptInfo = Test-ScriptFileInfo -Path $MyInvocation.MyCommand.Definition } #Region Paths #.......................................................... #If ReportPath if blank, create the default path if (!$ReportPath) { $ReportPath = "$script_root\Report" } #If ReportPath does not exist, create it if (!(Test-Path $ReportPath)) { New-Item -ItemType Directory -Path $ReportPath | Out-Null } #Define report CSV file $ReportFileNameCSV = "ActiveUserLicenseDetail_$fileSuffix.csv" $ReportFileNameHTML = "ActiveUserLicenseCount_$fileSuffix.HTML" $ReportCSV = "$ReportPath\$ReportFileNameCSV" $ReportHTML = "$ReportPath\$ReportFileNameHTML" #If LogPath if blank, create the default path if (!$LogPath) { $LogPath = "$script_root\Log" } #If ReportPath does not exist, create it if (!(Test-Path $LogPath)) { New-Item -ItemType Directory -Path $LogPath | Out-Null } #Define Log file $LogFile = "$LogPath\Log_$fileSuffix.log" #Start Transcript Start-TxnLogging $LogFile #.......................................................... #EndRegion Paths #Region MailParamCheck #.......................................................... $isAllGood = $true if ($sendEmail -eq $true) { if (!$From) { WriteError "A valid sender email address is not specified." $isAllGood = $false } if (!$To) { WriteError "No recipient specified." $isAllGood = $false } } if ($isAllGood -eq $false) { WriteError "Exiting Script." EXIT } #.......................................................... #EndRegion MailParamCheck if ($To) { $toAddressJSON = @() $To | ForEach-Object {$toAddressJSON += @{EmailAddress = @{Address = $_}}} } #Graph API Token WriteInfo "Acquire Graph Token." try { $body = @{grant_type="client_credentials";scope="https://graph.microsoft.com/.default";client_id=$appID;client_secret=$appKey} $oauth = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token -Body $body $headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"} } catch { WriteError "Error getting Graph Token." WriteError $_.Exception.Message EXIT } $graphApiUri = "https://graph.microsoft.com/v1.0/organization" $organizationName = (Invoke-RestMethod -Method Get -Uri $graphApiUri -Headers $headerParams -ErrorAction STOP).Value.DisplayName $graphApiUri = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D30')" try { $raw = (Invoke-RestMethod -Method Get -Uri $graphApiUri -Headers $headerParams -ErrorAction STOP).Remove(0,3)| ConvertFrom-Csv } catch { WriteError "Error retrieving data." WriteError $_.Exception.Message EXIT } $raw | Export-Csv $ReportCSV -NoTypeInformation WriteInfo "Report saved to $ReportCSV." #If sendEmail is not equal to TRUE, exit here. if ($sendEmail -ne $true) {EXIT} #Else, continue with email report #convert attachment to base 64 encoded format WriteInfo "Converting Report to Base 64 for use as email attachment." $fileContentBytes = [System.Text.Encoding]::UTF8.GetBytes((get-content $ReportCSV -Raw)) $base64_csv = [System.Convert]::ToBase64String($fileContentBytes) #Outlook API WriteInfo "Acquire Outlook Token." try { $outlookMailApiUri = "https://outlook.office.com/api/v2.0/users/$($From)/sendmail" $body = @{grant_type="client_credentials";scope="https://outlook.office.com/.default";client_id=$appID;client_secret=$appKey} $oauth = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token -Body $body -ErrorAction STOP $headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"} } catch { WriteError "Error getting Outlook Token." WriteError $_.Exception.Message EXIT } # Why use Outlook Mail REST API to send email instead of MS Graph API? # Because MS Graph API cannot handle attachments larger than 4MB WriteInfo "Creating Report." $licenseProps = @{ Exchange = ($raw | Where-Object {$_."Has Exchange License" -eq $true}).count Sharepoint = ($raw | Where-Object {$_."Has Sharepoint License" -eq $true}).count OneDrive = ($raw | Where-Object {$_."Has OneDrive License" -eq $true}).count SkypeForBusiness = ($raw | Where-Object {$_."Has Skype For Business License" -eq $true}).count Teams = ($raw | Where-Object {$_."Has Teams License" -eq $true}).count Yammer = ($raw | Where-Object {$_."Has Yammer License" -eq $true}).count } $license = New-Object psobject -Property $licenseProps #message $messageSubject = "[$($organizationName)] Assigned Licenses Report : " + (Get-Date -format F) $cssString = @' <style type="text/css"> .tftable {table-layout:fixed;width: 40%;font-family:"Segoe UI";font-size:12px;color:#333333;border-width: 1px;border-color: #729ea5;border-collapse: collapse;} .tftable th {width: 30%;font-size:12px;background-color:#acc8cc;border-width: 1px;padding: 8px;border-style: solid;border-color: #729ea5;text-align:left;} .tftable tr {background-color:#d4e3e5;} .tftable td {width: 10%font-size:12px;border-width: 1px;padding: 8px;border-style: solid;border-color: #729ea5;} .tftable tr:hover {background-color:#ffffff;} </style> '@ $messageBody = '<html>' $messageBody += "<head><title>$($messageSubject)</title>" $messageBody += '<meta http-equiv="Content-Type content="text/html; charset=ISO-8859-1 />' $messageBody += $cssString $messageBody += '</head><body>' $messageBody += '<p><font face="Segoe UI"><h3>Summary of Assigned Licenses Count</h3></font></p>' $messageBody += '<table class="tftable">' $messageBody += '<tr><th>Exchange</th><td>'+("{0:n0}" -f $license.Exchange)+'</td></tr>' $messageBody += '<tr><th>SharePoint</th><td>'+("{0:n0}" -f $license.Sharepoint)+'</td></tr>' $messageBody += '<tr><th>OneDrive</th><td>'+("{0:n0}" -f $license.OneDrive)+'</td></tr>' $messageBody += '<tr><th>Skype for Business</th><td>'+("{0:n0}" -f $license.SkypeForBusiness)+'</td></tr>' $messageBody += '<tr><th>Teams</th><td>'+("{0:n0}" -f $license.Teams)+'</td></tr>' $messageBody += '<tr><th>Yammer</th><td>'+("{0:n0}" -f $license.Yammer)+'</td></tr>' $messageBody += '</table><hr />' $messageBody += '<p><font face="Segoe UI"><h3>End of Report<h3></font></p>' $messageBody += '<p><font size="2" face="Segoe UI">' $messageBody += 'Source: ' + ($env:COMPUTERNAME) + '<br />' $messageBody += 'Script Path: ' + ($MyInvocation.MyCommand.Definition) + '<br />' $messageBody += 'Script Version: <a href="' + ($scriptInfo.ProjectURI) + '">'+ ($MyInvocation.MyCommand.Definition.ToString().Split("\")[-1].Split(".")[0]) + ' ' + ($scriptInfo.version) + '</a><br />' $messageBody += '</body>' $messageBody += '</html>' $messageBody | out-file $ReportHTML #Careful with this, the Outlook REST API body is CASE-sensitive when in comes to the parameters (eg. 'Subject' is not the same as 'subject') $mailBody = @{ Message = @{ Subject = $messageSubject Body = @{ ContentType = "HTML" Content = $messageBody #Content = "ATTACHED" } ToRecipients = @( $ToAddressJSON ) Attachments = @( @{ "@odata.type" = "#Microsoft.OutlookServices.FileAttachment" Name = "$($ReportFileNameCSV)" ContentType = "multipart/mixed" ContentBytes = $base64_csv } ) } SaveToSentItems = $false } $mailBody = $mailBody | ConvertTo-JSON -Depth 4 #send email WriteInfo "Send Email Report." try { Invoke-RestMethod -Method Post -Uri $outlookMailApiUri -Body $mailbody -Headers $headerParams -ContentType application/json -ErrorAction STOP } catch { WriteError "Error sending email." WriteError $_.Exception.Message EXIT } |