282 lines
12 KiB
PowerShell
282 lines
12 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Generates an Azure license report, including license usage data, and sends it via email.
|
|
|
|
.DESCRIPTION
|
|
This PowerShell script connects to the Microsoft Graph API, retrieves available licenses and licensed users, and calculates the assigned and available licenses.
|
|
It then generates an HTML report and sends it to the specified recipients. A summary report is also generated and saved to a file.
|
|
|
|
.PARAMETER None
|
|
This script does not require any parameters.
|
|
|
|
.EXAMPLE
|
|
.\Azure-License-Report.ps1
|
|
|
|
.NOTES
|
|
This script is intended for use in a test or production environment. Make sure to test the script in a non-production environment before running it in production.
|
|
Author: D.de Kooker - info@dcomputers.nl
|
|
Version: 1.0
|
|
|
|
DISCLAIMER: Use scripts at your own risk, if there is anything I can help you with I will try but I do not take responsibility for the way that anyone else uses my scripts.
|
|
Sharing is caring. Share your knowledge with the world so that everybody can learn from it.
|
|
|
|
.LINK
|
|
The latest version can Always be found on my GIT page on the link below:
|
|
https://git.dcomputers.nl/Dcomputers/PowershellScripts
|
|
#>
|
|
|
|
#region Global script settings and variables
|
|
#General
|
|
$Version = "v1.0"
|
|
$logfilelocation = "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\Logs"
|
|
$logfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-Licensing-Report.log"
|
|
$summaryfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-Licensing-Report.txt"
|
|
|
|
#Azure Enterprise app configuration
|
|
$STR_TenantID = ""
|
|
$STR_AppID = ""
|
|
$STR_ClientSecret = ""
|
|
|
|
#Email report settings
|
|
$STR_SMTPServer = ""
|
|
$STR_SMTPServerPort = ""
|
|
$STR_SMTPUsername = ""
|
|
$STR_SMTPPassword = ""
|
|
$STR_EmailSubject= "Azure License report - $(Get-Date -Format "dd-MM-yyyy")"
|
|
$STR_SMTPFromaddress = "Servicedesk ICT <servicedesk@contoso.com>"
|
|
$STR_Receivers = "servicedesk@contoso.com,systemengineer1@contoso.com" #List of commaseperated emailaddresses
|
|
#endregion
|
|
|
|
#region functions
|
|
function SendMailv2 ($To,$Subject,$Body){
|
|
$SMTPClient = New-Object Net.Mail.SmtpClient($STR_SMTPServer, $STR_SMTPServerPort)
|
|
# $SMTPClient.EnableSsl = $true
|
|
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($STR_SMTPUsername, $STR_SMTPPassword);
|
|
$SMTPMessage = New-Object System.Net.Mail.MailMessage($STR_SMTPFromaddress,$To,$Subject,$Body)
|
|
$SMTPMessage.IsBodyHTML = $true
|
|
$SMTPClient.Send($SMTPMessage)
|
|
}
|
|
function Initiate-Log {
|
|
# Get current user and session information
|
|
$username = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
$computerName = $env:COMPUTERNAME
|
|
$sessionID = $pid
|
|
$date = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
|
|
# Write log header
|
|
$logHeader = "[$date] Log initiated by $username on $computerName (Session ID: $sessionID)"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "**********************"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "LogFile initiation"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Start time: $date"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Username: $username"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Machine: $computerName"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Process ID: $sessionID"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Script Version: $Version"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "Script Source: https://git.dcomputers.nl/Dcomputers/PowershellScripts"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value "**********************"
|
|
}
|
|
|
|
function Write-Log {
|
|
[CmdletBinding()]
|
|
Param (
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$Message,
|
|
[Parameter(Mandatory=$false)]
|
|
[ValidateSet("INFO", "WARNING", "ERROR")]
|
|
[string]$Level = "INFO"
|
|
)
|
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
$logmessage = "[$timestamp] [$Level] $Message"
|
|
Add-Content -Path $logfilelocation\$logfilename -Value $logmessage
|
|
}
|
|
|
|
function Write-Summary {
|
|
[CmdletBinding()]
|
|
Param (
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$Message
|
|
)
|
|
Add-Content -Path $logfilelocation\$summaryfilename -Value $Message
|
|
}
|
|
#endregion
|
|
|
|
#region prerequisites check
|
|
#Create log directory if not present and initiate logfile
|
|
if (!(test-path $logfilelocation)) {mkdir $logfilelocation}
|
|
Initiate-Log
|
|
|
|
#Check if the required Powershell Modules are available
|
|
$modules = @("Microsoft.Graph")
|
|
foreach ($module in $modules) {
|
|
if (!(Get-Module -Name $module -ListAvailable)) {
|
|
Write-Host "The $module module is not installed. Please install it and try again."
|
|
Write-Log -Message "The $module module is not installed. Please install it and try again." -Level ERROR
|
|
exit 1
|
|
}
|
|
else {
|
|
Import-Module $module
|
|
}
|
|
}
|
|
|
|
#Setup MSGraph connection
|
|
$ClientSecretPass = ConvertTo-SecureString -String $STR_ClientSecret -AsPlainText -Force
|
|
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $STR_AppID, $ClientSecretPass
|
|
Connect-MgGraph -TenantId $STR_TenantID -ClientSecretCredential $ClientSecretCredential
|
|
Write-Log -Message "Connected to MsGraph API" -Level INFO
|
|
|
|
#Download the latest product name translation file
|
|
if (Test-Path "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\ProductNames.csv") {
|
|
Remove-Item "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\ProductNames.csv" -Force
|
|
}
|
|
$uri = "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
|
|
Invoke-WebRequest $uri -OutFile "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\ProductNames.csv"
|
|
$ReadableNames = Import-Csv "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\ProductNames.csv"
|
|
#endregion
|
|
|
|
#region collect license usage and generate a readable report
|
|
Write-Log -Message "Collecting license usage data" -Level INFO
|
|
|
|
# Get all available licenses and licensed users
|
|
$availableLicenses = Get-MgSubscribedSku | Where-Object {$_.CapabilityStatus -eq "Enabled"}
|
|
Write-Log -Message "Retrieved available licenses" -Level INFO
|
|
$licensedUsers = Get-MgUser -Filter 'assignedLicenses/$count ne 0' -ConsistencyLevel eventual -CountVariable licensedUserCount -All
|
|
Write-Log -Message "Retrieved licensed users" -Level INFO
|
|
|
|
# Initialize an empty array to store the license usage data
|
|
$licenseUsageData = @()
|
|
$userLicenses = @()
|
|
|
|
# Loop through each available license
|
|
foreach ($licensedUser in $licensedUsers) {
|
|
$userLicenses += Get-MgUserLicenseDetail -UserId $licensedUser.Id
|
|
}
|
|
foreach ($availableLicense in $availableLicenses) {
|
|
# Get the license details
|
|
$licenseDetails = Get-MgSubscribedSku -SubscribedSkuId $availableLicense.Id
|
|
Write-Log -Message "Retrieved license details for $($availableLicense.SkuPartNumber)" -Level INFO
|
|
|
|
# Get the total licenses available
|
|
$totalLicenses = $licenseDetails.PrePaidUnits.Enabled
|
|
|
|
# Get the assigned licenses
|
|
$assignedLicenses = 0
|
|
foreach ($userLicense in $userLicenses) {
|
|
if ($userLicense.SkuId -eq $availableLicense.SkuId) {
|
|
$assignedLicenses++
|
|
}
|
|
}
|
|
|
|
# Calculate the available licenses
|
|
$availableLicensesCount = $totalLicenses - $assignedLicenses
|
|
|
|
# Calculate license status
|
|
if ($availableLicensesCount -le 1) {
|
|
$licenseStatus = "ERROR"
|
|
} elseif ($availableLicensesCount -le ($totalLicenses * 0.05)) {
|
|
$licenseStatus = "WARNING"
|
|
} else {
|
|
$licenseStatus = "OK"
|
|
}
|
|
|
|
# Create a custom object to store the license usage data
|
|
$licenseUsageObject = [PSCustomObject]@{
|
|
LicenseDisplayName = $($ReadableNames | Where-Object {$_.String_Id -eq $($availableLicense.SkuPartNumber)} | Select-Object -ExpandProperty Product_Display_Name)[0]
|
|
LicenseSKU = $availableLicense.SkuPartNumber
|
|
AssignedLicenses = $assignedLicenses
|
|
AvailableLicenses = $availableLicensesCount
|
|
TotalLicenses = $totalLicenses
|
|
LicenseStatus = $licenseStatus
|
|
}
|
|
|
|
# Add the custom object to the array
|
|
$licenseUsageData += $licenseUsageObject
|
|
Write-Log -Message "Added license usage data for $($availableLicense.SkuPartNumber)" -Level INFO
|
|
}
|
|
|
|
# Create an HTML report
|
|
$htmlReport = @"
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
|
<style>
|
|
body{
|
|
font-family: 'Verdana', sans-serif;
|
|
font-size:9pt;
|
|
}
|
|
h2{
|
|
font-size: 12pt; /* sets the font size to 18 points */
|
|
font-weight: bold; /* sets the font weight to bold */
|
|
margin-bottom: 10px; /* adds a margin of 10 pixels below the heading */
|
|
}
|
|
tbody{
|
|
font-family: 'Verdana', sans-serif;
|
|
font-size:9pt;
|
|
}
|
|
th{
|
|
font-weight: bold; /* sets the font weight to bold */
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Azure Product license report - $(Get-Date -Format "dd-MM-yyyy - HH:mm")</h2>
|
|
Script version: $Version <br/></p>
|
|
<table border='1'>
|
|
<tr>
|
|
<th>License Name</th>
|
|
<th>License SKU</th>
|
|
<th>Assigned Licenses</th>
|
|
<th>Available Licenses</th>
|
|
<th>Total Licenses</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
"@
|
|
foreach ($licenseUsageObject in $($licenseUsageData | Sort-Object -Property LicenseDisplayName)) {
|
|
|
|
$htmlReport += @"
|
|
|
|
<tr>
|
|
<td>$($licenseUsageObject.LicenseDisplayName)</td>
|
|
<td>$($licenseUsageObject.LicenseSKU)</td>
|
|
<td>$($licenseUsageObject.AssignedLicenses)</td>
|
|
<td>$($licenseUsageObject.AvailableLicenses)</td>
|
|
<td>$($licenseUsageObject.TotalLicenses)</td>
|
|
$(switch ($licenseUsageObject.LicenseStatus) {
|
|
'ERROR' {"<td bgcolor='red'>$($licenseUsageObject.LicenseStatus)</td>"}
|
|
'WARNING' {"<td bgcolor='Yellow'>$($licenseUsageObject.LicenseStatus)</td>"}
|
|
default {"<td>$($licenseUsageObject.LicenseStatus)</td>"}
|
|
} )
|
|
</tr>
|
|
"@
|
|
}
|
|
$htmlReport += @"
|
|
</table>
|
|
<p><i>This is an automated report.</i>
|
|
</p>
|
|
</body>
|
|
</html>
|
|
"@
|
|
|
|
#endregion
|
|
|
|
#region send reports and generate summary report
|
|
# Send the report via email
|
|
$licenseStatus = "OK"
|
|
switch ($licenseUsageData | Select-Object -ExpandProperty LicenseStatus) {
|
|
{ $_ -contains "ERROR" } { $licenseStatus = "ERROR"; break }
|
|
{ $_ -contains "WARNING" } { $licenseStatus = "WARNING"; break }
|
|
}
|
|
if ($licenseStatus -ne "OK") {
|
|
$subject = "$($LicenseStatus): $STR_EmailSubject"
|
|
}
|
|
else {
|
|
$subject = "$STR_EmailSubject"
|
|
}
|
|
$body = $htmlReport
|
|
SendMailv2 -To $STR_Receivers -Subject $subject -Body $body
|
|
|
|
# write summary
|
|
Write-Summary "Azure Product license report Summary:"
|
|
Write-Summary "---------------------------"
|
|
Write-Summary "Report date: $(Get-Date -Format "dd-MM-yyy HH:mm:ss")"
|
|
Write-Summary $($licenseUsageData | Sort-Object -Property LicenseDisplayName)
|
|
Write-Summary "---------------------------" |