#!/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 Verbose Enable verbose output (alias: -vb) .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(Mandatory)] [Alias("fl")] [string]$File, [Parameter(Mandatory)] [Alias("so")] [string]$SObject, [ValidateSet("insert", "update", "upsert")] [Alias("op")] [string]$Operation = "insert", [Alias("ei")] [string]$ExternalId, [Alias("to")] [string]$TargetOrg, [Alias("bk")] [switch]$Bulk, [Alias("wt")] [int]$Wait = 10, [Alias("bs")] [int]$BatchSize = 10000, [Alias("ie")] [switch]$IgnoreErrors, [Alias("vb")] [switch]$Verbose, [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 ($Verbose) { 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 ($Verbose) { $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 ($Verbose) { 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 }