EnergyCap.PowerShell.ps1

#Requires -version 7
$ErrorActionPreference = "Stop"
function Get-ConversionFactor {
    param (
        [Parameter(Mandatory)]
        [string]$ConversionBaseUrl,
        [Parameter(Mandatory)]
        [string]$ConversionQuery,
        [Parameter(Mandatory)]
        [string]$BaseCurrency,
        [Parameter(Mandatory)]
        [string]$TargetCurrency,
        [Parameter(Mandatory)]
        [DateTime]$Date 
    )
    $formattedDate = $Date.ToString("yyy-MM-dd")
    $ConversionQuery = $ConversionQuery -replace "DATE", $formattedDate
    $ConversionQuery = $ConversionQuery -replace "CUR_BASE", $BaseCurrency
    $url = $ConversionBaseUrl,$ConversionQuery -join ""
    $standardConversionFactor = Get-StandardCurrencyCode -CurrencyCode $TargetCurrency

    try{
        $conversionFactors= Invoke-WebRequest -Uri $url -Method Get -ContentType 'application/json' | ConvertFrom-Json
        if($null -ne $conversionFactors.rates.$standardConversionFactor){
            return $conversionFactors.rates.$standardConversionFactor
        }
        else {
            $message = "Error in GetFactor: Target Currency ($standardConversionFactor) Unrecognized"
            Throw $message
        }
    }
    catch{
        throw "Error in GetFactor: Unsuccessful Conversion Factor Query (" + $url + ")" + [System.Environment]::NewLine + $_
    }

}

