UpBank.psm1
|
$Global:token = $null $Global:profileName = $null $Global:APIVersion = $null $UpBankConfigurationFile = Join-Path $env:LOCALAPPDATA UpBankConfiguration.clixml if (Test-Path $UpBankConfigurationFile) { $script:UpBankConfiguration = Import-Clixml $UpBankConfigurationFile if ($UpBankConfiguration.APIVersion) { $Global:APIVersion = $UpBankConfiguration.APIVersion } if ($UpBankConfiguration.defaultProfile) { $profile = $UpBankConfiguration.defaultProfile $Global:token = [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($UpBankConfiguration.$profile.PersonalAccessToken)) } } else { $script:UpBankConfiguration = @{ APIVersion = "v1" } $Global:APIVersion = "v1" } if (-not $Global:APIVersion) { $Global:APIVersion = "v1" } # --- Private helpers (not exported) --- function Invoke-UpBankGet { <# .SYNOPSIS Private helper. Performs a GET request against the Up Bank API with retry on rate limiting (HTTP 429). #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [String] $Uri ) $attempt = 0 while ($true) { try { return Invoke-RestMethod -Method Get ` -Uri $Uri ` -Headers @{Authorization = "Bearer $($Global:token)" } } catch { $statusCode = 0 if ($_.Exception.Response) { $statusCode = [int]$_.Exception.Response.StatusCode } $attempt++ if ($statusCode -eq 429 -and $attempt -lt 3) { $delay = [int][Math]::Pow(2, $attempt) try { $retryAfter = $null $headers = $_.Exception.Response.Headers if ($headers -is [System.Net.WebHeaderCollection]) { # Windows PowerShell 5.1 $retryAfter = $headers['Retry-After'] } elseif ($headers.Contains('Retry-After')) { # PowerShell 7+ (HttpResponseHeaders) $retryAfter = ($headers.GetValues('Retry-After') | Select-Object -First 1) } if ($retryAfter) { $delay = [int]$retryAfter } } catch {} Write-Verbose "Up Bank API rate limit reached (429). Retrying in $($delay) seconds." Start-Sleep -Seconds $delay } else { throw } } } } function Invoke-UpBankRequest { <# .SYNOPSIS Private helper. Builds an Up Bank API request URI, validates the Personal Access Token and (optionally) follows the links.next cursor to paginate results. .PARAMETER Path Relative API path. e.g 'transactions', 'accounts/<id>/transactions', 'util/ping' .PARAMETER Query Ordered dictionary of query string parameters. Keys are used literally (e.g 'page[size]'), values are URL encoded. .PARAMETER Paginate Follow the links.next cursor returned by the API and emit each record to the pipeline. .PARAMETER First With -Paginate, stop after this many records. 0 = return everything. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [String] $Path, [System.Collections.IDictionary] $Query, [switch] $Paginate, [int] $First = 0 ) if ([string]::IsNullOrEmpty($Global:token)) { throw "No Personal Access Token found in Up Bank Configuration. Use Set-UpBankCredential and Save-UpBankConfiguration to securely store your Up Bank Personal Access Token." } $uri = "https://api.up.com.au/api/$($Global:APIVersion)/$($Path)" if ($Query -and $Query.Count -gt 0) { $pairs = foreach ($key in $Query.Keys) { "{0}={1}" -f $key, [uri]::EscapeDataString([string]$Query[$key]) } $uri = "$($uri)?$($pairs -join '&')" } Write-Verbose $uri if (-not $Paginate) { return Invoke-UpBankGet -Uri $uri } $emitted = 0 $response = Invoke-UpBankGet -Uri $uri while ($true) { if ($response.data) { foreach ($item in @($response.data)) { $item $emitted++ if ($First -gt 0 -and $emitted -ge $First) { return } } } if (-not $response.links.next) { return } # links.next is a complete cursor URL that carries all filters $response = Invoke-UpBankGet -Uri $response.links.next } } function Format-UpBankDate { <# .SYNOPSIS Private helper. Formats a DateTime as RFC-3339 for the Up Bank API filter[since]/filter[until] parameters. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [DateTime] $Date ) if ($Date.Kind -eq [System.DateTimeKind]::Unspecified) { $Date = [DateTime]::SpecifyKind($Date, [System.DateTimeKind]::Local) } return $Date.ToString("yyyy-MM-ddTHH:mm:ssK") } # --- Exported cmdlets --- function Set-UpBankCredential { <# .SYNOPSIS Sets the default Up Bank API credentials. .DESCRIPTION Sets the default Up Bank API credentials. Configuration values can be securely saved to a user's profile using Save-UpBankConfiguration. .PARAMETER Credential A standard Powershell Credential object. .EXAMPLE $cred = Get-Credential -Message 'Custom message...' -UserName 'Custom Username' Set-UpBankCredential -Credential $cred .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [PSCredential]$Credential ) $script:UpBankConfiguration[$Credential.UserName] = @{PersonalAccessToken = $Credential.Password } $Global:token = [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($Credential.Password)) $Global:profileName = $Credential.UserName } function Switch-UpBankProfile { <# .SYNOPSIS Changes the Up Bank Account Credentials used to a different Up Bank Profile. .DESCRIPTION Changes the Up Bank Account Credentials used to a different Up Bank Profile. Optionally sets the default Up Bank API credentials to a different profile. Configuration values can be securely saved to a user's profile using Save-UpBankConfiguration. .PARAMETER profile Profile Name to switch too. .PARAMETER default (Optional) Set the profile being switched to as the new Default Profile that will be loaded on Module Load. .EXAMPLE Switch-UpBankProfile -profile Darren .EXAMPLE Switch-UpBankProfile -profile Darren -default .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$profile, [Parameter(Mandatory = $false, Position = 0)] [switch]$default ) if ($script:UpBankConfiguration.$profile) { $Global:token = [System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($script:UpBankConfiguration.$profile.PersonalAccessToken)) $Global:profileName = $profile if ($default) { $script:UpBankConfiguration.defaultProfile = $profile Export-Clixml -Path $UpBankConfigurationFile -InputObject $script:UpBankConfiguration } } else { Write-Error "No Profile with name $($profile) was found in the Up Bank Configuration file." return } } function Save-UpBankConfiguration { <# .SYNOPSIS Saves default Up Bank configuration to a file in the current users Profile. .DESCRIPTION Saves default Up Bank configuration to a file within the current users Profile. If it exists, this file is then read, each time the Up Bank Module is imported. Allowing settings to persist between sessions. .EXAMPLE Save-UpBankConfiguration .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param ([switch]$default) if ($default) { $script:UpBankConfiguration.defaultProfile = $Global:profileName } Export-Clixml -Path $UpBankConfigurationFile -InputObject $script:UpBankConfiguration } function Test-UpBankAPI { <# .SYNOPSIS Ping UpBank .DESCRIPTION Ping UpBank to validate the currently loaded Personal Access Token. .EXAMPLE Test-UpBankAPI .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param() $response = Invoke-UpBankRequest -Path "util/ping" return $response.meta } function Get-UpBankAccounts { <# .SYNOPSIS List UpBank Accounts .DESCRIPTION List UpBank Accounts. Retrieves all accounts, following pagination automatically. .PARAMETER AccountType (Optional) Only return accounts of this type. SAVER, TRANSACTIONAL or HOME_LOAN. .PARAMETER OwnershipType (Optional) Only return accounts with this ownership structure. INDIVIDUAL or JOINT. .PARAMETER PageSize The number of records to return in each page request. Defaults to 100 (the API maximum). .EXAMPLE Get-UpBankAccounts .EXAMPLE Get-UpBankAccounts -AccountType SAVER .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [ValidateSet("SAVER", "TRANSACTIONAL", "HOME_LOAN")] [String] $AccountType, [Parameter(Mandatory = $false)] [ValidateSet("INDIVIDUAL", "JOINT")] [String] $OwnershipType, [Parameter(Mandatory = $false, Position = 0)] [ValidateRange(1, 100)] [int] $PageSize = 100 ) $query = [ordered]@{} $query['page[size]'] = $PageSize if ($AccountType) { $query['filter[accountType]'] = $AccountType } if ($OwnershipType) { $query['filter[ownershipType]'] = $OwnershipType } Invoke-UpBankRequest -Path "accounts" -Query $query -Paginate } function Get-UpBankAccount { <# .SYNOPSIS Get an UpBank Account .DESCRIPTION Get an UpBank Account .PARAMETER id (Required) The id of account to return .EXAMPLE Get-UpBankAccount -id af78838b-95a9-47f6-aa38-70d827af9539 .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [String] $id ) $response = Invoke-UpBankRequest -Path "accounts/$($id)" return $response.data } function Get-UpBankTransactions { <# .SYNOPSIS Retrieve a list of all transactions across all accounts for the currently authenticated user. .DESCRIPTION Retrieve a list of all transactions across all accounts for the currently authenticated user. Results are returned newest first. Pagination is followed automatically. .PARAMETER Status (Optional) Only return transactions with this status. HELD or SETTLED. .PARAMETER Since (Optional) The start date-time from which to return records. Accepts a DateTime or a parseable date string. .PARAMETER Until (Optional) The end date-time up to which to return records. Accepts a DateTime or a parseable date string. .PARAMETER Category (Optional) Only return transactions with this category id (parent or child). e.g groceries .PARAMETER Tag (Optional) Only return transactions with this exact tag. .PARAMETER PageSize The number of records to return in each page request. Defaults to 100 (the API maximum). .PARAMETER First The total number of records to return. Defaults to 100. Not valid with -All. .PARAMETER All Return every matching transaction, following pagination until exhausted. .EXAMPLE Get-UpBankTransactions -First 30 .EXAMPLE Get-UpBankTransactions -Since '2025-07-01' -Until '2026-07-01' -All .EXAMPLE Get-UpBankTransactions -Status SETTLED -Category groceries -First 50 .LINK http://darrenjrobinson.com/ #> [CmdletBinding(DefaultParameterSetName = "Limited")] param( [Parameter(Mandatory = $false)] [ValidateSet("HELD", "SETTLED")] [String] $Status, [Parameter(Mandatory = $false)] [DateTime] $Since, [Parameter(Mandatory = $false)] [DateTime] $Until, [Parameter(Mandatory = $false)] [String] $Category, [Parameter(Mandatory = $false)] [String] $Tag, [Parameter(Mandatory = $false)] [ValidateRange(1, 100)] [int] $PageSize = 100, [Parameter(Mandatory = $false, ParameterSetName = "Limited")] [ValidateRange(1, [int]::MaxValue)] [int] $First = 100, [Parameter(Mandatory = $false, ParameterSetName = "All")] [switch] $All ) $recordsToReturn = 0 if (-not $All) { $recordsToReturn = $First } $size = $PageSize if ($recordsToReturn -gt 0 -and $recordsToReturn -lt $size) { $size = $recordsToReturn } $query = [ordered]@{} $query['page[size]'] = $size if ($Status) { $query['filter[status]'] = $Status } if ($PSBoundParameters.ContainsKey('Since')) { $query['filter[since]'] = Format-UpBankDate -Date $Since } if ($PSBoundParameters.ContainsKey('Until')) { $query['filter[until]'] = Format-UpBankDate -Date $Until } if ($Category) { $query['filter[category]'] = $Category } if ($Tag) { $query['filter[tag]'] = $Tag } Invoke-UpBankRequest -Path "transactions" -Query $query -Paginate -First $recordsToReturn } function Get-UpBankTransaction { <# .SYNOPSIS Retrieve a specific Up Bank Transaction. .DESCRIPTION Retrieve a specific Up Bank Transaction. .PARAMETER id (Required) ID of the transaction to return. .EXAMPLE Get-UpBankTransaction -id 167ae4de-084f-4ed0-85f7-152525f790fb .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [String] $id ) $response = Invoke-UpBankRequest -Path "transactions/$($id)" return $response.data } function Get-UpBankAccountTransactions { <# .SYNOPSIS Retrieve a list of all transactions for a specific account for the currently authenticated user. .DESCRIPTION Retrieve a list of all transactions for a specific account for the currently authenticated user. Results are returned newest first. Pagination is followed automatically. Accepts account objects from Get-UpBankAccounts via the pipeline. .PARAMETER AccountId (Required) Account ID to return transactions from. .PARAMETER Status (Optional) Only return transactions with this status. HELD or SETTLED. .PARAMETER Since (Optional) The start date-time from which to return records. Accepts a DateTime or a parseable date string. .PARAMETER Until (Optional) The end date-time up to which to return records. Accepts a DateTime or a parseable date string. .PARAMETER Category (Optional) Only return transactions with this category id (parent or child). e.g groceries .PARAMETER Tag (Optional) Only return transactions with this exact tag. .PARAMETER PageSize The number of records to return in each page request. Defaults to 100 (the API maximum). .PARAMETER First The total number of records to return. Defaults to 100. Not valid with -All. .PARAMETER All Return every matching transaction, following pagination until exhausted. .EXAMPLE Get-UpBankAccountTransactions -AccountId 4bda3ac4-034e-44bc-b761-8277ccbfe2ee .EXAMPLE Get-UpBankAccounts | Get-UpBankAccountTransactions -All -Since '2025-07-01' .LINK http://darrenjrobinson.com/ #> [CmdletBinding(DefaultParameterSetName = "Limited")] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [Alias("accountID", "id")] [String] $AccountId, [Parameter(Mandatory = $false)] [ValidateSet("HELD", "SETTLED")] [String] $Status, [Parameter(Mandatory = $false)] [DateTime] $Since, [Parameter(Mandatory = $false)] [DateTime] $Until, [Parameter(Mandatory = $false)] [String] $Category, [Parameter(Mandatory = $false)] [String] $Tag, [Parameter(Mandatory = $false)] [ValidateRange(1, 100)] [int] $PageSize = 100, [Parameter(Mandatory = $false, ParameterSetName = "Limited")] [ValidateRange(1, [int]::MaxValue)] [int] $First = 100, [Parameter(Mandatory = $false, ParameterSetName = "All")] [switch] $All ) process { $recordsToReturn = 0 if (-not $All) { $recordsToReturn = $First } $size = $PageSize if ($recordsToReturn -gt 0 -and $recordsToReturn -lt $size) { $size = $recordsToReturn } $query = [ordered]@{} $query['page[size]'] = $size if ($Status) { $query['filter[status]'] = $Status } if ($PSBoundParameters.ContainsKey('Since')) { $query['filter[since]'] = Format-UpBankDate -Date $Since } if ($PSBoundParameters.ContainsKey('Until')) { $query['filter[until]'] = Format-UpBankDate -Date $Until } if ($Category) { $query['filter[category]'] = $Category } if ($Tag) { $query['filter[tag]'] = $Tag } Invoke-UpBankRequest -Path "accounts/$($AccountId)/transactions" -Query $query -Paginate -First $recordsToReturn } } function Get-UpBankCategories { <# .SYNOPSIS List UpBank Categories .DESCRIPTION List UpBank Categories. This endpoint is not paginated; all categories are returned. .EXAMPLE Get-UpBankCategories .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param() $response = Invoke-UpBankRequest -Path "categories" return $response.data } function Get-UpBankCategory { <# .SYNOPSIS Retrieve a specific Up Bank Category. .DESCRIPTION Retrieve a specific Up Bank Category. .PARAMETER id (Required) ID of the Category to return. .EXAMPLE Get-UpBankCategory -id technology .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [String] $id ) $response = Invoke-UpBankRequest -Path "categories/$($id)" return $response.data } function Get-UpBankTags { <# .SYNOPSIS List UpBank Tags .DESCRIPTION List UpBank Tags. Retrieves all tags, following pagination automatically. .PARAMETER PageSize The number of records to return in each page request. Defaults to 100 (the API maximum). .EXAMPLE Get-UpBankTags .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $false, Position = 0)] [ValidateRange(1, 100)] [int] $PageSize = 100 ) $query = [ordered]@{} $query['page[size]'] = $PageSize Invoke-UpBankRequest -Path "tags" -Query $query -Paginate } function ConvertTo-UpBankTransactionReport { <# .SYNOPSIS Flattens Up Bank transaction objects into flat objects suitable for Export-Csv. .DESCRIPTION Flattens the nested JSON:API transaction objects returned by Get-UpBankTransactions and Get-UpBankAccountTransactions into flat PSCustomObjects (one row per transaction) suitable for Export-Csv, Out-GridView or reporting. e.g providing transaction data to an accountant. .PARAMETER Transaction One or more Up Bank transaction objects. Accepts pipeline input. .PARAMETER ResolveAccountNames (Optional) Resolve account ids to account display names (adds an AccountName column). Makes a single call to Get-UpBankAccounts. .EXAMPLE Get-UpBankTransactions -All -Since '2025-07-01' -Until '2026-07-01' | ConvertTo-UpBankTransactionReport | Export-Csv .\UpBank-FY2025-26.csv -NoTypeInformation .EXAMPLE Get-UpBankTransactions -First 50 | ConvertTo-UpBankTransactionReport -ResolveAccountNames | Out-GridView .LINK http://darrenjrobinson.com/ #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [object[]] $Transaction, [Parameter(Mandatory = $false)] [switch] $ResolveAccountNames ) begin { $accountNames = @{} if ($ResolveAccountNames) { foreach ($account in (Get-UpBankAccounts)) { $accountNames[$account.id] = $account.attributes.displayName } } } process { foreach ($txn in $Transaction) { $attributes = $txn.attributes $relationships = $txn.relationships $createdAt = "" if ($attributes.createdAt) { $createdAt = ([DateTime]$attributes.createdAt).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") } $settledAt = "" if ($attributes.settledAt) { $settledAt = ([DateTime]$attributes.settledAt).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") } $foreignAmount = $null $foreignCurrency = "" if ($attributes.foreignAmount) { $foreignAmount = [decimal]$attributes.foreignAmount.value $foreignCurrency = $attributes.foreignAmount.currencyCode } $category = "" if ($relationships.category.data) { $category = $relationships.category.data.id } $parentCategory = "" if ($relationships.parentCategory.data) { $parentCategory = $relationships.parentCategory.data.id } $tags = "" if ($relationships.tags.data) { $tags = (@($relationships.tags.data) | ForEach-Object { $_.id }) -join ";" } $accountId = "" if ($relationships.account.data) { $accountId = $relationships.account.data.id } $transferAccountId = "" if ($relationships.transferAccount.data) { $transferAccountId = $relationships.transferAccount.data.id } $roundUp = $null if ($attributes.roundUp -and $attributes.roundUp.amount) { $roundUp = [decimal]$attributes.roundUp.amount.value } $cardMethod = "" $cardSuffix = "" if ($attributes.cardPurchaseMethod) { $cardMethod = [string]$attributes.cardPurchaseMethod.method $cardSuffix = [string]$attributes.cardPurchaseMethod.cardNumberSuffix } $note = "" if ($attributes.note) { $note = [string]$attributes.note.text } $report = [ordered]@{ Id = $txn.id CreatedAt = $createdAt SettledAt = $settledAt Status = [string]$attributes.status Description = [string]$attributes.description Message = [string]$attributes.message RawText = [string]$attributes.rawText Amount = [decimal]$attributes.amount.value CurrencyCode = [string]$attributes.amount.currencyCode ForeignAmount = $foreignAmount ForeignCurrency = $foreignCurrency TransactionType = [string]$attributes.transactionType Category = $category ParentCategory = $parentCategory Tags = $tags AccountId = $accountId } if ($ResolveAccountNames) { $accountName = "" if ($accountNames.ContainsKey($accountId)) { $accountName = $accountNames[$accountId] } $report['AccountName'] = $accountName } $report['TransferAccountId'] = $transferAccountId $report['RoundUp'] = $roundUp $report['CardMethod'] = $cardMethod $report['CardSuffix'] = $cardSuffix $report['Note'] = $note $report['DeepLinkURL'] = [string]$attributes.deepLinkURL [PSCustomObject]$report } } } |