- Replace problematic Get-Help call with simple Write-Host statements - Ensures help works in all PowerShell execution contexts - Maintains consistent help format with other scripts - Tested successfully on PowerShell Core 7.5.2
264 lines
8.5 KiB
PowerShell
264 lines
8.5 KiB
PowerShell
#!/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 <ORG_ALIAS> [-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 <ORG_ALIAS>" -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
|