function Get-StandardCurrencyCode {
    param (
        [string]$CurrencyCode
    )
    
    $result = $CurrencyCode.ToUpper()

    switch ($CurrencyCode) {
        "USDOLLARS" { $result ="USD" }
        "UKPOUND" { $result = "GBP"}
        "CANADIANDOLLARS" {$result = "CAD"}
        "EURO" {$result = "EUR"}
        "LRK" {$result = "LKR"}
        "CFA" {$result = "XAF"} # CFA stands fro XOF and XAF, which have the same exchange rate
        Default {}
    }
    return $result
}
# Variables that we may want to modify
function Convert-EnergyCapBill{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName="ApiKey")]
        [string]$EcapBaseUrl,
        [Parameter(Mandatory, ParameterSetName="ApiKey")]
        [string]$EcapApiKey,
        [Parameter(Mandatory, ParameterSetName="EcapSdk")]
        [EnergyCap.Sdk.EnergyCapApi]$EcapSdk,
        [Parameter(Mandatory)]
        [int]$BillId,
        [double]$ConversionThreshold = .5,
        [int]$CurrencyConversionFactorObservationTypeId = 1027,
        [int]$InfoNativeCostObservationTypeId = 1029,
        [switch]$ReturnObj
    )
    
    if(!$env:ConversionFactorEnvironment){
        throw "Argument Exception: Environmental variable ConversionFactorEnvironment is not set"
    }
    if(!$env:ConversionFactorHistoricalQuery){
        throw "Argument Exception: Environmental variable ConversionFactorHistoricalQuery is not set."
    }

    Set-Variable -Name CURRENCYCONVERSIONFACTOR_OBSERVATIONTYPEID -Option ReadOnly -Value $CurrencyConversionFactorObservationTypeId 
    Set-Variable -Name INFONATIVECOST_OBSERVATIONTYPEID -Option ReadOnly -Value $InfoNativeCostObservationTypeId
    Set-Variable -Name USDOLLARS_UNITID -Option ReadOnly -Value 58
    Set-Variable -Name TOTALPAYAMOUNT_OBSERVATIONTYPEID -Option ReadOnly -Value 1025

    $totalPayAmount = $null
    if(!$EcapSdk){
        $EcapSdk = Get-EnergyCapSDK -EcapApiKey $EcapApiKey -BaseUrl $EcapBaseUrl
    }
    $conversionStatus = "$BillId : Bill not converted."

    try{
        # You cannot have a bill without bodylines, so we assume that there is always at least one
        $bodyLineObj = $ecapSdk.GetBillBodylinesWithHttpMessagesAsync($BillId).GetAwaiter().GetResult().Body
        
        # Look for the currency conversion body line -- if it exists, it's safe to bail
        $currencyConversionBodyLine = $bodyLineObj.ObservationType | Where-Object { $_.ObservationTypeId -eq $CURRENCYCONVERSIONFACTOR_OBSERVATIONTYPEID } 
        if($null -ne $currencyConversionBodyLine){
            $conversionStatus = "$BillId : Bill already converted."
            return $conversionStatus
        }

        # Are there any lines that are not in USD? $bodyLineObj cannot be null, because a bill cannot exist without any body lines
        $foreignCurrencyCodes = $bodyLineObj.CostUnit | Select-Object 'UnitId','UnitCode' -Unique | Where-Object {$_.UnitCode -ne "USDOLLARS"}
        # There are no non-USD lines
        if($null -eq $foreignCurrencyCodes){
            $conversionStatus += " Bill in USD"
            return $conversionStatus
        }
        # Gets the Bill part of a bill and converts the Bill Response to a Bill Edit.
        $billEditObj = Get-EnergyCapBillEditObj -EcapSdk $EcapSdk -BillId $BillId
 
        # We are passing in a semi-populated BillEditObj and an array of BodyLine Response Objects -- no APIs being made in this call
        Get-EnergyCapBodylines -BillEditObj $billEditObj -ecapSdk $EcapSdk -bodyLineObj $bodyLineObj

        $conversionDate = Get-ConversionDate -Bill $billEditObj
        
        $currencyLookupHash = @{}
        # Unlikely, but there may be mixed currency bills
        ForEach($foreignCurrency in $foreignCurrencyCodes){
            # This could throw exceptions
            $conversionFactor = Get-ConversionFactor -Date $conversionDate -ConversionBaseUrl $env:ConversionFactorEnvironment -ConversionQuery $env:ConversionFactorHistoricalQuery -BaseCurrency "USD" -TargetCurrency $foreignCurrency.UnitCode
            Add-Member -InputObject $foreignCurrency -Type NoteProperty -Name "ConversionFactor" -Value $conversionFactor
            $currencyLookupHash.Add($foreignCurrency.UnitId, $foreignCurrency)
        }

        # Do the conversion
        foreach($meter in $billEditObj.Meters){
            $foreignCurrencyBodyLines = New-Object System.Collections.Generic.List[EnergyCap.Sdk.Models.BillMeterBodyLineEdit]
            foreach ($line in $meter.BodyLines) {
                if($line.CostUnitId -and $line.CostUnitId -ne $USDOLLARS_UNITID){
                    $bodylineCostCopy = [EnergyCap.Sdk.Models.BillMeterBodyLineEdit]::new()
                    Copy-EnergyCapPropertiesFromObj1ToObj2 -Obj1 $line -Obj2 $bodylineCostCopy
                    $bodylineCostCopy.ObservationTypeId = $INFONATIVECOST_OBSERVATIONTYPEID
                    $bodylineCostCopy.BodyLineId = $null
                    $bodylineCostCopy.Value = $null
                    $bodylineCostCopy.ValueUnitId = $null

                    $line.CostUnitId = $USDOLLARS_UNITID
                    $conversionFactor = $currencyLookupHash[$bodylineCostCopy.CostUnitId].ConversionFactor
                    $line.Cost = [Math]::Round($bodylineCostCopy.Cost / [Math]::Round($conversionFactor,6),6)
                    $foreignCurrencyBodyLines.Add($bodylineCostCopy)
                }
            }
            # Handle foreign currency lines with a null cost
            $foreignCurrencyBodyLines | Where-Object {$null -eq $_.Cost} | ForEach-Object {$_.Cost = 0}
           $foreignCurrencyBodyLines | ForEach-Object { $meter.BodyLines.Add($_)}
        }
        # Convert Account Body Lines
        $foreignCurrencyBodyLines = New-Object 'System.Collections.Generic.List[EnergyCap.Sdk.Models.BillAccountBodyLineEdit]'
        foreach ($line in $billEditObj.AccountBodyLines) {
            if($line.CostUnitId -ne $USDOLLARS_UNITID){
                $bodylineCostCopy = [EnergyCap.Sdk.Models.BillAccountBodyLineEdit]::new()
                Copy-EnergyCapPropertiesFromObj1ToObj2 -Obj1 $line -Obj2 $bodylineCostCopy
                $bodylineCostCopy.ObservationTypeId = $INFONATIVECOST_OBSERVATIONTYPEID
                $bodylineCostCopy.BodyLineId = $null

                $line.CostUnitId = $USDOLLARS_UNITID
                $conversionFactor = $currencyLookupHash[$bodylineCostCopy.CostUnitId].ConversionFactor
                $line.Cost = [Math]::Round($bodylineCostCopy.Cost / [Math]::Round($conversionFactor,6),6)

                if($line.ObservationTypeId -eq $TOTALPAYAMOUNT_OBSERVATIONTYPEID){
                    $totalPayAmount = $line.Cost
                }
                $foreignCurrencyBodyLines.Add($bodylineCostCopy)
            }
        }
        $foreignCurrencyBodyLines | Foreach-Object { $billEditObj.AccountBodyLines.Add($_)}

        # Add the conversion factor body line(s)
        foreach($currency in $foreignCurrencyCodes){
            New-CurrencyConversionBodyLine -ConversionFactor $($currency.ConversionFactor.ToString()) -EcapSDK $EcapSdk -BillEditObj $billEditObj -FromCurrency $currency.UnitCode -CurrencyConversionFactorObservationTypeId $CURRENCYCONVERSIONFACTOR_OBSERVATIONTYPEID -Date $conversionDate
        }

        $result = $EcapSdk.EditBillWithHttpMessagesAsync($BillId,$billEditObj).GetAwaiter().GetResult().Body
        if($totalPayAmount){
            $calculatedBill = $EcapSdk.GetBillWithHttpMessagesAsync($BillId).GetAwaiter().GetResult().Body
            $calculatedTotalCost = $calculatedBill.TotalCost
            $variance = [Math]::Round([Math]::Abs($calculatedTotalCost - $totalPayAmount),2)

            # If the EnergyCAP calculated value is different than the total pay amount AND within a certain variance ConversionThreshold, we quietly update it -- per client request
            if($variance -ne 0 -and ($variance -lt $ConversionThreshold -and $variance -ge .01)){
                $billEditObj.AccountBodyLines | Where-Object {$_.ObservationTypeId -eq $TOTALPAYAMOUNT_OBSERVATIONTYPEID} | ForEach-Object {$_.Cost = $calculatedTotalCost}
                $EcapSdk.EditBillWithHttpMessagesAsync($BillId,$billEditObj).GetAwaiter().GetResult().Body|Out-Null
            }
        }
        # This allows us to capture any updates to the total cost
        if($ReturnObj){
            $conversionStatus = $billEditObj
        }
        else{
            $conversionStatus = ConvertTo-Json $billEditObj -Depth 10
        }
    }
    catch{
        $errorMessage = Get-EnergyCapException($_)
        $result = Handle-Exception -ConversionStatus $conversionStatus -ErrorMessage $errorMessage
        if($result.ThrowException) {throw $result.Message}
        else { return $result.Message}
    }
    
    return $conversionStatus
}
<#
.SYNOPSIS
Returns date for which we're pulling the currency conversion factor. In order of preference: Statment Date, EndDate, BeginDate, Today if all other dates are in the future
Due date is not considered.
 
