diff --git a/README.md b/README.md
index cbe7f12..27b9c50 100644
--- a/README.md
+++ b/README.md
@@ -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
---
-### [🏠](#salesforce-cli-wrapper-scripts) sf-org-lic
+### [🏠](#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)
diff --git a/sf-org-lic.ps1 b/sf-org-lic.ps1
new file mode 100644
index 0000000..54cb066
--- /dev/null
+++ b/sf-org-lic.ps1
@@ -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 " -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