#!/usr/bin/env pwsh <# .SYNOPSIS Salesforce org license utilization reporting tool .DESCRIPTION Generate comprehensive Salesforce license utilization reports for an org. Reports on User Licenses and Permission Set Licenses with detailed totals and professional formatting suitable for license audits. .PARAMETER to Target org alias or username (required) .PARAMETER hp Show this help message .EXAMPLE .\sf-org-lic.ps1 -to "PROD-ORG" Generate license report for production org .EXAMPLE .\sf-org-lic.ps1 -to "admin@company.com" Generate report for specific user .EXAMPLE .\sf-org-lic.ps1 -hp Show help message .NOTES This script requires: - Salesforce CLI (sf) - PowerShell 5.1+ or PowerShell Core - Valid authentication to the target org Features: - User Licenses - Core Salesforce user license utilization - Permission Set Licenses - Add-on feature license usage - Comprehensive totals - Overall usage summary with remaining capacity - Clean formatting - Professional tabular output - Error handling - Clear messages for invalid orgs with suggestions #> param( [Parameter(Mandatory=$false)] [string]$to = "", [switch]$hp ) function Show-Help { Write-Host "sf-org-lic.ps1 - Salesforce org license utilization reporting tool" Write-Host "" Write-Host "Usage: .\\sf-org-lic.ps1 -to [-hp]" Write-Host "" Write-Host "Generate comprehensive Salesforce license utilization reports for an org." Write-Host "Reports on User Licenses and Permission Set Licenses with detailed totals." Write-Host "" Write-Host "Parameters:" Write-Host " -to Target org alias or username (required)" Write-Host " -hp Show this help message" Write-Host "" Write-Host "Examples:" Write-Host " .\\sf-org-lic.ps1 -to \"PROD-ORG\"" Write-Host " .\\sf-org-lic.ps1 -to \"admin@company.com\"" Write-Host " .\\sf-org-lic.ps1 -hp" } function Write-Error-And-Exit { param([string]$Message, [int]$ExitCode = 1) Write-Host "Error: $Message" -ForegroundColor Red exit $ExitCode } # Show help if requested or if no org specified if ($hp) { Show-Help exit 0 } if ($to -eq "") { Write-Host "Usage: .\sf-org-lic.ps1 -to " -ForegroundColor Yellow Write-Host "" Write-Host "Generate Salesforce license utilization report for an org" Write-Host "" Write-Host "Examples:" Write-Host " .\sf-org-lic.ps1 -to PROD-ORG" Write-Host " .\sf-org-lic.ps1 -to dev@company.com" Write-Host " .\sf-org-lic.ps1 -hp" exit 1 } # Check for required dependencies try { Get-Command sf -ErrorAction Stop | Out-Null } catch { Write-Error-And-Exit "'sf' CLI is required but not found. Please install Salesforce CLI." 1 } # Validate org exists and is authorized Write-Host "Validating org: $to..." -ForegroundColor Yellow try { $orgCheckResult = & sf org display --target-org $to --json 2>&1 if ($LASTEXITCODE -ne 0) { # Try to get available orgs for suggestions try { $orgListResult = & sf org list --json 2>$null if ($LASTEXITCODE -eq 0) { $orgList = $orgListResult | ConvertFrom-Json $availableOrgs = $orgList.result.other | ForEach-Object { $_.alias } | Where-Object { $_ -ne $null } | Sort-Object $orgSuggestions = $availableOrgs -join ", " if ($orgSuggestions -eq "") { $orgSuggestions = "none" } } else { $orgSuggestions = "none" } } catch { $orgSuggestions = "none" } Write-Error-And-Exit "org alias '$to' not found or not authorized. Available orgs: $orgSuggestions" 2 } } catch { Write-Error-And-Exit "Failed to validate org: $_" 2 } # Helper function to run SOQL queries with error handling function Invoke-SafeSOQLQuery { param([string]$Query) try { $result = & sf data query --target-org $to --json --query $Query 2>&1 if ($LASTEXITCODE -ne 0) { return $null } $jsonResult = $result | ConvertFrom-Json # Check if result contains error information if ($jsonResult.name -or $jsonResult.error -or ($jsonResult.message -and ($jsonResult.commandName -or $jsonResult.status))) { $errorMsg = $jsonResult.message -or $jsonResult.error -or "Unknown error" # Check if it's a "not supported" error (non-fatal) if ($errorMsg -like "*not supported*" -or $errorMsg -like "*does not exist*" -or $errorMsg -like "*INVALID_TYPE*") { return "NOT_AVAILABLE" } Write-Error-And-Exit "SOQL query failed: $errorMsg" 4 } return $jsonResult } catch { return $null } } # Helper function to format table output function Format-LicenseTable { param( [object]$JsonData, [string[]]$Headers, [string]$LicenseType ) $records = $JsonData.result.records if (-not $records) { $records = $JsonData.records } if (-not $records) { $records = @() } # Print headers Write-Host ($Headers -join "`t") -ForegroundColor Cyan Write-Host ("-" * ($Headers -join "`t").Length) -ForegroundColor Cyan if ($records.Count -eq 0) { Write-Host "(no rows)" Write-Host "" return @{ Total = 0; Used = 0 } } $totalLicenses = 0 $totalUsed = 0 foreach ($record in $records) { if ($LicenseType -eq "User") { $total = [int]($record.TotalLicenses -or 0) $used = [int]($record.UsedLicenses -or 0) $remaining = $total - $used Write-Host "$($record.Name)`t$total`t$used`t$remaining" } elseif ($LicenseType -eq "PSL") { $total = [int]($record.TotalLicenses -or 0) $used = [int]($record.UsedLicenses -or 0) $remaining = $total - $used Write-Host "$($record.MasterLabel)`t$($record.DeveloperName)`t$total`t$used`t$remaining" } $totalLicenses += $total $totalUsed += $used } Write-Host "" $remaining = $totalLicenses - $totalUsed Write-Host "Totals: Total=$totalLicenses Used=$totalUsed Remaining=$remaining" -ForegroundColor Green Write-Host "" return @{ Total = $totalLicenses; Used = $totalUsed } } # Main execution Write-Host "" Write-Host "Salesforce License Utilization — $to" -ForegroundColor White -BackgroundColor Blue Write-Host "Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Gray Write-Host "" # SOQL queries for the two main license types $userLicenseQuery = "SELECT Id, Name, TotalLicenses, UsedLicenses FROM UserLicense ORDER BY Name LIMIT 200" $pslQuery = "SELECT Id, MasterLabel, DeveloperName, TotalLicenses, UsedLicenses FROM PermissionSetLicense ORDER BY MasterLabel LIMIT 200" $grandTotalLicenses = 0 $grandTotalUsed = 0 # 1) User Licenses Write-Host "User Licenses" -ForegroundColor White -BackgroundColor DarkBlue Write-Host "-------------" -ForegroundColor White -BackgroundColor DarkBlue $userLicenseResult = Invoke-SafeSOQLQuery -Query $userLicenseQuery if ($userLicenseResult -eq "NOT_AVAILABLE") { Write-Host "UserLicense object not available in this org." Write-Host "" } elseif ($userLicenseResult -ne $null) { $userTotals = Format-LicenseTable -JsonData $userLicenseResult -Headers @("Name", "Total", "Used", "Remaining") -LicenseType "User" $grandTotalLicenses += $userTotals.Total $grandTotalUsed += $userTotals.Used } else { Write-Host "Failed to query UserLicense." Write-Host "" } # 2) Permission Set Licenses Write-Host "Permission Set Licenses" -ForegroundColor White -BackgroundColor DarkBlue Write-Host "-----------------------" -ForegroundColor White -BackgroundColor DarkBlue $pslResult = Invoke-SafeSOQLQuery -Query $pslQuery if ($pslResult -eq "NOT_AVAILABLE") { Write-Host "PermissionSetLicense object not available in this org." Write-Host "" } elseif ($pslResult -ne $null) { $pslTotals = Format-LicenseTable -JsonData $pslResult -Headers @("MasterLabel", "DeveloperName", "Total", "Used", "Remaining") -LicenseType "PSL" $grandTotalLicenses += $pslTotals.Total $grandTotalUsed += $pslTotals.Used } else { Write-Host "Failed to query PermissionSetLicense." Write-Host "" } # Summary Write-Host "Note: This report covers the main Salesforce license types available in most orgs." -ForegroundColor Gray Write-Host "FeatureLicense objects are not commonly available and have been excluded." -ForegroundColor Gray