.DESCRIPTION
Long description
 
.PARAMETER Bill
Bill Edit object
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Get-ConversionDate{
    param([EnergyCap.Sdk.Models.BillEdit]$Bill)

    $today = Get-Date

    if($Bill.StatementDate -and $Bill.StatementDate -lt $today){
        return $Bill.StatementDate
    }

    if($Bill.EndDate -and $Bill.EndDate -lt $today){
        return $Bill.EndDate
    }

    if($Bill.BeginDate -and $Bill.BeginDate -lt $today){
        return $Bill.BeginDate
    }

    return $today
}

function New-CurrencyConversionBodyLine{
    param(
        [EnergyCap.Sdk.EnergyCapApi]$EcapSDK,
        [EnergyCap.Sdk.Models.BillEdit]$BillEditObj,
        [string] $FromCurrency,
        [string]$ToCurrency = "USD",
        [string]$ConversionFactor,
        [int]$CurrencyConversionFactorObservationTypeId,
        [DateTime]$Date,
        $ValueUnitId = 12
    )

    $cleanedFromCurrency = Get-StandardCurrencyCode -CurrencyCode $FromCurrency
    $cleanedToCurrency = Get-StandardCurrencyCode -CurrencyCode $ToCurrency

    $formattedDate = $Date.ToString("MM/dd/yyy")
    $accountId = $BillEditObj.AccountId
    $filter = "accountId equals '" + $accountId + "'";
    if($BillEditObj.Meters.Count -eq 0){
        $BillEditObj.Meters = New-Object 'System.Collections.Generic.List[EnergyCap.Sdk.Models.BillMeterEdit]'
        $meters = $EcapSDK.GetMetersWithHttpMessagesAsync($filter).GetAwaiter().GetResult().Body
        $meterInfo = [EnergyCap.Sdk.Models.BillMeterEdit]::new()
        $meterInfo.MeterId = $meters[0].MeterId
        $meterInfo.BodyLines = New-Object System.Collections.Generic.List[EnergyCap.Sdk.Models.BillMeterBodyLineEdit]
        $BillEditObj.Meters.Add($meterInfo)
    }

    $bodyLine = [EnergyCap.Sdk.Models.BillMeterBodyLineEdit]::new()
    $bodyLine.ValueUnitId = $ValueUnitId
    $bodyLine.ObservationTypeId = $CurrencyConversionFactorObservationTypeId
    $bodyLine.Value = $ConversionFactor
    $bodyLine.Caption = "From $($cleanedFromCurrency) to $($cleanedToCurrency) ($($formattedDate))"
    $BillEditObj.Meters[0].BodyLines.Insert(0,$bodyLine)
}

