Last active
May 18, 2018 20:08
-
-
Save JohnLBevan/8c6e9825ad6fde535a23a75f2125e47e to your computer and use it in GitHub Desktop.
SalesForce API Query; loosely based on https://github.com/matt2005/SalesForcePowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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