Skip to content

Instantly share code, notes, and snippets.

@gitfvb
Last active January 21, 2020 15:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gitfvb/fca93fac61cb7bd6065d92d458749fff to your computer and use it in GitHub Desktop.
Save gitfvb/fca93fac61cb7bd6065d92d458749fff to your computer and use it in GitHub Desktop.
Example of how to handle the Apteco Orbit API / FastStats API v2
  • replace variable with you username, sometimes your email address
  • replace variable with a password secure string you can generate via command
    read-host "Please enter the password" -assecurestring | convertfrom-securestring
    
  • Change all the other details you need in $settings
  • replace "" with the file you wish to up- and download
<#
NOTES
* swagger: http://www.tealgreenholidays.co.uk/OrbitAPI/swagger/ui/index.html#/About/About_GetDataViews
#>
################################################
#
# SETTINGS
#
################################################
<#
create password secure string via
read-host "Please enter the password" -assecurestring | convertfrom-securestring
#>
$settings = @{
baseUrl = "http://www.tealgreenholidays.co.uk/OrbitAPI/"
dataViewName = "CloudDemo" # if set to "", then the script asks for selecting a dataview
user = "<email>"
password = "<passwordSecureString>" # secure string that cannot be encrypted backwards except for my machine
loginType = "SALTED" # SIMPLE|SALTED
uploadType = "MULTIPART" # ONEPART|MULTIPART
}
################################################
#
# FUNCTIONS
#
################################################
# Get-Endpoint -Key "CreateLoginParameters"
Function Get-Endpoint{
param(
[String]$Key
)
$endpoints | where { $_.name -eq $Key }
}
# Resolve the endpoint by adding baseUrl and replace some parameters
Function Resolve-Url {
param(
[Parameter(Mandatory=$true)][PSCustomObject] $endpoint,
[Parameter(Mandatory=$false)][Hashtable] $additional,
[Parameter(Mandatory=$false)][Hashtable] $query
)
# build the endpoint
$uri = "$( $Script:settings.baseUrl )$( $endpoint.urlTemplate )"
# replace the dataview
$uri = $uri -replace "{dataViewName}", $Script:settings.dataViewName
# replace other parameters in path
if ($additional) {
$additional.Keys | ForEach {
$uri = $uri -replace "{$( $_ )}", $additional[$_]
}
}
# add parts to the query
if ($query) {
$uri += "?"
$query.Keys | ForEach {
$uri += "$( $_ )=$( [System.Web.HttpUtility]::UrlEncode($query[$_]) )"
}
}
$uri
}
Function Get-StringHash
{
param(
[Parameter(Mandatory=$true)][string]$inputString,
[Parameter(Mandatory=$true)][string]$hashName,
[Parameter(Mandatory=$false)][string]$salt,
[Parameter(Mandatory=$false)][boolean]$uppercase=$false
)
$string = $inputString + $salt
$StringBuilder = New-Object System.Text.StringBuilder
[System.Security.Cryptography.HashAlgorithm]::Create($hashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($string))|%{
[Void]$StringBuilder.Append($_.ToString("x2"))
}
$res = $StringBuilder.ToString()
if ( $uppercase ) {
$res.ToUpper()
} else {
$res
}
}
Function Crypt-Password {
param(
[String]$password
)
$cryptedPassword = @()
$password.ToCharArray() | %{[int][char]$_} | ForEach {
If ($_ % 2 -eq 0) {
$cryptedPassword += [char]( $_ + 1 )
} else {
$cryptedPassword += [char]( $_ - 1 )
}
}
$cryptedPassword -join ""
}
# https://stackoverflow.com/questions/4533570/in-powershell-how-do-i-split-a-large-binary-file
Function Split-File{
param(
$inFile,
$outPrefix,
[Int32] $bufSize
)
$file = Get-Item -Path $inFile
$stream = [System.IO.File]::OpenRead($inFile)
$chunkNum = 1
$barr = New-Object byte[] $bufSize
$chunks = @()
while( $bytesRead = $stream.Read($barr,0,$bufsize)){
$outFile = "$( $file.DirectoryName )\$( $file.Name ).$( $outPrefix )$( $chunkNum )"
$ostream = [System.IO.File]::OpenWrite($outFile)
$ostream.Write($barr,0,$bytesRead);
$ostream.close();
#echo "wrote $outFile"
$chunks += $outFile
$chunkNum++
}
return ,$chunks # the comma enforces the function to return an array with one element rather than a string, if it is only one element
}
Function Prepare-MultipartUpload {
param(
[Parameter(Mandatory=$true)][String]$path,
[Parameter(Mandatory=$false)]$part = $false
)
# standard settings
$uploadEncoding = "ISO-8859-1"
$crlf = "`r`n";
# if multipart, remove the part prefix
$fileItem = Get-Item -Path $path
if ($part) {
$fileName = $fileItem.Name.Substring(0, $fileItem.Name.lastIndexOf('.'))
} else {
$fileName = $fileItem.Name
}
# get file, load and encode it
$fileBytes = [System.IO.File]::ReadAllBytes($fileItem.FullName);
$fileEncoded = [System.Text.Encoding]::GetEncoding($uploadEncoding).GetString($fileBytes);
# create guid for multipart upload
$boundary = [System.Guid]::NewGuid().ToString();
# create body
$body = (
"--$( $boundary )",
"Content-Disposition: form-data; name=`"file`"; filename=`"$( $fileName )`"",
"Content-Type: application/octet-stream$( $crlf )",
$fileEncoded,
"--$( $boundary )--$( $crlf )"
) -join $crlf
# put it together
@{
"body"=$body
"contentType"="multipart/form-data; boundary=""$( $boundary )"""
}
}
################################################
#
# LOAD ALL ENDPOINTS FIRST
#
################################################
$pageSize = 100
$offset = 0
$endpoints = @()
Do {
$uri = "$( $settings.baseUrl )About/Endpoints?count=$( $pageSize )&offset=$( $offset )"
$res = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Verbose
$endpoints += $res.list
$offset += $pageSize
} Until ( $endpoints.count -eq $res.totalCount )
#$endpoints | out-gridview
################################################
#
# LOAD SOME METADATA AND CHOOSE DATAVIEW
#
################################################
# Get the current version of APIv2 and output to console
$endpoint = Get-Endpoint -key "GetVersionDetails"
$uri = Resolve-Url -endpoint $endpoint
$version = Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType "application/json"
$version.version
# Set dataview, if not filled in settings
if ( $settings.dataViewName -eq "" ) {
$endpoint = Get-Endpoint -key "GetDataViews" # GetDataViews|GetDataViewsForDomain|GetDataViewsForSystemName
$uri = Resolve-Url -endpoint $endpoint
$dataviews = Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType "application/json"
$settings.Item("dataViewName") = ( $dataviews.list | Out-GridView -PassThru ).Name
}
################################################
#
# LOGIN VIA API
#
################################################
#-----------------------------------------------
# LOAD CREDENTIALS
#-----------------------------------------------
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $settings.user,($settings.password | ConvertTo-SecureString)
$user = $credentials.GetNetworkCredential().Username
$pw = $credentials.GetNetworkCredential().password
#-----------------------------------------------
# PREPARE LOGIN
#-----------------------------------------------
$headers = @{
"accept"="application/json"
}
switch ( $settings.loginType ) {
#-----------------------------------------------
# SIMPLE LOGIN PREPARATION
#-----------------------------------------------
"SIMPLE" {
$endpoint = Get-Endpoint -key "CreateSessionSimple"
$body = @{
"UserLogin"=$user
"Password"=$pw
}
}
#-----------------------------------------------
# SALTED LOGIN PREPARATION
#-----------------------------------------------
"SALTED" {
# GET LOGIN DETAILS FIRST
$endpoint = Get-Endpoint -key "CreateLoginParameters"
$body = @{
"userName"=$user
}
$uri = Resolve-Url -endpoint $endpoint
$loginDetails = Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType "application/x-www-form-urlencoded" -Headers $headers -Body $body -Verbose
# GET ALL INFORMATION TOGETHER
$endpoint = Get-Endpoint -key "CreateSessionSalted"
<#
1. "Encrypt" password + optionally add salt
2. Hash that string
3. Add LoginSalt and hash again
#>
$pwStepOne = Crypt-Password -password $pw
if ($loginDetails.saltPassword -eq $true -and $loginDetails.userSalt -ne "") {
# TODO [ ] test password salting (and if userSalt from API is the correct value for that)
# TODO [ ] put salt in settings
$pwStepOne += $loginDetails.userSalt
}
$pwStepTwo = Get-StringHash -inputString $pwStepOne -hashName $loginDetails.hashAlgorithm -uppercase $false
$pwStepThree = Get-StringHash -inputString $pwStepTwo -hashName $loginDetails.hashAlgorithm -salt $loginDetails.loginSalt -uppercase $false
$body = @{
"Username"=$user
"LoginSalt"=$loginDetails.loginSalt
"PasswordHash"=$pwStepThree
}
}
}
#-----------------------------------------------
# LOGIN + GET SESSION
#-----------------------------------------------
$uri = Resolve-Url -endpoint $endpoint
$login = Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType "application/x-www-form-urlencoded" -Headers $headers -Body $body -Verbose
$login
$auth = "Bearer $( $login.accessToken )"
$session = $login.sessionId
$headers += @{ "Authorization"=$auth }
################################################
#
# DO SOMETHING
#
################################################
#-----------------------------------------------
# GET SESSION DETAILS
#-----------------------------------------------
$endpoint = Get-Endpoint -key "GetSessionDetails"
$uri = Resolve-Url -endpoint $endpoint -additional @{"sessionId"=$session}
$sessionDetails = Invoke-RestMethod -Uri $uri -Method $endpoint.method -Headers $headers -Verbose
$sessionDetails
################################################
#
# UPLOAD TEMPORARY FILE
#
################################################
<#
Some links for multipart upload
hint from https://stackoverflow.com/questions/36268925/powershell-invoke-restmethod-multipart-form-data
other: https://stackoverflow.com/questions/51739874/multipart-form-script-works-perfectly-in-powershell-but-not-in-powershell-core
with .NET classes https://get-powershellblog.blogspot.com/2017/09/multipartform-data-support-for-invoke.html
#>
# Choose file
$fileItem = Get-Item -Path "<uploadfile>"
$fileId = [System.Guid]::NewGuid().ToString()
switch ( $settings.uploadType ) {
#-----------------------------------------------
# UPLOAD IN ONE PART
#-----------------------------------------------
"ONEPART" {
# Prepare multipart
$multipart = Prepare-MultipartUpload -path $fileItem.FullName
# Prepare API call
$endpoint = Get-Endpoint -key "UpsertTemporaryFile"
$uri = Resolve-Url -endpoint $endpoint -additional @{"id"=$fileId}
# Execute API call
$tempUpload = Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType $multipart.contentType -Headers $headers -Body $multipart.body -Verbose
}
#-----------------------------------------------
# UPLOAD IN MULTIPLE PARTS
#-----------------------------------------------
"MULTIPART" {
# TODO [ ] put no of parts or buffersize in settings
$noParts = 2
$partPrefix = "part"
$secondsToWait = 30
# create chunks
$buffer = $fileItem.Length / $noParts
$chunks = Split-File -inFile $fileItem.FullName -outPrefix $partPrefix -bufSize $buffer
# Prepare API call
$endpoint = Get-Endpoint -key "UpsertTemporaryFilePart"
# Upload chunks
$final = $false
$tempUpload = @()
for ( $i = 0; $i -lt $chunks.Length ; $i++ ) {
# Choose part
$partFile = $chunks[$i]
if ( $i+1 -eq $chunks.Length ) { $final = $true }
# Prepare part
$multipart = Prepare-MultipartUpload -path $partFile -part $true
$uri = Resolve-Url -endpoint $endpoint -additional @{"id"=$fileId; "partNumber"=$i} -query @{"finalPart"="$( $final )".ToLower()}
# Execute API call
$tempUpload += Invoke-RestMethod -Uri $uri -Method $endpoint.method -ContentType $multipart.contentType -Headers $headers -Body $multipart.body -Verbose
}
# Wait a moment for the file to be processed
Start-Sleep -Seconds $secondsToWait
# Remove part files
$chunks | ForEach { Remove-Item $_ }
}
}
# Result of upload
$tempUpload
#-----------------------------------------------
# DOWNLOAD IN ONE PART
#-----------------------------------------------
# Where to save the file
$dlLocation = "$( $fileItem.DirectoryName )\$( $fileId )$( $fileItem.Extension )"
# Download file directly
$endpoint = Get-Endpoint -key "GetTemporaryFile"
$uri = Resolve-Url -endpoint $endpoint -additional @{"id"=$fileId}
Invoke-RestMethod -Uri $uri -Method $endpoint.method -Headers $headers -Verbose -ContentType "application/octet-stream" -OutFile $dlLocation
################################################
#
# PEOPLESTAGE
#
################################################
<#
# show all available endpoints
$endpoints | Out-GridView
# Get the current system
$endpoint = Get-Endpoint -key "GetPeopleStageSystems"
$uri = Resolve-Url -endpoint $endpoint -additional @{"dataViewName"=$settings.dataViewName} -query @{"count"=1000000}
$peoplestageSystems = Invoke-RestMethod -Uri $uri -Method $endpoint.method -Headers $headers -Verbose -ContentType "application/json"
$systemName = $peoplestageSystems.list[0].systemName
$diagramId = $peoplestageSystems.list[0].diagramId
$programmeId = $peoplestageSystems.list[0].programmeId
# Get all campaigns by diagram ID
$endpoint = Get-Endpoint -key "GetElementStatusForDescendants"
$uri = Resolve-Url -endpoint $endpoint -additional @{"dataViewName"=$settings.dataViewName;"systemName"=$systemName;"elementId"=$diagramId} -query @{"offset"=0;"count"=1000000;"orderBy"="-LastRan";"filter"="Type eq 'Campaign'"}
$peoplestageCampaigns = Invoke-RestMethod -Uri $uri -Method $endpoint.method -Headers $headers -Verbose -ContentType "application/json"
$peoplestageCampaigns.list | Out-GridView
# Get all audiences for campaigns
$campaignElements = @()
$peoplestageCampaigns.list | select -first 5 | ForEach {
$campaign = $_
$endpoint = Get-Endpoint -key "GetElementChildren"
$uri = Resolve-Url -endpoint $endpoint -additional @{"dataViewName"=$settings.dataViewName;"systemName"=$systemName;"elementId"=$campaign.id} -query @{"offset"=0;"count"=1000000}
$elements = Invoke-RestMethod -Uri $uri -Method $endpoint.method -Headers $headers -Verbose -ContentType "application/json"
$elements #.list | Out-GridView
$campaignElements += $elements.list
}
#>
@gitfvb
Copy link
Author

gitfvb commented May 2, 2019

In the Orbit API configurator you can set the temp file directory in "TemporaryFile service", normally in c:\temp\Orbit...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment