Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Last active May 18, 2018 20:08
Show Gist options
  • Select an option

  • Save JohnLBevan/8c6e9825ad6fde535a23a75f2125e47e to your computer and use it in GitHub Desktop.

Select an option

Save JohnLBevan/8c6e9825ad6fde535a23a75f2125e47e to your computer and use it in GitHub Desktop.
SalesForce API Query; loosely based on https://github.com/matt2005/SalesForcePowerShell
function ConvertTo-QueryString {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[Hashtable]$Parameters
)
Process {
($Parameters.Keys | %{"$_=$($Parameters[$_])"}) -join '&'
}
}
function Get-SalesForceToken {
[CmdletBinding()]
Param (
#we don't store credentials or secrets in pscredential / securestring objects since the Invoke-RestMethod sends this data in plaintext anyway, so security will be compromised regardless
[Parameter(Mandatory = $true)]
[string]$ApiKey
,
[Parameter(Mandatory = $true)]
[string]$ApiSecret
,
[Parameter(Mandatory = $true)]
[string]$UserName
,
[Parameter(Mandatory = $true)]
[string]$UserPassword
,
[Parameter(Mandatory = $true)]
[string]$UserToken # from user's account / keeps in sync with their password... seems pointless, but is required
)
Begin {
#since this method will be called before any other sales force methods, and the setting lasts for the duration of the session / is unlikely to be changed by other commands, we only bother to set this here
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 #POODLE
[Reflection.Assembly]::LoadWithPartialName('System.Web') | Out-Null
}
Process {
[hashtable]$body = [ordered]@{
grant_type = 'password'
client_id = $ApiKey
client_secret = $ApiSecret
username = ([System.Web.HttpUtility]::UrlEncode($UserName))
password = ([System.Web.HttpUtility]::UrlEncode($UserPassword) + $UserToken)
}
#Write-Verbose ($body | ConvertTo-QueryString)
Invoke-RestMethod -Method Post -Uri 'https://login.salesforce.com/services/oauth2/token' -Body ($body | ConvertTo-QueryString)
}
}
function Invoke-SalesForceMethod {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[PSCustomObject]$Token
,
[Parameter(Mandatory = $true)]
[string]$Path
,
[Parameter(Mandatory = $false)]
[ValidateSet('Get','Post')] #etc.
[string]$Method = 'Get'
)
begin {
[System.Uri]$rootUri = $Token.instance_url
[hashtable]$headers = @{
Authorization = "OAuth $($Token.access_token)"
Accept = 'application/json'
}
}
Process {
$uri = (new-object -TypeName 'System.Uri' -ArgumentList ($rootUri,$Path)).AbsoluteUri
Invoke-RestMethod -Method $Method -Headers $headers -Uri $uri
}
}
function Invoke-SalesForceQueryAll {
[CmdletBinding(DefaultParameterSetName = 'BySoqlQuery')]
Param (
[Parameter(Mandatory = $true)]
[PSCustomObject]$Token
,
#SOQL Reference: https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select.htm
[Parameter(ParameterSetName = 'BySoqlQuery', Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$SoqlQuery
,
[Parameter(ParameterSetName = 'BySimpleTable', Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$TableName
,
[Parameter(ParameterSetName = 'BySimpleTable', Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string[]]$Fields
,
[Parameter(ParameterSetName = 'BySimpleTable', Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string[]]$OrderBy
)
Begin {
[string]$query = if ($PSCmdlet.ParameterSetName -eq 'BySoqlQuery') {
$SoqlQuery
} else {
#no idea if we need to consider escaping field names, etc? ...
#haven't added logic for WHERE since we can't support that level of complexity (i.e. 'and' vs 'or' vs 'brackets', quotes around certain value types, equals or like, functions, etc)
[string]$temp = 'SELECT {0} FROM {1}' -f ($Fields -join ','), $TableName
if ($OrderBy) {
$temp = '{0} ORDER BY {1}' -f $temp, ($OrderBy -join ',')
}
$temp
}
$query = [System.Web.HttpUtility]::UrlEncode($query)
}
Process {
[string]$currentPath = '/services/data/v42.0/queryAll?q={0}' -f $query
While ($currentPath) {
Write-Verbose "Accessing: $currentPath"
$result = Invoke-SalesForceMethod -Token $Token -Path $currentPath
$result.records
$currentPath = $result.nextRecordsUrl
}
}
}
#effectively performs a left outer join on the resultant object graph
function Expand-SalesForceResultRecord {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[PSCustomObject]$Record
,
[Parameter(Mandatory = $false)]
[string]$Prefix = ''
,
[Parameter(Mandatory = $false)]
[switch]$AsHashTable
,
[Parameter(Mandatory = $false)]
[int]$MaxRecursion = 0
)
Process {
[hashtable[]]$results = @(@{})
[string[]]$props = $Record | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
$props | %{
[string]$fieldName = ($Prefix,$_ | ?{$_}) -join '.'
if (($Record."$_") -and ($Record."$_" -is [System.Management.Automation.PSCustomObject])) {
if ($MaxRecursion -gt 0) {
[pscustomobject[]]$childRecords = $Record."$_".Records | ?{$_}
[hashtable[]]$childResults = ($childRecords | Expand-SalesForceResultRecord -Prefix $fieldName -AsHashTable -MaxRecursion ($MaxRecursion - 1))
if ($childResults.Count -gt 0) {
$results = $results * $childResults.Count #if we have multiple results we need to create a new entry for each new combo
for ($i=0; $i -lt $results.Count; $i++)
{
$results[$i] = $results[$i] + $childResults[$i % $childResults.Count]
}
}
}
} else {
$value = $Record."$_"
$results | %{$_."$fieldName" = $value}
}
}
if ($AsHashTable.IsPresent) {
$results
} else {
$results | %{[pscustomobject]$_}
}
}
}
Clear-Host
$token = Get-SalesForceToken `
-ApiKey 'populate me' `
-ApiSecret 'populate me' `
-UserName 'populate me' `
-UserPassword 'populate me' `
-UserToken 'populate me' `
-Verbose
#simple query
Invoke-SalesForceQueryAll -Token $token -TableName 'User' -Fields @('Name','Email') -Verbose
#advanced query with expansion of result set and output to file
$soql = @'
SELECT Label, PermissionsTransferAnyLead,
(SELECT SobjectType, PermissionsRead FROM ObjectPerms),
(SELECT SobjectType, Field, PermissionsRead FROM FieldPerms),
(SELECT AssigneeId,Assignee.Name FROM Assignments)
FROM
PermissionSet
'@ -replace '\s+', ' '
Invoke-SalesForceQueryAll -Token $token -SoqlQuery $soql -Verbose | Expand-SalesForceResultRecord -MaxRecursion 1 | Export-Csv -NoTypeInformation -Encoding UTF8 -Path 'c:\temp\sfComplexPermissionsQuery.csv'
<#
Get the API Key and Secret by creating a new app in the SalesForce App Manager
Username and User Password are the actual user's credentials
UserToken can be generated on the user's profile under `Reset My Security Token`
Before this can be used the user needs to be approved for the app; if the app is configured to allow users to sign up for themselves they can go to the URL:
'https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id={0}&redirect_uri=https://localhost/callmebaby' -f $ApiKey
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment