Powershell : RDS CAL Licensing Report

Recently, I needed to generate a consolidated report of Remote Desktop Services Client Access License (RDS-CAL) usage across multiple licensing servers in our domain. What seemed like a straightforward task turned into a deep dive into the quirks of Windows Server licensing management. Here's my journey and the solution that finally worked.

The final results is a HTML website that shows all the servers defined with the RDS-CAL license usages:


The Initial Approach: PowerShell RDS Module

My first instinct was to use the Remote Desktop Services PowerShell module, which seemed purpose-built for this task:

Import-Module RemoteDesktopServices
Get-RDLicenseKeyPack -ConnectionBroker $serverName

This immediately failed with "The term 'Get-RDLicenseKeyPack' is not recognized." Apparently, this cmdlet isn't available or installed on our servers so that is not a reliable command to use.

Second Attempt: WMI Queries

Next, I tried the traditional WMI approach:

Get-WmiObject -Class Win32_TSLicenseKeyPack 
-ComputerName $serverName -Namespace "root\cimv2\TerminalServices"

This failed with "Invalid class Win32_TSLicenseKeyPack". It turns out the namespace might be incorrect or the WMI class isn't available remotely it needs to be run locally on the server.

Detective Work : Checking the Registry

At this point, I started looking at alternative methods, including checking the registry for license information:

Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\
TermServLicensing\Parameters'

While this showed the service was configured, it didn't provide actual license counts or usage data.

Using pre-existing tools for discovery

Windows Server includes several built-in tools for RDS licensing:

  • licmgr.exe - The GUI Remote Desktop Licensing Manager
  • lsdiag.msc - RD Licensing Diagnoser
  • licensingdiag.exe - Command-line diagnostic tool

Running licensingdiag.exe showed promise but required very specific parameters:

licensingdiag.exe -report "C:\Temp\diag.txt" -log "C:\Temp\diag.cab"

However, when run locally on the servers, the report was empty or didn't contain the actual license information I needed.

Locating the correct WMI Namespace

After much trial and error, I discovered that the correct WMI query was:

Get-WmiObject -Namespace "root\cimv2" -Class Win32_TSLicenseKeyPack

This worked locally on each server but not remotely. The key insight was that the WMI namespace for RDS licensing is actually under root\cimv2, not root\cimv2\TerminalServices as many online resources suggest - I do not like the rabbit holes when you fall down them!

The Final Solution: Remote WMI Queries with PowerShell

Here's the complete solution that generates both CSV and HTML reports:

# Remote RDS License Query Script
$licenseServers = @(
    "server1.domain.com",
    "server2.domain.com",
    "server3.domain.com"
)

$combinedReport = @()
$timestamp = Get-Date -Format "yyyyMMdd_HHmm"

foreach ($server in $licenseServers) {
    Write-Host "Querying $server..." -ForegroundColor Yellow
    
    try {
        # Use Invoke-Command to run the WMI query remotely
        $licenses = Invoke-Command -ComputerName $server -ScriptBlock {
            Get-WmiObject -Namespace "root\cimv2" -Class Win32_TSLicenseKeyPack
        } -ErrorAction Stop
        
        foreach ($license in $licenses) {
            $reportEntry = [PSCustomObject]@{
                ServerName = $server
                ProductVersion = $license.ProductVersion
                ProductVersionID = $license.ProductVersionID
                KeyPackType = switch ($license.KeyPackType) {
                    0 { "Unknown" }
                    1 { "Retail Purchase" }
                    2 { "Volume Purchase" }
                    3 { "Concurrent License" }
                    4 { "Temporary" }
                    5 { "Open License" }
                    6 { "Built-in" }
                    default { "Other" }
                }
                TotalLicenses = $license.TotalLicenses
                IssuedLicenses = $license.IssuedLicenses
                AvailableLicenses = $license.AvailableLicenses
                UsagePercentage = if ($license.TotalLicenses -gt 0) {
                    [math]::Round(($license.IssuedLicenses / $license.TotalLicenses)
                 * 100, 2)
                } else { 0 }
                ExpirationDate = if ($license.ExpirationDate) {
                    [Management.ManagementDateTimeConverter]
                ::ToDateTime($license.ExpirationDate)
                } else {
                    "Never"
                }
            }
            $combinedReport += $reportEntry
        }
    }
    catch {
        Write-Host "Failed to query $server : $_" -ForegroundColor Red
    }
}

# Export to CSV
$combinedReport | Export-Csv -Path 
"RDS_License_Report_${timestamp}.csv" -NoTypeInformation

# Generate HTML Report with visual formatting
$serverGroups = $combinedReport | Group-Object ServerName

Lessons Learned

  1. WMI namespaces matter: The correct namespace for RDS licensing is root\cimv2, not the seemingly obvious root\cimv2\TerminalServices.
  2. Not all PowerShell modules are created equal: The Remote Desktop Services module isn't always available or complete, even on RDS servers.
  3. Built-in tools can be misleading: Commands like netsh rds show license don't exist, despite some documentation suggesting otherwise.
  4. Local vs. remote execution: Some WMI queries work locally but fail remotely, making PowerShell remoting essential.
  5. Error handling is crucial: With multiple servers and potential connectivity issues, robust error handling prevents the script from failing completely.

Detective Work

To make the data more presentable, I added HTML report generation. Here's how I enhanced the script to create a color-coded report that highlights usage levels:

# Create HTML report
$htmlReport = @"
<!DOCTYPE html>
<html>
<head>
    <title>RDS License Report</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #4CAF50; color: white; }
        tr:nth-child(even) { background-color: #f2f2f2; }
        .high-usage { background-color: #ffcccc; }
        .medium-usage { background-color: #ffffcc; }
        .low-usage { background-color: #ccffcc; }
        .server-section { margin-top: 30px; }
    </style>
</head>
<body>
    <h1>RDS License Report</h1>
    <p>Generated: $(Get-Date)</p>
"@

foreach ($group in $serverGroups) {
    $htmlReport += @"
    <div class="server-section">
        <h2>$($group.Name)</h2>
        <table>
            <tr>
                <th>Product</th>
                <th>Type</th>
                <th>Total</th>
                <th>Issued</th>
                <th>Available</th>
                <th>Usage %</th>
                <th>Expiration</th>
            </tr>
"@
    foreach ($license in $group.Group) {
        $rowClass = ""
        if ($license.UsagePercentage -ge 90) { $rowClass = "high-usage" }
        elseif ($license.UsagePercentage -ge 75) { $rowClass = "medium-usage" }
        else { $rowClass = "low-usage" }
        
        $htmlReport += @"
            <tr class="$rowClass">
                <td>$($license.ProductVersionID)</td>
                <td>$($license.KeyPackType)</td>
                <td>$($license.TotalLicenses)</td>
                <td>$($license.IssuedLicenses)</td>
                <td>$($license.AvailableLicenses)</td>
                <td>$($license.UsagePercentage)%</td>
                <td>$($license.ExpirationDate)</td>
            </tr>
"@
    }
    $htmlReport += "</table></div>"
}

$htmlReport += @"
</body>
</html>
"@

$htmlReport | Out-File -FilePath "RDS_License_Report.html" -Encoding UTF8

The HTML report features:

  • Color-coded rows based on usage percentage (red for >90%, yellow for >75%, green for <75%)
  • Clear table formatting with alternating row colors
  • Server-by-server breakdown of license usage
  • Expiration date tracking

This visual representation makes it much easier to spot servers approaching their license limits at a glance.

Previous Post Next Post

نموذج الاتصال