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

+ + + + + + + + + +"@ + foreach ($licenseUsageObject in $($licenseUsageData | Sort-Object -Property LicenseDisplayName)) { + + $htmlReport += @" + + + + + + + + $(switch ($licenseUsageObject.LicenseStatus) { + 'ERROR' {""} + 'WARNING' {""} + default {""} + } ) + +"@ + } + $htmlReport += @" +
License NameLicense SKUAssigned LicensesAvailable LicensesTotal LicensesStatus
$($licenseUsageObject.LicenseDisplayName)$($licenseUsageObject.LicenseSKU)$($licenseUsageObject.AssignedLicenses)$($licenseUsageObject.AvailableLicenses)$($licenseUsageObject.TotalLicenses)$($licenseUsageObject.LicenseStatus)$($licenseUsageObject.LicenseStatus)$($licenseUsageObject.LicenseStatus)
+

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