From a2104bc742e1a7024980b0879c8baa1a755e2cfe Mon Sep 17 00:00:00 2001
From: Danny de Kooker
Date: Wed, 17 Jul 2024 20:25:50 +0200
Subject: [PATCH] Initial commit
---
Azure/Azure-Licensing-Report.ps1 | 255 +++++++++++++++++++++++++++++++
1 file changed, 255 insertions(+)
create mode 100644 Azure/Azure-Licensing-Report.ps1
diff --git a/Azure/Azure-Licensing-Report.ps1 b/Azure/Azure-Licensing-Report.ps1
new file mode 100644
index 0000000..780ce8d
--- /dev/null
+++ b/Azure/Azure-Licensing-Report.ps1
@@ -0,0 +1,255 @@
+#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 "
+ $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 = @"
+
+
+
+
+
+
+ Azure Product license report - $(Get-Date -Format "dd-MM-yyyy - HH:mm")
+ Script version: $Version
+
+
+ | License Name |
+ License SKU |
+ Assigned Licenses |
+ Available Licenses |
+ Total Licenses |
+ Status |
+
+"@
+ foreach ($licenseUsageObject in $($licenseUsageData | Sort-Object -Property LicenseDisplayName)) {
+
+ $htmlReport += @"
+
+
+ | $($licenseUsageObject.LicenseDisplayName) |
+ $($licenseUsageObject.LicenseSKU) |
+ $($licenseUsageObject.AssignedLicenses) |
+ $($licenseUsageObject.AvailableLicenses) |
+ $($licenseUsageObject.TotalLicenses) |
+ $(switch ($licenseUsageObject.LicenseStatus) {
+ 'ERROR' {"$($licenseUsageObject.LicenseStatus) | "}
+ 'WARNING' {"$($licenseUsageObject.LicenseStatus) | "}
+ default {"$($licenseUsageObject.LicenseStatus) | "}
+ } )
+
+"@
+ }
+ $htmlReport += @"
+
+ This is an automated report.
+
+
+
+"@
+
+#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 "---------------------------"
\ No newline at end of file