function Handle-Exception {
    param (
        [string]$ConversionStatus,
        [string]$ErrorMessage
    )
    $result = @{
        ThrowException = $true;
        Message = ""
    }
    # If bodylines can't be grabbed, it's very likely the bill no longer exists. This is not an error
    if($errorMessage -match "Status Code: NotFound"){
        $ConversionStatus = "WARNING: " + $ConversionStatus + [System.Environment]::NewLine + "API Result: " + $ErrorMessage
        $result.ThrowException = $False
    }
    else{
        $ConversionStatus += [System.Environment]::NewLine + " Error during conversion process: " + $ErrorMessage 
    }
    $result.Message = $ConversionStatus
    return $result
}
<#
.SYNOPSIS
Creates a copy of an EnergyCapBill in BillEdit form.
 
.DESCRIPTION
Long description
 
.PARAMETER ApiKey
EnergyCap ApiKey
 
.PARAMETER EcapBaseUrl
Base url. e.g. "https://app.energycap.com"
 
.PARAMETER BillId
Single id, not an array
 
.PARAMETER MaxRetry
Defaulted to 2
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Copy-DefaultEnergyCapBill{
    param(
        [Parameter(Mandatory,ParameterSetName = "ApiKey")]
        [string]$EcapBaseUrl,
        [Parameter(Mandatory, ParameterSetName = "ApiKey")]
        [string]$EcapApiKey,
        [Parameter(Mandatory, ParameterSetName = "EcapSDK")]
        [EnergyCap.Sdk.EnergyCapApi]$EcapSdk ,
        [Parameter(Mandatory)]
        [int]$BillId ,
        [switch]$ReturnObj,
        [int]$MaxRetry = 2
    )
    $ErrorActionPreference = "Stop"

    if(!$EcapSdk){
        $EcapSdk = Get-EnergyCapSDK -BaseUrl $EcapBaseUrl -EcapApiKey $EcapApiKey
    }


    try{
        $billEditObj = Get-EnergyCapBillEditObj -EcapSdk $EcapSdk -BillId $BillId
        Get-EnergyCapBodylines -BillId $BillId -ecapSdk $EcapSdk -BillEditObj $billEditObj -ErrorAction Stop
    }
    catch{
        # See previous note about having to rethrow exceptions
        throw Get-EnergyCapException($_)
    }
    # If we don't want to return it as an object, we return JSON
    if($ReturnObj){
        return $billEditObj
    }
    else{
        return ConvertTo-Json $billEditObj -Depth 10
    }
}
New-Alias "Copy-EnergyCapBill" -Value "Copy-DefaultEnergyCapBill"
<#
.SYNOPSIS
Copies properties from object 1 into object 2 where the property name exists in both objects.
 
.DESCRIPTION
Long description
 
.PARAMETER Obj1
Object that we copy property values from
 
.PARAMETER Obj2
Object that we copy property values to
 
.EXAMPLE
Copy-EnergyCapPropertiesFromObj1ToObj2 -Obj1 $billResponseObj -Obj2 $billEditObj
 
.NOTES
General notes
#>

