STOXR.psm1
#requires -version 3 #$VerbosePreference = 'Continue' <# Copyright (c) 2015-2017, Svendsen Tech, Joakim Borger Svendsen. All rights reserved. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #> $ErrorActionPreference = 'Stop' #Set-StrictMode -Version Latest function GetFuzzyCurrencyMatch { [CmdletBinding()] param([string] $Currency) # Hashtable for partial hits, and possibly complete currency text hits (System.String.Contains()). $Hits = @{} <#foreach ($Curr in $Script:CurrencyHash.GetEnumerator()) { if ($Curr.Name -eq $Currency) { Write-Verbose -Message "Got an exact hit for ${Currency}: $($Curr.Value + ' (' + $Curr.Name + ')')." # Return direct (unique currency abbreviation) hit as (the expected) hashtable. #$Script:Fuzzy = $false return @{ $Curr.Name = $Curr.Value } } }#> # Optimized with no drawbacks, I think... if ($Script:CurrencyHash.ContainsKey($Currency)) { return @{ $Currency = $Script:CurrencyHash[$Currency] } } # An extra loop first to see if the possibly partial description matches uniquely. if (@($Script:CurrencyHash.Values | Where-Object { $_ -like "*${Currency}*" }).Count -eq 1) { foreach ($Curr in $Script:CurrencyHash.GetEnumerator()) { if ($Curr.Value -like "*${Currency}*") { Write-Verbose -Message "Only one instance of this value text, so it's uniquely identifying ($($Curr.Value) ($($Curr.Name)))." # Only one instance, so it's unique. #$Script:Fuzzy = $false return @{ $Curr.Name = $Curr.Value } } } } foreach ($Curr in $Script:CurrencyHash.GetEnumerator()) { # Now handling this in a separate loop first. <#if ($Curr.Name -eq $Currency) { Write-Verbose -Message "Got an exact hit for ${Currency}: $($Curr.Value + ' (' + $Curr.Name + ')')." # Return direct (unique currency abbreviation) hit as (the expected) hashtable. $Script:Fuzzy = $false return @{ $Curr.Name = $Curr.Value } }#> # .Contains($Currency.ToLower())) { # abandoning this in favor of -like to allow for wildcards as input if ($Curr.Value.ToLower() -like "*${Currency}*") { Write-Verbose -Message "Got a (possibly) partial hit for ${Currency}: $($Curr.Value + ' (' + $Curr.Name + ')')." $Hits[$Curr.Name] = $Curr.Value # The uniquely identifying part above makes this prone to errors rather than being helpful. # Returning possible currencies instead. <# If the length is n % or more of the total length, stop looking, and assume it's exact. # This isn't perfect, but seems to be an improvement. # In addition add that if the length of the string is greater than 14, then 55 % is enough ... scientific numbers, bro. if ($Currency.Length / $Curr.Value.Length -gt 0.7 -or ` ($Curr.Value.Length -gt 18 -and ($Currency.Length / $Curr.Value.Length -gt 0.62))) { # Return one hit only, assume it's good ... write-Verbose -Message "Returning single hit since it overlaps more than 70 % with the compared description (or 62 % if length of string greater than 18). $($Curr.Value + ' (' + $Curr.Name + ')')." #$Script:Fuzzy = $true return @{ $Curr.Name = $Curr.Value } #break } else { continue }#> } # Final fuzzy step. Split into "words" and check each one. # Cache -split operation for an ever so slight performance gain. $Values = $Curr.Value -split '\s+' # Get matches for each word. foreach ($e in @($Currency -split '\s+')) { foreach ($TmpValue in $Values) { if ($TmpValue -like "*$e*") { Write-Verbose -Message "Got a partial hit for ${Currency}: $($Curr.Value + ' (' + $Curr.Name + ')')." $Hits[$Curr.Name] = $Curr.Value continue } } } } # If we get here we've got something fuzzy going on. #$Script:Fuzzy = $true return $Hits } function GetCurrencyString { param( [float] $Amount, [string] $From, [string] $ToCurr, [int] $Precision = 4) # USD is special since everything is calculated based on that rate. if ($From -ieq "USD") { "{0:N$Precision} {1} ({2}) is {3:N$Precision} {4} ({5}, {6:N$Precision}/USD). Date: {7}." -f ` [float]$Amount, $From.ToUpper(), $CurrencyHash.$From, [float]($Amount / $Script:ExchangeRates.Data.rates.$From * $Script:ExchangeRates.Data.rates.$ToCurr), $ToCurr.ToUpper(), $CurrencyHash.$ToCurr, $Script:ExchangeRates.Data.rates.$ToCurr, $Script:ExchangeRates.Date.ToString('yyyy-MM-dd HH:mm:ss') } elseif ($ToCurr -ieq "USD") { "{0:N$Precision} {1} ({2} at {3:N$Precision}/USD) is {4:N$Precision} {5} ({6}). Date: {7}." -f ` [float]$Amount, $From.ToUpper(), $CurrencyHash.$From, $Script:ExchangeRates.Data.rates.$From, [float]($Amount / $Script:ExchangeRates.Data.rates.$From * $Script:ExchangeRates.Data.rates.$ToCurr), $ToCurr.ToUpper(), $CurrencyHash.$ToCurr, $Script:ExchangeRates.Date.ToString('yyyy-MM-dd HH:mm:ss') } else { "{0:N$Precision} {1} ({2} at {3:N$Precision}/USD) is {4:N$Precision} {5} ({6}, {7:N$Precision}/USD). Date: {8}." -f ` [float]$Amount, $From.ToUpper(), $CurrencyHash.$From, $Script:ExchangeRates.Data.rates.$From, [float]($Amount / $Script:ExchangeRates.Data.rates.$From * $Script:ExchangeRates.Data.rates.$ToCurr), $ToCurr.ToUpper(), $CurrencyHash.$ToCurr, $Script:ExchangeRates.Data.rates.$ToCurr, $Script:ExchangeRates.Date.ToString('yyyy-MM-dd HH:mm:ss') } } function GetCurrencyObject { param( [decimal] $Amount, [string] $FromCurrency, [string] $ToCurrency, [int] $Precision = 4) [pscustomobject] @{ FromAmount = "{0:N$Precision}" -f $Amount FromAmountNumerical = $Amount FromCurrency = $FromCurrency.ToUpper() FromCurrencyText = $Script:CurrencyHash.$FromCurrency ToAmount = "{0:N$Precision}" -f [float]($Amount / $Script:ExchangeRates.Data.rates.$FromCurrency * $Script:ExchangeRates.Data.rates.$ToCurrency) ToAmountNumerical = $Amount / $Script:ExchangeRates.Data.rates.$FromCurrency * $Script:ExchangeRates.Data.rates.$ToCurrency ToCurrency = $ToCurrency.ToUpper() ToCurrencyText = $Script:CurrencyHash.$ToCurrency FromCurrencyPerUSD = "{0:N$Precision}" -f $Script:ExchangeRates.Data.rates.$FromCurrency ToCurrencyPerUSD = "{0:N$Precision}" -f $Script:ExchangeRates.Data.rates.$ToCurrency Date = $Script:ExchangeRates.Date Precision = $Precision #Fuzzy = $Script:Fuzzy } } <# .SYNOPSIS Get currency exchange data cache interval in seconds. This is the time between each refresh of currency exchange data. The first time the module is imported, it is set to 30 minutes (60 seconds * 30). Change it with Set-STOXRCacheInterval. #> function Get-STOXRCacheInterval { [CmdletBinding()] param() $PersistentFilePath = "$PSScriptRoot\STOXRCacheInterval.xml" if (-not (Get-Variable -Scope Script -Name CacheInterval -ErrorAction SilentlyContinue)) { if (-not (Test-Path -LiteralPath $PersistentFilePath)) { Write-Warning -Message "Performing first-run operation of setting currency exchange data refresh interval to 30 * 60 seconds (30 minutes). Use Set-STOXRCacheInterval to change it." [int] $Script:CacheInterval = 30 * 60 $Script:CacheInterval | Export-Clixml -LiteralPath $PersistentFilePath } else { Write-Verbose -Message "Reading cache interval from '$PersistentFilePath'. Use Set-STOXRCacheInterval to change it." [int] $Script:CacheInterval = Import-Clixml -LiteralPath $PersistentFilePath } } # Return currently set value (possibly (just) initialized above). return $Script:CacheInterval } <# .SYNOPSIS Set currency exchange data cache interval in seconds. This is the time between each refresh of currency exchange data. The first time the module is imported, it is set to 30 minutes (60 seconds * 30). Display current value with Get-STOXRCacheInterval. .PARAMETER CacheInterval Currency exchange data cache time in seconds. #> function Set-STOXRCacheInterval { [CmdletBinding()] param( [Parameter(Mandatory=$true)][ValidateRange(1, 2147483647)][int] $CacheInterval) $PersistentFilePath = "$PSScriptRoot\STOXRCacheInterval.xml" [int] $Script:CacheInterval = $CacheInterval $Script:CacheInterval | Export-Clixml -LiteralPath $PersistentFilePath } <# .SYNOPSIS Display the currently set Open Exchange Rates App ID (if any). Change it with Set-STOXRAppID. #> function Get-STOXRAppID { [CmdletBinding()] param() $PersistentFilePath = "$PSScriptRoot\STOXRAppID.xml" if (-not (Get-Variable -Scope Script -Name STOpenExchAppID -ErrorAction SilentlyContinue)) { if (-not (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf)) { Write-Warning -Message "No Open Exchange Rates App ID has been set. You need to set it to use this software. This can be obtained at https://openexchangerates.org/ . Use Set-STOXRAppID to set it." return } else { Write-Verbose -Message "Reading App ID from '$PersistentFilePath'. Use Set-STOXRAppID if you need to change it." [string] $Script:STOpenExchAppID = Import-Clixml -LiteralPath $PersistentFilePath } } # Return currently set value, return $Script:STOpenExchAppID } <# .SYNOPSIS Change or set the Open Exchange Rates API key. Obtain the App ID / API key at https://openexchangerates.org/ . .PARAMETER AppID The API key to set. .PARAMETER Force Do not prompt to overwrite an existing key (just do it). .EXAMPLE Set-STOXRAppID -AppID '1234567890abcde' .EXAMPLE Set-STOXRAppID -AppID '1234567890abcde' -Force #> function Set-STOXRAppID { [CmdletBinding()] param( [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $AppID, [switch] $Force) $PersistentFilePath = "$PSScriptRoot\STOXRAppID.xml" if (-not $Force -and (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf)) { $Answer = Read-Host -Prompt "An existing Open Exchange Rates App ID was found. Are you sure you want to overwrite it? Use -Force to avoid this prompt. Default is 'Y'." if ($Answer -match 'n') { Write-Verbose -Message "Aborted." return } } [string] $Script:STOpenExchAppID = $AppID $Script:STOpenExchAppID | Export-Clixml -LiteralPath $PersistentFilePath } <# .SYNOPSIS Get Svendsen Tech Open Exchange proxy information. #> function Get-STOXRProxy { [CmdletBinding()] param([switch] $IsInternalCommand) $PersistentFilePath = "$PSScriptRoot\STOXRProxy.xml" if (-not (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf)) { if (-not $IsInternalCommand) { Write-Warning -Message "No proxy information has been set." } return } $ProxyInfo = Import-Clixml -LiteralPath $PersistentFilePath <#if ($ProxyInfo | Get-Member -Name ProxyCredential -MemberType NoteProperty -ErrorAction SilentlyContinue) { if ($PasswordAsPlainText) { $ProxyInfo | Select Proxy, @{ Name = "PasswordAsPlainText"; Expression = { $_.PSCredential.GetNetworkCredential().Password } } return } }#> return $ProxyInfo } <# .SYNOPSIS Set the Svendsen Tech Open Exchange proxy information. .PARAMETER ProxyUri URI for the proxy to use. Example: http://proxy.internal.company.com:8080 .PARAMETER ProxyCredential A PSCredential object with credentials to use for the proxy. .PARAMETER ProxyUseDefaultCredentials Use default logon credentials for the proxy. .PARAMETER RemoveProxyInfo Delete/remove proxy information from disk. .PARAMETER Force Use this to not prompt to remove when you use -RemoveProxyInfo, or are overwriting existing proxy info / credentials. #> function Set-STOXRProxy { [CmdletBinding()] param( [string] $ProxyUri, [pscredential] $ProxyCredential = $null, [switch] $ProxyUseDefaultCredentials, [switch] $RemoveProxyInfo, [switch] $Force) $PersistentFilePath = "$PSScriptRoot\STOXRProxy.xml" if ($RemoveProxyInfo) { if (-not $Force -and (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf)) { $Answer = Read-Host "Are you sure you want to remove proxy information? Default is 'Y'. Use -Force to avoid this prompt." if ($Answer -match 'n') { return } } Remove-Item -LiteralPath $PersistentFilePath -ErrorAction SilentlyContinue Write-Verbose -Message "Cleared proxy data if it existed ($PersistentFilePath)." return } if (-not $Force -and (Test-Path -LiteralPath $PersistentFilePath)) { $Answer = Read-Host "Proxy credentials already exist. Are you sure you want to overwrite? Default is 'Y'. Use -Force to avoid this prompt." if ($Answer -match 'n') { return } } if ($ProxyCredential) { [pscustomobject] @{ Proxy = $ProxyUri Username = $ProxyCredential.Username Password = $ProxyCredential.GetNetworkCredential().SecurePassword } | Export-Clixml -LiteralPath $PersistentFilePath } elseif ($ProxyUseDefaultCredentials) { [pscustomobject] @{ Proxy = $ProxyUri UseDefaultCredentials = $true } | Export-Clixml -LiteralPath $PersistentFilePath } else { [pscustomobject] @{ Proxy = $ProxyUri } | Export-Clixml -LiteralPath $PersistentFilePath } } function Get-STOXRCurrencyList { [CmdletBinding()] param([switch] $AutoSizeTable) if ($AutoSizeTable) { $Script:CurrencyHash.GetEnumerator() | Sort-Object Name | Select-Object Name, @{ Name = 'Description' Expression = { $_.Value } } | Format-Table -AutoSize } else { $Script:CurrencyHash.GetEnumerator() | Sort-Object Name | Select-Object Name, @{ Name = 'Description' Expression = { $_.Value } } } } function Get-STOXRTrialCurrency { [CmdletBinding()] param() if ($Script:RegisteredProduct) { Write-Warning -Message "Your product is registered, so you have access to convert between all currencies.`nUse Get-STOXRCurrencyList to list them all." #return } $Script:TrialCurrency.ToUpper() } <# .SYNOPSIS Change the one allowed currency to convert to and from in the trial version of the software. There's a limit on changing the currency frequently, so you can only change it once every 12 hours. Buy and register the product with Register-STOXR to avoid this limitation. .PARAMETER Currency Currency code for the currency to use. #> function Set-STOXRTrialCurrency { [CmdletBinding()] param([Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $Currency) if ($Script:RegisteredProduct) { Write-Warning -Message "Your product is registered, so you do not need to change this currency value. Use Get-STOXRCurrencyList to list all available currencies." return } $PersistentFilePath = "$PSScriptRoot\STOXRTrialCurrency.xml" if ($Script:CurrencyHash.ContainsKey($Currency)) { if (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf) { $TrialCurrencyXml = Import-Clixml -LiteralPath $PersistentFilePath if ($TrialCurrencyXml.Date -lt [datetime]::Now.AddHours(-12)) { $Script:TrialCurrency = $Currency [pscustomobject] @{ Currency = $Script:TrialCurrency Date = [datetime]::Now } | Export-Clixml -LiteralPath $PersistentFilePath -ErrorAction Stop "Changed trial currency to '$($Currency.ToUpper())'. It cannot be changed again until 12 hours have passed. If you register the product, this limitation disappears." } else { Write-Warning -Message "Sorry, but 12 hours have not yet passed since the trial currency was last changed." $o = [datetime]::Now - $TrialCurrencyXml.Date Write-Warning -Message ("Time left before you can change: " + (12 - $o.Hours - 1) + " hours and " + (60 - $o.Minutes - 1) + " minutes (" + (12 * 60 - $o.TotalMinutes).ToString('N2') + " minutes).") } } } else { Write-Warning -Message "Unsupported currency abbreviation entered. Use Get-STOXRCurrencyList to list them all." } } function InvokeRestMethodWithProxyCheck { [CmdletBinding()] param( [string] $Uri) <#$ProxyPath = "$PSScriptRoot\STOXRProxy.xml" if (-not (Test-Path -LiteralPath $ProxyPath -PathType Leaf)) { Invoke-RestMethod -Uri $Uri return }#> #$ProxyInfo = Import-Clixml -LiteralPath $ProxyPath $ProxyInfo = Get-STOXRProxy -IsInternalCommand if (-not $ProxyInfo) { Invoke-RestMethod -Uri $Uri return } if ($ProxyInfo | Get-Member -Name UseDefaultCredentials -MemberType NoteProperty -ErrorAction SilentlyContinue) { Invoke-RestMethod -Uri $Uri -UseDefaultCredentials return } if ($ProxyInfo | Get-Member -Name Username -MemberType NoteProperty -ErrorAction SilentlyContinue) { $Credential = [pscredential] $null $Credential.Username = $ProxyInfo.Username $Credential.Password = $ProxyInfo.Password # ConvertTo-SecureString -AsPlainText -Force -String $ProxyInfo.Password Invoke-RestMethod -Uri $Uri -ProxyCredential $Credential return } Invoke-RestMethod -Uri $Uri } function GetExchangeRate { $BaseURL = 'http://openexchangerates.org/api/' if ($AppID = Get-STOXRAppID) { if (-not (Get-Variable -Scope Script CurrenciesJson -ErrorAction SilentlyContinue)) { $Uri = $BaseURL + "currencies.json?app_id=$AppID" $Script:CurrenciesJson = InvokeRestMethodWithProxyCheck -Uri $Uri } if (-not (Get-Variable -Scope Script ExchangeRates -ErrorAction SilentlyContinue) -or ` $Script:ExchangeRates.Date -lt [DateTime]::Now.AddSeconds((-1 * (Get-STOXRCacheInterval)))) { $Uri = $BaseURL + "latest.json?app_id=$AppID" $Json = InvokeRestMethodWithProxyCheck -Uri $Uri $Script:ExchangeRates = [pscustomobject] @{ Date = [DateTime]::Now Data = $Json } } } else { Write-Warning -Message "You need to set the Open Exchange Rates app ID with Set-STOXRAppID before using this software." } if (Get-Variable -Name CurrenciesJson -Scope Script -EA SilentlyContinue) { $Script:CurrencyHash = @{} foreach ($CurrencyAbbreviation in $Script:CurrenciesJson | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) { $Script:CurrencyHash[$CurrencyAbbreviation] = $Script:CurrenciesJson.$CurrencyAbbreviation } } } <# .SYNOPSIS Use the license key you bought and received to register the Svendsen Tech Open Exchange Rates module. This unlocks the full functionality and allows you to convert between any/multiple currencies. .PARAMETER LicenseKey .EXAMPLE Register-STOXR -LicenseKey "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" #> function Register-STOXR { [CmdletBinding()] param( [string] $LicenseKey, [switch] $RefreshUsingExistingLicense) $PersistentFilePath = "$PSScriptRoot\STOXRLicense.txt" #'VABoAGUAIABwAHIAbwBkAHUAYwB0ACAAaQBzACAAcgBlAGcAaQBzAHQAZQByAGUAZAAuACAATABvAHIAZQBtACAAaQBwAHMAdQBtACAAYQBuAGQAIABzAHQAdQBmAGYALgA=' if ($RefreshUsingExistingLicense) { if (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf) { if ((Get-Content -Raw -LiteralPath $PersistentFilePath).Trim() -eq ` 'VABoAGUAIABwAHIAbwBkAHUAYwB0ACAAaQBzACAAcgBlAGcAaQBzAHQAZQByAGUAZAAuACAATABvAHIAZQBtACAAaQBwAHMAdQBtACAAYQBuAGQAIABzAHQAdQBmAGYALgA=') { $Script:RegisteredProduct = $true "Found a valid license file and successfully activated full functionality." return } else { Write-Warning -Message "Found a license file, but the license appears to be invalid." return } } else { Write-Warning -Message "Could not find a license file." return } return } if ([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($LicenseKey)) -eq ` 'VgBBAEIAbwBBAEcAVQBBAEkAQQBCAHcAQQBIAEkAQQBiAHcAQgBrAEEASABVAEEAWQB3AEIAMABBAEMAQQBBAGEAUQBCAHoAQQBDAEEAQQBjAGcAQgBsAEEARwBjAEEAYQBRAEIAegBBAEgAUQBBAFoAUQBCAHkAQQBHAFUAQQBaAEEAQQB1AEEAQwBBAEEAVABBAEIAdgBBAEgASQBBAFoAUQBCAHQAQQBDAEEAQQBhAFEAQgB3AEEASABNAEEAZABRAEIAdABBAEMAQQBBAFkAUQBCAHUAQQBHAFEAQQBJAEEAQgB6AEEASABRAEEAZABRAEIAbQBBAEcAWQBBAEwAZwBBAD0A') { $LicenseKey | Set-Content -Encoding ASCII -LiteralPath $PersistentFilePath -ErrorAction Stop "Successfully registered the Svendsen Tech Open Exchange Rates module." $Script:RegisteredProduct = $true } else { Write-Warning -Message "Invalid license key." } } <# .SYNOPSIS Remove the license for the Svendsen Tech Open Exchange Rates module. After you do this, it will revert to the trial version mode with limited features. .PARAMETER Force Do not prompt to delete if an existing license is found, just do it. .EXAMPLE Unregister-STOXR -Force Successfully unregistered the Svendsen Tech Open Exchange Rates module (STOXR). #> function Unregister-STOXR { [CmdletBinding()] param( [switch] $Force) $PersistentFilePath = "$PSScriptRoot\STOXRLicense.txt" if (-not $Force) { if (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf) { $Answer = Read-Host -Prompt "License found. Are you sure you want to unregister the Svendsen Tech Open Exchange Rates module (STOXR)?" if ($Answer -notmatch "n") { Write-Verbose -Message "Deleting license." Remove-Item -LiteralPath $PersistentFilePath -ErrorAction Stop $Script:RegisteredProduct = $false "Successfully unregistered the Svendsen Tech Open Exchange Rates module (STOXR)." return } else { "User aborted." return } } else { "The Svendsen Tech Open Exchange Rates module was not registered and could therefore not be unregistered." return } } if (Test-Path -LiteralPath $PersistentFilePath -PathType Leaf) { Write-Verbose -Message "Deleting license." Remove-Item -LiteralPath $PersistentFilePath -ErrorAction Stop $Script:RegisteredProduct = $false "Successfully unregistered the Svendsen Tech Open Exchange Rates module (STOXR)." } else { "The Svendsen Tech Open Exchange Rates module was not registered and could therefore not be unregistered." } } <# .SYNOPSIS Display information about the module status. #> function Get-STOXRDebugInfo { [CmdletBinding()] param() if (Test-Path -LiteralPath "$PSScriptRoot\STOXRAppID.xml") { $AppIDFileFound = $true if ((Import-CliXml -LiteralPath "$PSScriptRoot\STOXRAppID.xml") -match '\S') { $AppIDStringFound = $true } else { $AppIDStringFound = $false } } else { $AppIDFileFound = $false $AppIDStringFound = $false } if (Get-Variable -Name CurrencyHash -Scope Script -ErrorAction SilentlyContinue) { $CurrencyHashSet = $true $NumberOfCurrencies = $Script:CurrencyHash.Keys.Count } else { $CurrencyHashSet = $false $NumberOfCurrencies = "NOT_SET" } [PSCustomObject] @{ IsRegistered = $Script:RegisteredProduct PSScriptRoot = $PSScriptRoot LicenseFileFound = Test-Path -LiteralPath "$PSScriptRoot\STOXRLicense.txt" AppIDFileFound = $AppIDFileFound AppIDStringFound = $AppIDStringFound TrialCurrency = Get-STOXRTrialCurrency CurrencyHashSet = $CurrencyHashSet NumberOfCurrencies = $NumberOfCurrencies } } function Initialize { [CmdletBinding()] param() $Script:RegisteredProduct = $false #$Script:Fuzzy = $false Write-Verbose -Message "PSScriptRoot: $PSScriptRoot" $LicensePath = "$PSScriptRoot\STOXRLicense.txt" if (Test-Path -LiteralPath $LicensePath -PathType Leaf) { if ((Get-Content -Raw -LiteralPath $LicensePath).Trim() -eq ` 'VABoAGUAIABwAHIAbwBkAHUAYwB0ACAAaQBzACAAcgBlAGcAaQBzAHQAZQByAGUAZAAuACAATABvAHIAZQBtACAAaQBwAHMAdQBtACAAYQBuAGQAIABzAHQAdQBmAGYALgA=') { Write-Verbose -Message "Found valid license key. Activating full functionality." $Script:RegisteredProduct = $true } else { Write-Warning -Message "Found a license file, but the license appears to be invalid. Trial version mode is active. Try setting a valid license with the command Register-STOXR." } } else { Write-Warning -Message "This is a trial version of the product. You can only change to or from one single currency (both ways). The currency is 'USD' by default. In the trial software it can be changed once every 12 hours. Change to for example British Pound Sterling with Set-STOXRTrialCurrency -Currency GBP. To display the currently set trial currency, use Get-STOXRTrialCurrency. To list all supported currencies, use Get-STOXRCurrencyList." } GetExchangeRate $TrialCurrencyPath = "$PSScriptRoot\STOXRTrialCurrency.xml" if (-not (Test-Path -LiteralPath $TrialCurrencyPath -PathType Leaf)) { [string] $Script:TrialCurrency = 'USD' [pscustomobject] @{ Currency = $Script:TrialCurrency Date = [datetime]::Now.AddHours(-13) # allow for one change } | Export-Clixml -LiteralPath $TrialCurrencyPath } else { [string] $Script:TrialCurrency = (Import-Clixml -LiteralPath $TrialCurrencyPath).Currency } } function GetDataFromCurrencyString { [CmdletBinding()] param( [string] $InputString) $FromCurrency, $ToCurrency = $InputString -isplit '\s+(?:in|to)\s+' | ForEach-Object { $_.Trim() } if (-not $FromCurrency -or -not $ToCurrency) { Write-Warning -Message "The input string needs to be in the format: <FROM_NUMBER> <FROM_CURRENCY> in/to <TO_CURRENCY>. Period as decimal separator. You can use commas as thousand separators for readability/flexibility." return } if ($FromCurrency -match '\s*([\d.,]+)\s*(.+)') { $Amount, $FromCurrency = $Matches[1,2] $Amount = [float] ($Amount -replace ',') } else { Write-Warning -Message 'Invalid "From" number/currency (that part is validated by the regex "\s*([\d.,]+)\s*(.+)"). The input string needs to be in the format: <FROM_NUMBER> <FROM_CURRENCYE> in/to <TO_CURRENCY>. Period as decimal separator. You can use commas as thousand separators for readability/flexibility.' return } return $Amount, $FromCurrency, $ToCurrency } function CurrencyStringOrObject { param( [switch] $AsString, [float] $Amount, [string] $FromCurrency, [string] $ToCurrency, [int] $Precision) if ($AsString) { GetCurrencyString -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } else { GetCurrencyObject -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } } function Convert-STOXRCurrency { <# .SYNOPSIS Convert to and from any of the currently 168 currencies supported by the Open Exchange Rates (OXR) API. See http://openexchangerates.org .DESCRIPTION For now, see the online documentation at https://www.powershelladmin.com/wiki/STOXR_-_Currency_Conversion_Software_-_Open_Exchange_Rates_API This function will be documented more extensively in the built-in help later. #> [CmdletBinding()] param( [decimal] $Amount, [string] $FromCurrency, [string] $ToCurrency, [int] $Precision = 4, [string] $FuzzyInput, [switch] $AsString) # Awesome feature limitation logic. Virtually impossible to circumvent. if (-not $Script:RegisteredProduct -and -not ($FromCurrency -eq $Script:TrialCurrency -or $ToCurrency -eq $Script:TrialCurrency)) { Write-Warning -Message "This product is not registered, so the limitations apply that either the 'From' or 'To' currency needs to be '$($Script:TrialCurrency.ToUpper())' (default USD) - and that -FuzzyInput does not work. The trial currency can be changed once every 12 hours with the command Set-STOXRTrialCurrency. Register the product to unlock full functionality." return } # Not using parameter sets and explicitly positioned parameters because the error message isn't # friendly/comprehensible to those not in the know when you use both -FuzzyInput and {Amount|FromCurrency|ToCurrency}. if ($FuzzyInput -and ($Amount -or $FromCurrency -or $ToCurrency)) { Write-Warning -Message "You cannot use the FuzzyInput parameter together with any of the following parameters: Amount, FromCurrency, ToCurrency." return } if ($FuzzyInput) { $Script:Fuzzy = $false $Amount, $FromCurrency, $ToCurrency = GetDataFromCurrencyString -InputString $FuzzyInput if (-not $Amount) { Write-Warning -Message "Unable to parse FuzzyInput string." return } } GetExchangeRate if (-not (Get-Variable -Name CurrencyHash -Scope Script -ErrorAction SilentlyContinue)) { Write-Warning -Message "Missing currency data. Cannot continue. Do you need to set an app ID with Set-STOXRAppID?" return } if ($Script:CurrencyHash.ContainsKey($FromCurrency)) { if ($Script:CurrencyHash.ContainsKey($ToCurrency)) { CurrencyStringOrObject -AsString:$AsString -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } else { $Hits = GetFuzzyCurrencyMatch -Currency $ToCurrency if ($Hits.Keys.Count -gt 1) { Write-Warning -Message "Multiple hits ($($Hits.Count)) for ""to"" currency:`n$(($Hits.GetEnumerator() | Sort-Object -Property Key | ForEach-Object { $_.Key + ', ' + $_.Value }) -join "`n")" return } if ($Hits.Keys.Count -eq 1) { $ToCurrency = $Hits.GetEnumerator() | Select-Object -ExpandProperty Key # We have both from and to currencies, let's do the math. CurrencyStringOrObject -AsString:$AsString -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } if (-not $Hits.Keys.Count) { Write-Warning -Message "No match for `"to`" currency." return } } } elseif ($Script:CurrencyHash.ContainsKey($ToCurrency)) { $Hits = GetFuzzyCurrencyMatch -Currency $FromCurrency #'Hits:'; $hits if ($Hits.Keys.Count -gt 1) { Write-Warning -Message "Multiple hits ($($Hits.Count)) for ""from"" currency:`n$(($Hits.GetEnumerator() | Sort-Object Key | ForEach-Object { $_.Key + ', ' + $_.Value }) -join "`n")" return } if ($Hits.Keys.Count -eq 1) { $FromCurrency = $Hits.GetEnumerator() | Select-Object -ExpandProperty Key #'Determined "from" currency''s code to be: ' + $FromCurr # We have both from and to currencies, let's do the math. CurrencyStringOrObject -AsString:$AsString -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } if (-not $Hits.Keys.Count) { Write-Warning -Message "No match for `"from`" currency." return } } # Fuzzy match both... else { $Hits = GetFuzzyCurrencyMatch -Currency $FromCurrency if ($Hits.Keys.Count -gt 1) { Write-Warning -Message "Multiple hits ($($Hits.Count)) for ""from"" currency:`n$(($Hits.GetEnumerator() | Sort-Object Key | ForEach-Object { $_.Key + ', ' + $_.Value }) -join "`n")" return } if ($Hits.Keys.Count -eq 1) { $FromCurrency = $Hits.Keys #'Determined "from" currency''s code to be: ' + $FromCurr $Hits = GetFuzzyCurrencyMatch -Currency $ToCurrency if ($Hits.Keys.Count -gt 1) { Write-Warning -Message "Multiple hits ($($Hits.Count)) for ""to"" currency:`n$(($Hits.GetEnumerator() | Sort-Object Key | ForEach-Object { $_.Key + ', ' + $_.Value }) -join "`n")" return } if ($Hits.Keys.Count -eq 1) { $ToCurrency = $Hits.Keys # We have both from and to currencies, let's do the math. CurrencyStringOrObject -AsString:$AsString -Amount $Amount -From $FromCurrency -To $ToCurrency -Precision $Precision } if (-not $Hits.Keys.Count) { Write-Warning -Message 'No match for "to" currency.' return } } if (-not $Hits.Keys.Count) { Write-Warning -Message "No match for `"from`" currency." return } } } Initialize Export-ModuleMember -Function '*-*' |