Files
sf-cli-wrapper/sf-org-lic.ps1
app 9c5c1964f8 Fix PowerShell help function
- 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
2025-09-01 23:49:47 +08:00

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