Get Campaign Status Reports

Dear all,

Can someone help me with the following. It’s been a while since I have done this, but basically a couple years ago it was possible to export Campaign Status Reports using an API, post-refresh…etc etc but long story short, I am unable to do it now. I have been using v2025 as much as possible, but hit a road block and exporting the Campaign Status reports to csv seems to be the last step I am struggling with.

Can some advise accordingly and let me know where I am going wrong, cause I am sure its something quite silly.

# ================================
# CONFIGURATION
# ================================
$AppProps = ConvertFrom-StringData (Get-Content "$PSScriptRoot/config/appdata.properties" -Raw)

$TenantWeb    = $AppProps.'tenantweb'
$TenantAPI    = $AppProps.'tenantapi'
$ClientID     = $AppProps.'clientId'
$ClientSecret = $AppProps.'clientSecret'

# ================================
# FUNCTIONS
# ================================
function Get-APIData {
    param(
        [string]$Url,
        [hashtable]$Headers
    )
    try {
        return Invoke-RestMethod -Uri $Url -Headers $Headers -Method Get
    } catch {
        Write-Warning "Failed to get data from $Url : $_"
        return $null
    }
}

# ================================
# AUTHENTICATION
# ================================
Write-Host "Authenticating to SailPoint ISC..."
$AuthBody = @{
    grant_type    = "client_credentials"
    client_id     = $ClientID
    client_secret = $ClientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://$TenantAPI/oauth/token" -Method POST -Body $AuthBody -ContentType "application/x-www-form-urlencoded"

$Headers = @{
    "Authorization" = "Bearer $($TokenResponse.access_token)"
    "Content-Type"  = "application/json"
}

$AuthTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$AuthStatus = if ($TokenResponse.access_token) { "Authentication succeeded" } else { "Authentication failed" }
Write-Host "$AuthStatus at $AuthTimestamp"

# ================================
# STEP 1: LIST ACTIVE CAMPAIGNS
# ================================
$Limit = 250
$Offset = 0
$AllCampaigns = @()
$MoreData = $true

while ($MoreData) {
    $CampaignsURL = "https://$TenantAPI/v2025/campaigns?detail=FULL&limit=$Limit&offset=$Offset&count=true"
    $Response = Get-APIData -Url $CampaignsURL -Headers $Headers

    if ($Response) {
        $ActiveCampaigns = $Response | Where-Object { $_.status -eq "ACTIVE" } | ForEach-Object {
            [PSCustomObject]@{
                id              = $_.id
                name            = $_.name
                ReportTriggered = $false
                ReportId        = $null
                Processed       = $false
            }
        }

        $AllCampaigns += $ActiveCampaigns
        if ($Response.Count -lt $Limit) { $MoreData = $false } else { $Offset += $Limit }
    } else {
        $MoreData = $false
    }
}

Write-Host "Total active campaigns retrieved: $($AllCampaigns.Count)"

# ================================
# STEP 2: TRIGGER REPORTS
# ================================
$ReportType = "CAMPAIGN_STATUS_REPORT"
$PollIntervalSeconds = 15

foreach ($campaign in $AllCampaigns) {
    $CampaignID   = $campaign.id
    $CampaignName = $campaign.name -replace '[\\\/:*?"<>|]', ''  # sanitize filename
    $RunReportURL = "https://$TenantAPI/v2025/campaigns/$CampaignID/run-report/$ReportType"

    try {
        $RunResponse = Invoke-RestMethod -Uri $RunReportURL -Headers $Headers -Method Post
        $campaign.ReportTriggered = $true
        $campaign.ReportId = $RunResponse.id
        Write-Host "Triggered report for $CampaignName, Run ID: $($campaign.id)"
    } catch {
        Write-Warning "Failed to trigger report for $CampaignName"
        $campaign.ReportTriggered = $false
    }
}

# ================================
# STEP 3: POLL REPORTS AND EXPORT TO CSV USING REPORT ID
# ================================
$Remaining = $AllCampaigns | Where-Object { $_.ReportTriggered -eq $true } | Measure-Object | Select-Object -ExpandProperty Count

while ($Remaining -gt 0) {
    foreach ($campaign in $AllCampaigns) {
        # Skip if already processed or report hasn't been triggered
        if ($campaign.Processed -eq $true -or $campaign.ReportTriggered -ne $true) { continue }

        $CampaignName = $campaign.name -replace '[\\\/:*?"<>|]', ''
        $ReportId     = $campaign.id  # <-- Use the run-report ID saved during Step 2

        if (-not $ReportId) { 
            Write-Warning "No report ID available yet for $CampaignName. Skipping for now."
            continue 
        }

        $StatusUrl = "https://$TenantAPI/v2025/report-results/$ReportId"
        try {
            $StatusResponse = Invoke-RestMethod -Uri $StatusUrl -Headers $Headers -Method GET
            Write-Host "DEBUG: Report $ReportId for $CampaignName status = $($StatusResponse.status) at $(Get-Date)"
        } catch {
            Write-Warning "Error checking status for $CampaignName - $_"
            continue
        }

        if ($StatusResponse.status -eq "SUCCESS") {
            $CsvURL    = "https://$TenantAPI/cc/api/report/get/$ReportId?format=csv"
            $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $CsvFile   = "$PSScriptRoot\$CampaignName`_$Timestamp.csv"

            # Optional short delay to ensure the report is fully available
            Start-Sleep -Seconds 5

            try {
                $CsvResponse = Invoke-RestMethod -Uri $CsvURL -Headers @{ "Authorization" = $Headers.Authorization } -Method GET -ContentType "text/csv"
                Set-Content -Path $CsvFile -Value $CsvResponse -Encoding UTF8
                Write-Host "Exported report for $CampaignName to $CsvFile"
                $campaign.Processed = $true
            } catch {
                Write-Warning "Failed to export report for $CampaignName $($_.Exception.Message)"
            }
        }
    }

    Start-Sleep -Seconds $PollIntervalSeconds
    $Remaining = $AllCampaigns | Where-Object { $_.ReportTriggered -eq $true -and $_.Processed -ne $true } | Measure-Object | Select-Object -ExpandProperty Count
}

Write-Host "All active campaigns processed."

it looks to me like you’re using a deprecated API endpoint to get the csv report

You should probably try the API endpoint(s) that replaced it.

I have a sample powershell script here using the v3 version

This seems to be the 2025 version

1 Like

Cool cool, let me give it a go! Thanks

Sounds good. It’s the URL you’re populating $CsvURL with just FYI

@aricoux any luck with the change?