Files
sf-cli-wrapper/sf-data-import.ps1
Reynold Lariza 134169a2ee Fix PowerShell parameter conflicts by renaming Verbose to VerboseOutput
- Fixed parameter conflicts in sf-data-export.ps1, sf-data-import.ps1, sf-retrieve.ps1, and sf-test-run.ps1
- Renamed conflicting \ parameter to \ to avoid conflicts with PowerShell's built-in -Verbose common parameter
- Added proper parameter sets to ensure -Help parameter works correctly in all scripts
- Added -hp aliases where needed for consistency across all scripts
- All scripts now properly support help functionality without parameter conflicts

This resolves issues where scripts would fail with 'A parameter with the name Verbose was defined multiple times' error.
2025-08-28 20:58:54 +08:00

373 lines
10 KiB
PowerShell

#!/usr/bin/env pwsh
<#
.SYNOPSIS
Data import wrapper for Salesforce CLI with CSV and JSON support
.DESCRIPTION
A user-friendly wrapper around 'sf data import' that simplifies data import
to Salesforce orgs with CSV/JSON support, upsert operations, and intelligent validation.
.PARAMETER File
CSV or JSON file to import (alias: -fl)
.PARAMETER SObject
Target sObject type (alias: -so)
.PARAMETER Operation
Operation: insert, update, upsert (default: insert) (alias: -op)
.PARAMETER ExternalId
External ID field for upsert/update operations (alias: -ei)
.PARAMETER TargetOrg
Target org username or alias (alias: -to)
.PARAMETER Bulk
Use bulk API for large datasets (alias: -bk)
.PARAMETER Wait
Wait time in minutes (default: 10) (alias: -wt)
.PARAMETER BatchSize
Batch size for bulk operations (default: 10000) (alias: -bs)
.PARAMETER IgnoreErrors
Continue on errors (don't fail entire job) (alias: -ie)
.PARAMETER VerboseOutput
Enable verbose output (alias: -ve)
.PARAMETER Help
Show this help message (alias: -hp)
.EXAMPLE
.\sf-data-import.ps1 -fl accounts.csv -so Account
.\sf-data-import.ps1 -fl contacts.json -so Contact -op upsert -ei Email
.\sf-data-import.ps1 -fl leads.csv -so Lead -bk -bs 5000
.\sf-data-import.ps1 -fl updates.csv -so Account -op update -ei AccountNumber
.NOTES
This script automatically checks for Salesforce CLI installation and runs
diagnostics if the CLI is not found.
Supported formats:
- CSV files with header row
- JSON files (array of objects or newline-delimited JSON)
#>
param(
[Parameter(ParameterSetName="Import", Mandatory)]
[Alias("fl")]
[string]$File,
[Parameter(ParameterSetName="Import", Mandatory)]
[Alias("so")]
[string]$SObject,
[Parameter(ParameterSetName="Import")]
[ValidateSet("insert", "update", "upsert")]
[Alias("op")]
[string]$Operation = "insert",
[Parameter(ParameterSetName="Import")]
[Alias("ei")]
[string]$ExternalId,
[Parameter(ParameterSetName="Import")]
[Alias("to")]
[string]$TargetOrg,
[Parameter(ParameterSetName="Import")]
[Alias("bk")]
[switch]$Bulk,
[Parameter(ParameterSetName="Import")]
[Alias("wt")]
[int]$Wait = 10,
[Parameter(ParameterSetName="Import")]
[Alias("bs")]
[int]$BatchSize = 10000,
[Parameter(ParameterSetName="Import")]
[Alias("ie")]
[switch]$IgnoreErrors,
[Parameter(ParameterSetName="Import")]
[Alias("ve")]
[switch]$VerboseOutput,
[Parameter(ParameterSetName="Help", Mandatory=$true)]
[Alias("hp")]
[switch]$Help
)
# Show help if requested
if ($Help) {
Get-Help $MyInvocation.MyCommand.Path -Detailed
exit 0
}
# Function to check if Salesforce CLI is installed
function Test-SalesforceCLI {
try {
$null = Get-Command sf -ErrorAction Stop
return $true
} catch {
return $false
}
}
# Function to run sf-check diagnostics
function Invoke-SalesforceCheck {
$checkScript = if (Test-Path "sf-check.ps1") {
".\sf-check.ps1"
} elseif (Test-Path "sf-check.sh") {
"bash sf-check.sh"
} else {
$null
}
if ($checkScript) {
Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow
Invoke-Expression $checkScript
} else {
Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red
Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red
}
}
# Function to detect file format
function Get-FileFormat {
param([string]$FilePath)
$extension = [System.IO.Path]::GetExtension($FilePath).ToLower()
switch ($extension) {
".csv" { return "csv" }
".json" { return "json" }
default {
# Try to detect by content
$firstLine = Get-Content $FilePath -First 1
if ($firstLine -match '^\s*\{.*\}$|^\s*\[') {
return "json"
} else {
return "csv"
}
}
}
}
# Function to validate CSV file
function Test-CSVFile {
param([string]$FilePath)
$lines = Get-Content $FilePath
if ($lines.Count -lt 2) {
Write-Host "Error: CSV file must have at least a header and one data row" -ForegroundColor Red
return $false
}
$headerFields = ($lines[0] -split ',').Count
$firstRowFields = ($lines[1] -split ',').Count
if ($headerFields -ne $firstRowFields) {
Write-Host "Warning: Header field count ($headerFields) differs from first row ($firstRowFields)" -ForegroundColor Yellow
}
return $true
}
# Function to validate JSON file
function Test-JSONFile {
param([string]$FilePath)
try {
$null = Get-Content $FilePath -Raw | ConvertFrom-Json
return $true
} catch {
Write-Host "Error: Invalid JSON format in file" -ForegroundColor Red
return $false
}
}
# Function to show file preview
function Show-FilePreview {
param([string]$FilePath, [string]$Format)
Write-Host "📄 File Preview ($Format):" -ForegroundColor Yellow
Write-Host "----------------------------------------" -ForegroundColor Gray
switch ($Format) {
"csv" {
$lines = Get-Content $FilePath
Write-Host "Header: $($lines[0])" -ForegroundColor Gray
if ($lines.Count -gt 1) {
Write-Host "Sample: $($lines[1])" -ForegroundColor Gray
}
Write-Host "Records: $($lines.Count - 1)" -ForegroundColor Gray
}
"json" {
try {
$content = Get-Content $FilePath -Raw | ConvertFrom-Json
if ($content -is [array]) {
Write-Host "Array format with $($content.Count) records" -ForegroundColor Gray
if ($content.Count -gt 0) {
$keys = ($content[0] | Get-Member -MemberType NoteProperty).Name -join ", "
Write-Host "Sample keys: $keys" -ForegroundColor Gray
}
} else {
$recordCount = (Get-Content $FilePath).Count
Write-Host "NDJSON format" -ForegroundColor Gray
Write-Host "Records: $recordCount" -ForegroundColor Gray
}
} catch {
Write-Host "Unable to parse JSON preview" -ForegroundColor Gray
}
}
}
$fileInfo = Get-Item $FilePath
$fileSize = if ($fileInfo.Length -gt 1MB) {
"{0:N1} MB" -f ($fileInfo.Length / 1MB)
} elseif ($fileInfo.Length -gt 1KB) {
"{0:N1} KB" -f ($fileInfo.Length / 1KB)
} else {
"$($fileInfo.Length) bytes"
}
Write-Host "File size: $fileSize" -ForegroundColor Gray
Write-Host "----------------------------------------" -ForegroundColor Gray
}
# Silently check for Salesforce CLI
if (-not (Test-SalesforceCLI)) {
Invoke-SalesforceCheck
exit 1
}
# Validate file exists
if (-not (Test-Path $File)) {
Write-Host "Error: File not found: $File" -ForegroundColor Red
exit 1
}
# Validate external ID for upsert/update operations
if (($Operation -eq "upsert" -or $Operation -eq "update") -and -not $ExternalId) {
Write-Host "Error: External ID field is required for $Operation operations" -ForegroundColor Red
exit 1
}
# Detect and validate file format
$fileFormat = Get-FileFormat $File
Write-Host "Using file: $File" -ForegroundColor Green
Write-Host "Detected format: $fileFormat" -ForegroundColor Cyan
# Validate file content
switch ($fileFormat) {
"csv" {
if (-not (Test-CSVFile $File)) {
exit 1
}
}
"json" {
if (-not (Test-JSONFile $File)) {
exit 1
}
}
}
# Show file preview if verbose
if ($VerboseOutput) {
Show-FilePreview $File $fileFormat
}
# Build the sf command
$sfArgs = @("data", $Operation, "--file", $File, "--sobject", $SObject)
# Add optional parameters
if ($TargetOrg) {
$sfArgs += "--target-org"
$sfArgs += $TargetOrg
Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan
}
if ($ExternalId) {
$sfArgs += "--external-id"
$sfArgs += $ExternalId
Write-Host "External ID field: $ExternalId" -ForegroundColor Cyan
}
if ($Bulk) {
$sfArgs += "--bulk"
Write-Host "Using Bulk API" -ForegroundColor Yellow
}
if ($Wait -ne 10) {
$sfArgs += "--wait"
$sfArgs += $Wait.ToString()
}
if ($Bulk -and $BatchSize -ne 10000) {
$sfArgs += "--batch-size"
$sfArgs += $BatchSize.ToString()
Write-Host "Batch size: $BatchSize" -ForegroundColor Cyan
}
if ($IgnoreErrors) {
$sfArgs += "--ignore-errors"
Write-Host "Ignoring individual record errors" -ForegroundColor Yellow
}
# Add verbose flag if requested
if ($VerboseOutput) {
$sfArgs += "--verbose"
}
# Display import information
Write-Host ""
Write-Host "📥 Starting Data Import" -ForegroundColor Blue
Write-Host "=======================" -ForegroundColor Blue
Write-Host "Operation: $Operation" -ForegroundColor Cyan
Write-Host "sObject: $SObject" -ForegroundColor Cyan
# Display the command being run
Write-Host ""
Write-Host "Executing: sf $($sfArgs -join ' ')" -ForegroundColor Gray
Write-Host ""
# Execute the command
try {
& sf @sfArgs
$exitCode = $LASTEXITCODE
Write-Host ""
if ($exitCode -eq 0) {
Write-Host "✅ Data import completed successfully!" -ForegroundColor Green
switch ($Operation) {
"insert" {
Write-Host "📊 Records inserted into $SObject" -ForegroundColor Cyan
}
"update" {
Write-Host "📊 Records updated in $SObject" -ForegroundColor Cyan
}
"upsert" {
Write-Host "📊 Records upserted in $SObject (using $ExternalId as external ID)" -ForegroundColor Cyan
}
}
if ($VerboseOutput) {
Write-Host "💡 Check the output above for detailed results and any warnings" -ForegroundColor Yellow
}
} else {
Write-Host "❌ Data import failed with exit code: $exitCode" -ForegroundColor Red
Write-Host "💡 Check data format, field mappings, and validation rules" -ForegroundColor Yellow
exit $exitCode
}
} catch {
Write-Host "Error executing sf command: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}