function Copy-EnergyCapPropertiesFromObj1ToObj2{
    param(
        [Object]$Obj1,
        [Object]$Obj2
    )
    $ErrorActionPreference='Stop'
   
    try{
        $obj1Properties = $(Get-Member -InputObject $Obj1 -MemberType Properties).Name
        $obj2Properties = $(Get-Member -InputObject $Obj2 -MemberType Properties).Name
    }
    catch{
        throw "Copy-EnergyCapPropertiesFromObj1ToObj2: Both Object1 and Object2 must not be null."
    }

    # Copy over stuff from the bill
    foreach ($property in $obj1Properties) {
        if($property -in $obj2Properties){
            $Obj2.$property = $Obj1.$property
        }
    }
}
<#
.SYNOPSIS
    The purpose of this tiny function is to avoid using ".GetAwaiter().GetResult()" to get return values from EnergyCAP SDK methods.
 
.EXAMPLE
    PS />$sdk.GetSystemSettingsWithHttpMessagesAsync()|Get-AsyncResult
#>

function Get-AsyncResult{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline)] 
        $asyncSdkTask
    )
    return $asyncSdkTask.GetAwaiter().GetResult()
}
<#
.SYNOPSIS
Takes in a Bill Edit object and populates to the bodyline fields. Optionally takes in a BodyLineResponse object and populates the body line fields
without re-invoking the get BodyLine API
 
.DESCRIPTION
Long description
 
.PARAMETER BillId
Parameter description
 
.PARAMETER EcapBaseUrl
Parameter description
 
.PARAMETER ApiKey
Parameter description
 
.PARAMETER EcapSdk
Parameter description
 
.PARAMETER BillEditObj
Parameter description
 
