Varibill.SourceCollector.Core.psm1
<#
.Synopsis Collect and send Varibill billing data via PowerShell (JSON based). .Description Builds JSON payloads for Unrated or Rated source data records and posts them to the Varibill API. Supports saving of generated JSON payloads and API responses. Splits records into multiple files if needed. Unsuccessful posts leave files as .rdy for retry. .Example # Create an UnratedSourceData instance $unrated = New-UnratedSourceData -SourceCollectorAPI "https://qasc.varibill.com/" ` -TenantKey ([guid]::NewGuid()) -SourceCollectorKey ([guid]::NewGuid()) -SourceCollectorSecret "secret" -Verbose # Add multiple records 1..2500 | ForEach-Object { $unrated.AddUnratedSourceData("Client$_", "Prod", "Rec$_", "UID$_", (Get-Date), $_, "Tag$_") } # Post records (saves .rdy files and posts them) $unrated.PostSourceData -Verbose # Create a RatedSourceData instance $rated = New-RatedSourceData -SourceCollectorAPI "https://qasc.varibill.com/" ` -TenantKey ([guid]::NewGuid()) -SourceCollectorKey ([guid]::NewGuid()) -SourceCollectorSecret "secret" -Verbose # Add multiple records 1..1500 | ForEach-Object { $rated.AddRatedSourceData("Client$_", "Prod", "Rec$_", "UID$_", (Get-Date), $_, $_*2, $_*1.5, "TAG$_") } # Post records $rated.PostSourceData -Verbose #> enum SourceDataType { unrated; rated } enum FileExtension { json; rdy } Class SourceDataBase { hidden [string]$SourceCollectorAPI hidden [Guid]$TenantKey hidden [Guid]$SourceCollectorKey hidden [string]$SourceCollectorSecret hidden [string]$SourceCollectorPath hidden [System.Collections.ArrayList]$Records hidden [PSCredential]$Credential hidden [int]$MaxRecordsPerFile hidden [SourceDataType]$SourceDataType SourceDataBase( [string]$SourceCollectorAPI, [Guid]$TenantKey, [Guid]$SourceCollectorKey, [string]$SourceCollectorSecret, [string]$SourceCollectorPath = $null, [int]$MaxRecordsPerFile = 10000, [SourceDataType]$SourceDataType ) { Write-Verbose "Initializing SourceDataBase..." $this.SourceCollectorAPI = $SourceCollectorAPI $this.TenantKey = $TenantKey $this.SourceCollectorKey = $SourceCollectorKey $this.SourceCollectorSecret = $SourceCollectorSecret $this.MaxRecordsPerFile = $MaxRecordsPerFile $this.SourceDataType = $SourceDataType switch ($this.SourceDataType) { "unrated" { $baseUri = [System.Uri]::new($this.SourceCollectorAPI ) $relativePath = "api/v1/SourceCollectors/Unrated" $fullUri = [System.Uri]::new($baseUri, $relativePath) $this.SourceCollectorAPI = $fullUri.AbsoluteUri } "rated" { $baseUri = [System.Uri]::new($this.SourceCollectorAPI ) $relativePath = "api/v1/SourceCollectors/Rated" $fullUri = [System.Uri]::new($baseUri, $relativePath) $this.SourceCollectorAPI = $fullUri.AbsoluteUri } Default {} } if (-not $SourceCollectorPath) { $this.SourceCollectorPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "Varibill" $this.SourceCollectorPath = Join-Path -Path $this.SourceCollectorPath -ChildPath "$TenantKey" $this.SourceCollectorPath = Join-Path -Path $this.SourceCollectorPath -ChildPath "$SourceCollectorKey" Write-Verbose "Using temp path: $($this.SourceCollectorPath)" } else { $this.SourceCollectorPath = $SourceCollectorPath } if (-not (Test-Path $this.SourceCollectorPath)) { Write-Verbose "Creating directory: $($this.SourceCollectorPath)" New-Item -ItemType Directory -Path $this.SourceCollectorPath -Force | Out-Null } $subDirs = @("$($this.SourceDataType.ToString())", "log") $subDirs | ForEach-Object { $path = Join-Path -Path $this.SourceCollectorPath -ChildPath $_ if (-not (Test-Path $path)) { Write-Verbose "Creating directory: $path" New-Item -ItemType Directory -Path $path -Force | Out-Null } } $this.Records = [System.Collections.ArrayList]::new() $this.Credential = New-Object System.Management.Automation.PSCredential( "$TenantKey#$SourceCollectorKey", (ConvertTo-SecureString $SourceCollectorSecret -AsPlainText -Force) ) Write-Verbose "SourceDataBase initialized with TenantKey=$TenantKey, SourceCollectorKey=$SourceCollectorKey" } hidden [string]GetFileName([FileExtension]$extension) { $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") $guid = [guid]::NewGuid().ToString() return "$timestamp`_$guid.$($extension.ToString())" } SaveData() { if (-not $this.Records -or $this.Records.Count -eq 0) { Write-Verbose "No records to save as .rdy"; return} $chunks = [Math]::Ceiling($this.Records.Count / $this.MaxRecordsPerFile) Write-Verbose "Saving $($this.Records.Count) records into $chunks .rdy file(s)." for ($i = 0; $i -lt $chunks; $i++) { $start = $i * $this.MaxRecordsPerFile $count = [Math]::Min($this.MaxRecordsPerFile, $this.Records.Count - $start) $subset = $this.Records[$start..($start + $count - 1)] $fileName = Join-Path $this.SourceCollectorPath $this.SourceDataType.ToString() $fileName = Join-Path $fileName $this.GetFileName([FileExtension]::Rdy) Write-Verbose "Writing $count records to $fileName" $subset | ConvertTo-Json -Depth 5 | Set-Content -Path $fileName -Encoding UTF8 } $this.Records.Clear() return } PostSourceData() { Write-Verbose "PostSourceData called → saving to .rdy files first..." $this.SaveData() $filePath = Join-Path $this.SourceCollectorPath $this.SourceDataType.ToString() Write-Verbose "Now posting saved .rdy files..." $files = Get-ChildItem -Path $filePath -Filter "*.rdy" -File if (-not $files) { Write-Verbose "No .rdy files found in $filePath" return } foreach ($file in $files) { Write-Verbose "Processing file: $($file.FullName)" $jsonBody = Get-Content -Path $file.FullName -Raw $headers = @{ "Content-Type" = "application/json" } # Invoke-WebRequest to capture StatusCode $response = Invoke-WebRequest -Uri $this.SourceCollectorAPI -Method Post -Body $jsonBody -Headers $headers -Authentication Basic -Credential $this.Credential -ErrorAction Stop -RetryIntervalSec 5 -MaximumRetryCount 3 $fileName = Join-Path $this.SourceCollectorPath "log" $fileName = Join-Path $fileName $this.GetFileName([FileExtension]::json) $response.Content | Out-File -FilePath $fileName -Encoding utf8 Write-Verbose "HTTP Status Code: $($response.StatusCode)" Write-Verbose "Response content: $($response.Content)" if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) { Write-Verbose "Post successful." $newName = $file.FullName -replace '\.rdy$', '.json' if (Test-Path $newName) { Remove-Item $newName -Force } Rename-Item -Path $file.FullName -NewName $newName Write-Verbose "Upload successful for $($file.Name). Renamed to $(Split-Path $newName -Leaf)" } else { Write-Warning "Post failed with HTTP status $($response.StatusCode)" } } return } } Class UnratedSourceData : SourceDataBase { UnratedSourceData([string]$SourceCollectorAPI,[Guid]$TenantKey,[Guid]$SourceCollectorKey,[string]$SourceCollectorSecret,[string]$SourceCollectorPath=$null,[int]$MaxRecordsPerFile=1000) : base($SourceCollectorAPI,$TenantKey,$SourceCollectorKey,$SourceCollectorSecret,$SourceCollectorPath,$MaxRecordsPerFile,[SourceDataType]::Unrated) { Write-Verbose "UnratedSourceData instance created." } AddUnratedSourceData([string]$ClientIdentifier,[string]$ProductIdentifier,[string]$RecordIdentifier,[string]$UniqueIdentifier,[datetime]$LastSeenDate,[decimal]$Quantity,[string]$Tag="") { Write-Verbose "Adding UnratedSourceData record for ClientIdentifier=$ClientIdentifier" $record = [PSCustomObject]@{ ClientIdentifier=$ClientIdentifier; ProductIdentifier=$ProductIdentifier; RecordIdentifier=$RecordIdentifier; UniqueIdentifier=$UniqueIdentifier; LastSeenDate=$LastSeenDate.ToString("o"); Quantity=$Quantity; Tag=$Tag } [void]$this.Records.Add($record) } } Class RatedSourceData : SourceDataBase { RatedSourceData([string]$SourceCollectorAPI,[Guid]$TenantKey,[Guid]$SourceCollectorKey,[string]$SourceCollectorSecret,[string]$SourceCollectorPath=$null,[int]$MaxRecordsPerFile=1000) : base($SourceCollectorAPI,$TenantKey,$SourceCollectorKey,$SourceCollectorSecret,$SourceCollectorPath,$MaxRecordsPerFile,[SourceDataType]::Rated) { Write-Verbose "RatedSourceData instance created." } AddRatedSourceData([string]$ClientIdentifier,[string]$ProductIdentifier,[string]$RecordIdentifier,[string]$UniqueIdentifier,[datetime]$LastSeenDate,[decimal]$Quantity,[decimal]$Total=0,[decimal]$Cost=0,[string]$Tag="") { Write-Verbose "Adding RatedSourceData record for ClientIdentifier=$ClientIdentifier" $record = [PSCustomObject]@{ ClientIdentifier=$ClientIdentifier; ProductIdentifier=$ProductIdentifier; RecordIdentifier=$RecordIdentifier; UniqueIdentifier=$UniqueIdentifier; LastSeenDate=$LastSeenDate.ToString("o"); Quantity=$Quantity; Total=$Total; Cost=$Cost; Tag=$Tag } [void]$this.Records.Add($record) } } Function New-UnratedSourceData { [CmdletBinding()] Param( [Parameter(Mandatory)][string]$SourceCollectorAPI, [Parameter(Mandatory)][string]$TenantKey, [Parameter(Mandatory)][string]$SourceCollectorKey, [Parameter(Mandatory)][string]$SourceCollectorSecret, [string]$SourceCollectorPath=$null, [int]$MaxRecordsPerFile=1000 ) Write-Verbose "Creating new UnratedSourceData instance (MaxRecordsPerFile=$MaxRecordsPerFile)..." return [UnratedSourceData]::new($SourceCollectorAPI,$TenantKey,$SourceCollectorKey,$SourceCollectorSecret,$SourceCollectorPath,$MaxRecordsPerFile) } Function New-RatedSourceData { [CmdletBinding()] Param( [Parameter(Mandatory)][string]$SourceCollectorAPI, [Parameter(Mandatory)][string]$TenantKey, [Parameter(Mandatory)][string]$SourceCollectorKey, [Parameter(Mandatory)][string]$SourceCollectorSecret, [string]$SourceCollectorPath=$null, [int]$MaxRecordsPerFile=1000 ) Write-Verbose "Creating new RatedSourceData instance (MaxRecordsPerFile=$MaxRecordsPerFile)..." return [RatedSourceData]::new($SourceCollectorAPI,$TenantKey,$SourceCollectorKey,$SourceCollectorSecret,$SourceCollectorPath,$MaxRecordsPerFile) } |