Add PowerShell version: sf-org-lic.ps1

- Complete PowerShell implementation of the license reporting tool
- Full PowerShell parameter handling with Get-Help integration
- Error handling consistent with PowerShell patterns
- Color-coded output using PowerShell Write-Host
- Updated README.md to document both Unix and PowerShell versions
- Added PowerShell examples and installation instructions
- Maintains feature parity with Bash version

Cross-platform support:
- sf-org-lic (Bash/Unix)
- sf-org-lic.ps1 (PowerShell/Windows)
This commit is contained in:
app
2025-09-01 23:43:26 +08:00
parent fafc8fac20
commit d6c55ce0b3
2 changed files with 254 additions and 2 deletions

View File

@@ -20,7 +20,7 @@ Authentication:
Org and metadata:
- **[`sf-org-create` / `sf-org-create.ps1`](#sf-org-create)** - Smart scratch org creation
- **[`sf-org-lic`](#sf-org-lic)** - Salesforce org license utilization report
- **[`sf-org-lic` / `sf-org-lic.ps1`](#sf-org-lic--sf-org-licps1)** - Salesforce org license utilization report
- **[`sf-org-info` / `sf-org-info.ps1`](#sf-org-info--sf-org-infops1)** - Quick org info, limits, and context
- **[`sf-retrieve` / `sf-retrieve.ps1`](#sf-retrieve--sf-retrieveps1)** - Streamlined metadata retrieval (types, manifest, packages)
@@ -176,7 +176,7 @@ sf-org-create -al TestOrg -dd 5 -nn
---
### <a id="sf-org-lic"></a>[🏠](#salesforce-cli-wrapper-scripts) sf-org-lic
### <a id="sf-org-lic--sf-org-licps1"></a>[🏠](#salesforce-cli-wrapper-scripts) sf-org-lic / sf-org-lic.ps1
Generate comprehensive Salesforce license utilization reports.
@@ -184,6 +184,9 @@ Generate comprehensive Salesforce license utilization reports.
```bash
sf-org-lic -to ORG [-hp]
```
```powershell
sf-org-lic.ps1 -to "PROD-ORG"
```
**Options:**
- `-to` - Target org alias or username (required)

249
sf-org-lic.ps1 Normal file
View File

@@ -0,0 +1,249 @@
#!/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 {
Get-Help $MyInvocation.MyCommand.Path -Detailed
}
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