.PARAMETER BodyLineObj
Parameter description
Optional.
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Get-EnergyCapBodylines{
    param(
        [int]$BillId,
        [Parameter(Mandatory, ParameterSetName="ApiKey")]
        [string]$EcapBaseUrl,
        [Parameter(Mandatory, ParameterSetName="ApiKey")]
        [string]$EcapApiKey ,
        [Parameter(Mandatory, ParameterSetName="EcapSdk")]
        [EnergyCap.Sdk.EnergyCapApi]$EcapSdk,
        [EnergyCap.Sdk.Models.BillEdit]$BillEditObj,
        [EnergyCap.Sdk.Models.BodylineResponse[]]$BodyLineObj = $null
    )
    if(!$EcapSdk){
        $EcapSdk = Get-EnergyCapSDK -EcapApiKey $EcapApiKey -BaseUrl $EcapBaseUrl
    }

    # if there is an issue, this will throw an exception and bubble up
    if(!$bodyLineObj){
            $bodyLineObj = $ecapSdk.GetBillBodylinesWithHttpMessagesAsync($BillId).GetAwaiter().GetResult().Body
    }
    $meterDict = @{}
    foreach ($line in $bodyLineObj) {
        # account body line
        if($null -eq $line.meter){
            $accountBodyline = [EnergyCap.Sdk.Models.BillAccountBodyLineEdit]::new()
            $accountBodyline.BodyLineId = $line.BodyLineId
            $accountBodyline.ObservationTypeId = $line.observationType.ObservationTypeId
            $accountBodyline.CostUnitId = $line.CostUnit.UnitId
            $accountBodyline.Cost = $line.cost
            $accountBodyline.Caption = $line.caption
            $BillEditObj.AccountBodyLines.Add($accountBodyline)
        }
        else{
            $meterId = $line.meter.meterId 
            if($meterId -notin $meterDict.Keys){
                $billMeterEdit = [EnergyCap.Sdk.Models.BillMeterEdit]::new()
                $billMeterEdit.MeterId = $meterId
                $billMeterEdit.BodyLines = [System.Collections.Generic.List[EnergyCap.Sdk.Models.BillMeterBodyLineEdit]]::new()
                $meterDict.Add($meterId, $billMeterEdit)
            }
            $meterBodyLine = [EnergyCap.Sdk.Models.BillMeterBodyLineEdit]::new()
            $meterBodyLine.BodyLineId = $line.BodyLineId
            $meterBodyLine.ObservationTypeId = $line.observationType.observationTypeId
            $meterBodyLine.ValueUnitId = $line.unit ? $line.unit.unitId : $null
            $meterBodyLine.Value = $line.Value
            $meterBodyLine.CostUnitId = $null -eq $line.costUnit ? $null :  $line.costUnit.unitId 
            $meterBodyLine.Cost = $line.Cost 
            
            # If the CostUnitID is set, then cost cannot be null
            if($null -ne $meterBodyLine.CostUnitId -and $null -eq $meterBodyLine.Cost){
                $meterBodyLine.Cost = 0
            }

            $meterBodyLine.Caption = $line.Caption
            $meterDict[$meterId].BodyLines.Add($meterBodyLine)
        }
    }
    if($meterDict.Values.Count -gt 0){
        $BillEditObj.Meters = [System.Collections.Generic.List[EnergyCap.Sdk.Models.BillMeterEdit]]$($meterDict.Values)
    }
}
function Get-EnergyCapEmailConnectionInfo{
    if($CustomScriptContext.EmailConnectionInfo) {
        return $CustomScriptContext.EmailConnectionInfo;
    }
    elseif( $env:EmailConnectionString){
        $emailConnectionInfo = @{}
        $array = $Env:EmailConnectionString -split ";"
        $array | ForEach-Object{
            $kvp = $_ -split "="
            $emailConnectionInfo.Add($kvp[0], $kvp[1])
        }
        return $emailConnectionInfo
    }
    else{
        throw "Argument exception: EmailConnectionString has not been set"
    }
}
New-Alias -Name 'Get-EmailConnectionInfo' -Value 'Get-EnergyCapEmailConnectionInfo'
function Get-EnergyCapException{
    param($EnergyCapException)
    if($null -ne $EnergyCapException.Exception.InnerException){
        if($null -ne $EnergyCapException.Exception.InnerException.Response){
            $errorCode = "Status Code: " +  $EnergyCapException.Exception.InnerException.Response.StatusCode + " ("+$EnergyCapException.Exception.InnerException.Response.StatusCode.value__ + ")"
            $responseObj = $EnergyCapException.Exception.InnerException.Response.Content | ConvertFrom-Json -Depth 10
            $result = $errorCode + [System.Environment]::NewLine + $($responseObj.Status.Message)
            return  $result
        }
        else{
            return $EnergyCapException.Exception.InnerException.Message
        }
    }
    else{
        return $EnergyCapException.Exception.Message
    }
}
function Get-EnergyCapSDK{
    param(
        [string]$EcapApiKey,
        [string]$BaseUrl)
        try{

            $settings = New-Object EnergyCap.Sdk.Extensions.EnergyCapClientSettingsApiKeyAuth
            $settings.ApiKey = $EcapApiKey
            $settings.EnvironmentUrl = $BaseUrl
            $settings.UserAgentSuffix = "EnergyCapPowerShell"
            $settings.TimeoutSeconds = 7200 <# 2 hours #>
            $ecapSdk = [EnergyCap.Sdk.Extensions.EnergyCapApiClientFactory]::CreateClientAsync($settings).GetAwaiter().GetResult()
            $ignoreMe = $ecapSdk.MeMethodWithHttpMessagesAsync().GetAwaiter().GetResult().Body 
            $ecapSdk.DeserializationSettings.NullValueHandling = [Newtonsoft.Json.NullValueHandling]::Include
            $ecapSdk.SerializationSettings.NullValueHandling = [Newtonsoft.Json.NullValueHandling]::Include;
            return $ecapSdk
        }
        catch{
            throw Get-EnergyCapException($_)
        }
}
<#
.SYNOPSIS
Takes in a bill id, gets a Bill Response object and returns a populated Bill Edit object.
 
.DESCRIPTION
Long description
 
.PARAMETER EcapSdk
Required.
 
.PARAMETER BillId
Required
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Get-EnergyCapBillEditObj {
    param (
        [Parameter(Mandatory)]
        [EnergyCap.Sdk.EnergyCapApi]$EcapSdk,
        [Parameter(Mandatory)]
        [int]$BillId
    )
    $ErrorActionPreference = 'Stop'
    
    # Will let the exception bubble up
    $billResponseObj = $EcapSdk.GetBillWithHttpMessagesAsync($BillId).GetAwaiter().GetResult().Body

    $billEditObj = [EnergyCap.Sdk.Models.BillEdit]::new()
    $billEditObj.AccountBodyLines = [System.Collections.Generic.List[EnergyCap.Sdk.Models.BillAccountBodyLineEdit]]::new()

    Copy-EnergyCapPropertiesFromObj1ToObj2 -Obj1 $billResponseObj -Obj2 $billEditObj | Out-Null
    $billEditObj.Note = $billResponseObj.BillNote
    $billEditObj.AccountId = $billResponseObj.Account.AccountId
    return $billEditObj
}
function Initialize-EnergyCapPowerShell{
    param([Parameter(Mandatory)]$EnergyCapSdkDllPath)

    try{
        Add-Type -Path "$EnergyCapSdkDllPath" -ErrorAction Stop
    }
    catch{
        Write-Host "Unable to load EnergyCap.Sdk.dll. Please check path and try again."
    }
}
class NotificationButton{
    [string]$Url;
    [String]$Label;
    [bool]$OpenInNewWindow
}
<#
.SYNOPSIS
Sends an in-app notification to a specified EnergyCAP user group or set of individual users
 
.DESCRIPTION
Sends an in-app notification to a specified EnergyCAP user group or set of individual users
 
.PARAMETER BaseUrl
Mandatory
e.g. "https://develop.energycap.com/"
 
.PARAMETER UserGroupIds
Mandatory if UserIds is not set.
An array of EnergyCAP user group ids.
 
.PARAMETER UserIds
Mandatory if UserGroupIds is not set.
An array of EnergyCap user ids
 
.PARAMETER EcapApiKey
Mandatory
EnergyCAP Api Key
 
.Parameter Notification Type
SystemMessages = 1,
BillImportAutomation = 2,
BillExportAutomation = 3,
IntervalDataAutomation = 4,
UserGroupMessages = 5,
UpdatesAndNewFeatures = 6,
HelpArticlesTipsAndSuggestions = 7,
CustomerServiceMessages = 8
 
.PARAMETER Subject
Mandatory
Subject line of notification message
 
.PARAMETER Message
Mandatory
Body of notification message
 
.PARAMETER ReplyTo
Optional
Default is set to no-reply-eventrouter@energycap.com
 
.PARAMETER PrimaryAction
Optional - of class NotificationButton
This module contains the custom class NotificationButton, which is defined below. All properties must be set in order to be considered valid.
 
class NotificationButton{
    [string]$Url;
    [String]$Label;
    [bool]$OpenInNewWindow
}
 
.PARAMETER SecondaryAction
Optional - of class NotificationButton
 
See PrimaryAction parameter for additional details.
 
.PARAMETER LogFilePath
Optional
Full Path to log file
 
.EXAMPLE
Send-EnergyCapNotification -BaseUrl "https://develop.energycap.com/" -UserIds @(1202) -EcapApiKey "123ABC" -Subject "Pony" -Message "Pony" -LogFilePath "C:\temp\test.txt" -Verbose
 
 
.NOTES
General notes
#>

function Send-EnergyCapNotification{
    param(
        [string] $EcapApikey,
        [string] $BaseUrl,
        [EnergyCap.Sdk.EnergyCapApi]$EcapSdk,
        [Parameter(Mandatory, ParameterSetName='Group1')]
        [array]$UserGroupIds,
        
        [Parameter(ParameterSetName='Group1')]
        [Parameter(Mandatory,ParameterSetName='Group2')]
        [array]$UserIds,

        [Parameter(Mandatory,ParameterSetName='Group3')]
        [boolean]$SendToAllUsers,
        [int]$NotificationType = 5, #
        [Parameter(Mandatory)]
        [string]$Subject,

        [Parameter(Mandatory)]
        [string]$Message,
        [string]$ReplyTo = "no-reply@energycap.com",
        [NotificationButton]$PrimaryAction = $null,
        [NotificationButton]$SecondaryAction = $null,
        [string]$LogFilePath = "",
        [switch]$isVerbose
    )
    $ApiStem = "api/v202108/notification/type"
    if(!$BaseUrl){
        if($EcapSdk){
            $BaseUrl = $EcapSdk.BaseUri
        }
        else{
            throw [System.ArgumentException] "Either baseUrl or EcapSdk needs to be defined."
        }
    }
    
    $index = $BaseUrl.LastIndexOfAny("/")
    if($index -eq $BaseUrl.Length - 1){
        # lops off final /
        $BaseUrl = $BaseUrl.Substring(0,$index)
    }

    $notificationUrl = "$BaseUrl/$apiStem/$notificationType"
    
    # If a button is not null, it must have Label and Url properties defined
     if( !($(Validate-NotificationButton -button $PrimaryAction) -and $(Validate-NotificationButton -button $SecondaryAction))){
         Write-EnergyCapLog -FilePath $LogFilePath -Data "Invalid PrimaryAction or SecondaryAction Notification buttons. Label and Url properties must be defined."
         throw [System.ArgumentException]"PrimaryAction or SecondaryAction, if defined, must have Label and Url properties defined"
     }
    Write-EnergyCapLog -FilePath $LogFilePath -Data "Primary Action and Secondary Actions buttons are valid." -isVerbose $isVerbose 
    
   
    if(!$EcapApiKey){
        if($EcapSdk){
            $EcapApiKey= $EcapSdk.HttpClient.DefaultRequestHeaders.GetValues("Eci-ApiKey")[0]
        }
        else{
            throw [System.ArgumentException]"Apikey or EcapSdk must be defined"
        }
    }

    $header = @{"Eci-ApiKey" = $EcapApiKey}
  
    
    $notificationSendRequestDTO = @{
        UserIds = $UserIds;
        UserGroupIds = $userGroupIds;
        SendToAllUsers = $SendToAllUsers ? $SendToAllUsers : $false;
        Subject = $subject;
        Message = $message;
        ReplyTo = $ReplyTo;
        PrimaryAction =  $PrimaryAction ? $PrimaryAction : $null
        SecondaryAction = $SecondaryAction ? $SecondaryAction : $null
    } | ConvertTo-Json

    try{
        $result = Invoke-WebRequest -Method Post -Uri $notificationUrl -ContentType "application/json" -Body $notificationSendRequestDTO -Headers $header | ConvertFrom-Json
        Write-EnergyCapLog -FilePath $logfilePath -Data "Notification successfully sent to EnerygCAP." -isVerbose $isVerbose 
        Write-EnergyCapLog -FilePath $logfilePath -Data $result -isVerbose $isVerbose
    }
    catch{
        $errorDto = @{
            StatusCode = $_.Exception.Message;
            EnergyCAPMessage = $_.ErrorDetails.Message 
        } | ConvertTo-Json

        Write-EnergyCapLog -FilePath $logfilePath -Data $errorDto -isVerbose $isVerbose 
        throw
    }    
}


<#
.SYNOPSIS
Returns a boolean indicating whether the NotificationButton class is fully populated
 
.DESCRIPTION
Returns a boolean indicating whether the NotificationButton class is fully populated.
 
Null values for the parameter button will also return true.
 
This function is used by the Send-EnergyCapNotification function.
 
.PARAMETER button
Of the NotificationButton class, may be null
 
.EXAMPLE
Validate-NotificationButton -button $PrimaryAction
 
#>

function Validate-NotificationButton(){
    param([NotificationButton]$button)
    if($null -eq $button){
        return $true
    }
    $properties = Get-Member -InputObject $button -MemberType Properties
    $populatedProperties = $properties.Name | ForEach-Object { $button.$_ } | Where-Object { $_ -ne $null}
    return $populatedProperties.count -eq $properties.count
}
<#
.SYNOPSIS
Logs to data file, if path defined, and to stdout, if isVerbose is set to true
 
.DESCRIPTION
Logs to data file, if path defined, and to stdout, if isVerbose is set to true
 
.PARAMETER FilePath
Fully-qualified path to log file
 
.PARAMETER Data
Data to be logged
 
.PARAMETER isVerbose
Bool - controls whether or not contents of data variable is written to stdout
 
.EXAMPLE
Write-EnergyCapLog -FilePath "C:\temp\logfile.log" -Data "Here's some data" -isVerbose $true
 
.NOTES
General notes
#>

function Write-EnergyCapLog{
    param(
        [string]$FilePath = "",
        $Data = "",
        [bool]$isVerbose = $false
        )
    # If the file path is defined, log it to the log file
    if($FilePath -ne ""){
        $dir = Split-Path -Path $FilePath -Parent
        if(!(Test-Path $dir -PathType Container)){
            New-Item -ItemType directory -Path $dirPath|Out-Null
        }
        $time=Get-Date -format "dd-MMM-yyyy HH:mm:ss"
        $nowobj = New-Object System.Text.StringBuilder
        [void]$nowobj.Append($time + " " + $data)
        Add-Content $FilePath $nowobj.ToString() 
    }
    # If we're verbose, log to stdout
    if($isVerbose){
        write-information $Data -InformationAction continue
    }
}