Skip to content

Instantly share code, notes, and snippets.

@pbsull
Last active February 26, 2025 18:16
Show Gist options
  • Select an option

  • Save pbsull/19f0bd939c84ab28e31730a0c10a5e74 to your computer and use it in GitHub Desktop.

Select an option

Save pbsull/19f0bd939c84ab28e31730a0c10a5e74 to your computer and use it in GitHub Desktop.
Salesforce - Get All Reports

Salesforce Reports Retrieval Script

This Go script retrieves all reports from a Salesforce org and saves them locally. It uses the Salesforce CLI (sf) to authenticate and query the reports, and the sfdx CLI to retrieve the reports in metadata format.

Prerequisites

  • Go installed on your machine
  • Salesforce CLI (sf) installed

Usage

  1. Clone the repository or download the script.

  2. Navigate to the directory containing the script.

  3. Run the script using the following command:

    go run main.go

    To include managed package reports, use the --include-managed flag:

    go run main.go --include-managed

Script Details

The script performs the following steps:

  1. Checks if the sf CLI is installed.
  2. Authenticates to the Salesforce org using the sf org login web command.
  3. Queries all reports in the org using the sf data query command.
  4. Creates a package.xml file with the queried reports.
  5. Uses the sfdx force:mdapi:retrieve command to retrieve the reports in metadata format.
  6. Unzips the retrieved reports and saves them in the ./force-app/main/default/reports/ directory.

Error Handling

  • The script checks if the sf CLI is installed and exits if not found.
  • It handles authentication and query errors, logging appropriate messages.
  • If the initial wait time for the sfdx force:mdapi:retrieve command exceeds, it increases the wait time and retries.

Notes

  • Ensure you have the necessary permissions to retrieve reports from the Salesforce org.
  • The script creates a temporary package.xml file which is deleted after execution.
  • Retrieved reports are saved in the ./force-app/main/default/reports/ directory.

License

This script is provided as-is without any warranty. Use it at your own risk.

package main
import (
"archive/zip"
"bytes"
"encoding/csv"
"fmt"
"html"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func main() {
includeManagedPackages := false
if len(os.Args) > 1 && os.Args[1] == "--include-managed" {
includeManagedPackages = true
}
// Check if sf is installed
if _, err := exec.LookPath("sf"); err != nil {
log.Fatal("sf could not be found. Please install the Salesforce CLI.")
}
// Authenticate to Salesforce org
fmt.Printf("Authenticating to Salesforce org...\n")
cmd := exec.Command("sf", "org", "login", "web")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("Failed to authenticate: %v", err)
}
// Query all reports
fmt.Printf("Querying all reports in the org...\n")
query := "SELECT DeveloperName, FolderName, NamespacePrefix FROM Report"
if !includeManagedPackages {
query += " WHERE NamespacePrefix = ''"
}
cmd = exec.Command("sf", "data", "query", "--query", query, "-r", "csv")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("Failed to query reports: %v", err)
}
// Create package.xml
fmt.Println("Creating package.xml...")
packageXML := `<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
`
// Read the CSV data from the buffer and add each report to the package.xml
reader := csv.NewReader(bytes.NewReader(out.Bytes()))
records, err := reader.ReadAll()
if err != nil {
log.Fatalf("Failed to read CSV data: %v", err)
}
if len(records) < 2 {
log.Fatal("CSV file does not contain the expected format (header and at least one data row).")
}
for _, record := range records[1:] {
escapedFolderName := html.EscapeString(record[1])
escapedDeveloperName := html.EscapeString(record[0])
namespacePrefix := record[2]
if namespacePrefix != "" {
escapedDeveloperName = namespacePrefix + "__" + escapedDeveloperName
}
packageXML += fmt.Sprintf(" <members>%s/%s</members>\n", escapedFolderName, escapedDeveloperName)
}
packageXML += ` <name>Report</name>
</types>
<version>57.0</version>
</Package>
`
var fileName = "reports" + time.Now().Format("20060102150405") + "package.xml"
if err := os.WriteFile(fileName, []byte(packageXML), 0644); err != nil {
log.Fatalf("Failed to write package.xml: %v", err)
}
// Ensure the temporary file is deleted after execution
defer os.Remove(fileName)
waitTime := "10"
cmd = exec.Command("sfdx", "force:mdapi:retrieve", "-r", "./", "-k", fileName, "-w", waitTime)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
for {
if err := cmd.Run(); err != nil {
if waitTime == "10" {
fmt.Println("Initial wait time exceeded, increasing wait time to 30 minutes...")
waitTime = "30"
cmd = exec.Command("sfdx", "force:mdapi:retrieve", "-r", "./", "-k", fileName, "-w", waitTime)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
continue
} else {
log.Fatalf("Failed to retrieve reports after increasing wait time: %v", err)
}
} else {
break
}
}
// Check if a zip file was created
zipFileName := "unpackaged.zip"
if _, err := os.Stat(zipFileName); err == nil {
fmt.Println("Unzipping retrieved reports...")
if err := unzip(zipFileName, "./force-app/main/default/reports/"); err != nil {
log.Fatalf("Failed to unzip reports: %v", err)
}
os.Remove(zipFileName)
}
fmt.Println("Reports have been retrieved.")
}
// Unzip extracts a zip archive to a specified destination.
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
// Only include report files and their folders
if !strings.HasPrefix(f.Name, "unpackaged/reports/") {
continue
}
// Remove the top-level "unpackaged/reports" folder from the path
fpath := filepath.Join(dest, strings.TrimPrefix(f.Name, "unpackaged/reports/"))
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("%s: illegal file path", fpath)
}
if f.FileInfo().IsDir() {
if _, err := os.Stat(fpath); os.IsNotExist(err) {
os.MkdirAll(fpath, os.ModePerm)
}
continue
}
// Ensure .report suffix is converted to .report-meta.xml
if strings.HasSuffix(fpath, ".report") {
fpath = strings.TrimSuffix(fpath, ".report") + ".report-meta.xml"
}
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment