---
updatedAt: 2025-11-12T06:10:04.000Z
agentTools:
  siteIndex: https://developer.n-able.com/llms.txt
  projectIndex: https://developer.n-able.com/n-able-cove/llms.txt
---

# Cove - Get Device Session Statistics

```powershell PowerShell
<# ----- About: ----
    # Get N-able Backup Device Session Statistics 
    # Revision v14 - 2025-06-24
    # Author: Eric Harless, Head Backup Nerd - N-able 
    # Twitter @Backup_Nerd  Email:eric.harless@n-able.com
    # Reddit https://www.reddit.com/r/Nable/
# -----------------------------------------------------------#>  ## About

<# ----- Legal: ----
    # Sample scripts are not supported under any N-able support program or service.
    # The sample scripts are provided AS IS without warranty of any kind.
    # N-able expressly disclaims all implied warranties including, warranties
    # of merchantability or of fitness for a particular purpose. 
    # In no event shall N-able or any other party be liable for damages arising
    # out of the use of or inability to use the sample scripts.
# -----------------------------------------------------------#>  ## Legal

<# ----- Compatibility: ----
    # For use with the Standalone edition of N-able Cove Data Protection
    # Sample scripts may contain non-public API calls which are subject to change without notification
# -----------------------------------------------------------#>  ## Compatibility

<# ----- Behavior: ----
    # Check/ Get/ Store secure credentials 
    # Authenticate to https://backup.management console
    # Check partner level/ Enumerate partners/ GUI select partner
    # Enumerate devices/ GUI select devices
    # Get device statistics for selected partner/ devices
    # Get device session statistics for selected partner/ devices
    # Optionally export to XLS/CSV
    #
    # Use the -AllPartners switch parameter to skip GUI partner selection
    # Use the -AllDevices switch parameter to skip GUI device selection
    # Use the -DeviceCount ## (default=5000) parameter to define the maximum number of devices returned
    # Use the -Export switch parameter to export statistics to XLS/CSV files
    # Use the -ExportPath (?:\Folder) parameter to specify XLS/CSV file path
    # Use the -Launch switch parameter to launch the XLS/CSV file after completion
    # Use the -Delimiter (default=',') parameter to set the delimiter for XLS/CSV output (i.e. use ';' for The Netherland)
    # Use the -ClearCredentials parameter to remove stored API credentials at start of script
    # Use the -DeviceFilter (default="AT==1 && OT==2 && TL>0") parameter to filter devices by Active, Online and Total Last Session Time
    # Use the -Size (default=50) parameter to set the number of total sessions to query
    # Use the -Unique (default=1) parameter to set the number of unique sessions per data source to return
    # Use the -Order (default=ASC) parameter to set the order of sessions to pull
    # Use the -Query parameter to set the query to filter sessions
    
    # https://documentation.n-able.com/backup/userguide/documentation/Content/service-management/json-api/home.htm
    # https://documentation.n-able.com/backup/userguide/documentation/Content/service-management/json-api/API-column-codes.htm
# -----------------------------------------------------------#>  ## Behavior

[CmdletBinding()]
    Param (
        [Parameter(Mandatory=$False)] [switch]$AllPartners = $false,                    ## Skip partner selection
        [Parameter(Mandatory=$False)] [switch]$AllDevices = $false,                     ## Skip device selection
        [Parameter(Mandatory=$False)] [int]$DeviceCount = 5000,                         ## Change Maximum Number of devices results to return
        [Parameter(Mandatory=$False)] [Int]$size = 50,                                  ## Number of Total Sessions query 
        [Parameter(Mandatory=$False)] [Int]$unique = 1,                                 ## Number of Unique Sessions per Datasource to return
        [Parameter(Mandatory=$False)] [switch]$Export = $true,                          ## Generate CSV / XLS Output Files
        [Parameter(Mandatory=$False)] [switch]$Launch = $true,                          ## Launch XLS or CSV file 
        [Parameter(Mandatory=$False)] [string]$Delimiter = ',',                         ## specify ',' or ';' Delimiter for XLS & CSV file   
        [Parameter(Mandatory=$False)] $ExportPath = "$PSScriptRoot",                    ## Export Path
        [Parameter(Mandatory=$False)] [switch]$ClearCredentials,                        ## Remove Stored API Credentials at start of script
        [Parameter(Mandatory=$False)] [string]$DeviceFilter = "AT==1 && OT==2 && TL>0"  ## Filter for Devices
    )

#region ----- Environment, Variables, Names and Paths ----
    Clear-Host
    $ConsoleTitle = "Get Device Statistics"
    $host.UI.RawUI.WindowTitle = $ConsoleTitle
    $scriptpath = $MyInvocation.MyCommand.Path
    Write-output "  $ConsoleTitle`n`n$ScriptPath"
    $Syntax = Get-Command $PSCommandPath -Syntax
    Write-Output "  Script Parameter Syntax:`n`n  $Syntax"

    $dir = Split-Path $scriptpath
    Push-Location $dir
    $CurrentDate = Get-Date -format "yyy-MM-dd_HH-mm-ss"
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $Script:strLineSeparator = "  ---------"
    $urljson = "https://api.backup.management/jsonapi"

    Write-output "  Current Parameters:"
    Write-output "  -AllPartners = $AllPartners"
    Write-output "  -AllDevices  = $AllDevices"
    Write-output "  -DeviceCount = $DeviceCount"
    Write-output "  -Export      = $Export"
    Write-output "  -Launch      = $Launch"
    Write-output "  -ExportPath  = $ExportPath"
    Write-output "  -Delimiter   = $Delimiter"

#endregion ----- Environment, Variables, Names and Paths ----

#region ----- Functions ----

#region ----- Authentication ----
Function Set-APICredentials {

    Write-Output $Script:strLineSeparator 
    Write-Output "  Setting Backup API Credentials" 
    if (Test-Path $APIcredpath) {
        Write-Output $Script:strLineSeparator 
        Write-Output "  Backup API Credential Path Present" }else{ New-Item -ItemType Directory -Path $APIcredpath} 

        Write-Output "  Enter Exact, Case Sensitive Partner Name for N-able | Cove Data Protection | Backup.Management API i.e. 'Acme, Inc (bob@acme.net)'"
    DO{ $Script:PartnerName = Read-Host "  Enter Login Partner Name" }
    WHILE ($PartnerName.length -eq 0)
    $PartnerName | out-file $APIcredfile

    $BackupCred = Get-Credential -UserName "" -Message 'Enter Login Email and Password for N-able | Cove Data Protection  API'
    $BackupCred | Add-Member -MemberType NoteProperty -Name PartnerName -Value "$PartnerName" 

    $BackupCred.UserName | Out-file -append $APIcredfile
    $BackupCred.Password | ConvertFrom-SecureString | Out-file -append $APIcredfile
    
    Start-Sleep -milliseconds 300

    Send-APICredentialsCookie  ## Attempt API Authentication

}  ## Set API credentials if not present

Function Get-APICredentials {

    $Script:True_path = "C:\ProgramData\MXB\"
    $Script:APIcredfile = join-path -Path $True_Path -ChildPath "$env:computername API_Credentials.Secure.txt"
    $Script:APIcredpath = Split-path -path $APIcredfile

    if (($ClearCredentials) -and (Test-Path $APIcredfile)) { 
        Remove-Item -Path $Script:APIcredfile
        $ClearCredentials = $Null
        Write-Output $Script:strLineSeparator 
        Write-Output "  Backup API Credential File Cleared"
        Send-APICredentialsCookie  ## Retry Authentication
        
        }else{ 
            Write-Output $Script:strLineSeparator 
            Write-Output "  Getting Backup API Credentials" 
        
            if (Test-Path $APIcredfile) {
                Write-Output    $Script:strLineSeparator        
                "  Backup API Credential File Present"
                $APIcredentials = get-content $APIcredfile
                
                $Script:cred0 = [string]$APIcredentials[0] 
                $Script:cred1 = [string]$APIcredentials[1]
                $Script:cred2 = $APIcredentials[2] | Convertto-SecureString 
                $Script:cred2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Script:cred2))

                Write-Output    $Script:strLineSeparator 
                Write-output "  Stored Backup API Partner  = $Script:cred0"
                Write-output "  Stored Backup API User     = $Script:cred1"
                Write-output "  Stored Backup API Password = Encrypted"
                
            }else{
                Write-Output    $Script:strLineSeparator 
                Write-Output "  Backup API Credential File Not Present"
                Set-APICredentials  ## Create API Credential File if Not Found
                }
            }

}  ## Get API credentials if present

Function Send-APICredentialsCookie {

Get-APICredentials  ## Read API Credential File before Authentication

$url = "https://api.backup.management/jsonapi"
$data = @{}
$data.jsonrpc = '2.0'
$data.id = '2'
$data.method = 'Login'
$data.params = @{}
$data.params.partner = $Script:cred0
$data.params.username = $Script:cred1
$data.params.password = $Script:cred2

$webrequest = Invoke-WebRequest -Method POST `
    -ContentType 'application/json' `
    -Body (ConvertTo-Json $data) `
    -Uri $url `
    -SessionVariable Script:websession `
    -UseBasicParsing
    $Script:cookies = $websession.Cookies.GetCookies($url)
    $Script:websession = $websession
    $Script:Authenticate = $webrequest | convertfrom-json

#Debug Write-output "$($Script:cookies[0].name) = $($cookies[0].value)"

if ($authenticate.visa) { 

    $Script:visa = $authenticate.visa
    $script:UserId = $authenticate.result.result.id
    }else{
        Write-Output    $Script:strLineSeparator 
        Write-output "  Authentication Failed: Please confirm your Backup.Management Partner Name and Credentials"
        Write-output "  Please Note: Multiple failed authentication attempts could temporarily lockout your user account"
        Write-Output    $Script:strLineSeparator 
        
        Set-APICredentials  ## Create API Credential File if Authentication Fails
    }

}  ## Use Backup.Management credentials to Authenticate

#endregion ----- Authentication ----

#region ----- Data Conversion ----
Function Convert-UnixTimeToDateTime($inputUnixTime){
    if ($inputUnixTime -gt 0 ) {
    $epoch = Get-Date -Date "1970-01-01 00:00:00Z"
    $epoch = $epoch.ToUniversalTime()
    $epoch = $epoch.AddSeconds($inputUnixTime)
    return $epoch
    }else{ return ""}
}  ## Convert epoch time to date time 

Function Save-CSVasExcel {
    param (
        [string]$CSVFile = $(Throw 'No file provided.')
    )
    
    BEGIN {
        function Resolve-FullPath ([string]$Path) {    
            if ( -not ([System.IO.Path]::IsPathRooted($Path)) ) {
                # $Path = Join-Path (Get-Location) $Path
                $Path = "$PWD\$Path"
            }
            [IO.Path]::GetFullPath($Path)
        }

        function Release-Ref ($ref) {
            ([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
        
        $CSVFile = Resolve-FullPath $CSVFile
        $xl = New-Object -ComObject Excel.Application
    }

    PROCESS {
        $wb = $xl.workbooks.open($CSVFile)
        $xlOut = $CSVFile -replace '\.csv$', '.xlsx'
        
        # can comment out this part if you don't care to have the columns autosized
        $ws = $wb.Worksheets.Item(1)
        $range = $ws.UsedRange
        [void]$range.AutoFilter()
        [void]$range.EntireColumn.Autofit()

        $num = 1
        $dir = Split-Path $xlOut
        $base = $(Split-Path $xlOut -Leaf) -replace '\.xlsx$'
        $nextname = $xlOut
        while (Test-Path $nextname) {
            $nextname = Join-Path $dir $($base + "-$num" + '.xlsx')
            $num++
        }

        $wb.SaveAs($nextname, 51)
    }

    END {
        $xl.Quit()
    
        $null = $ws, $wb, $xl | ForEach-Object {Release-Ref $_}

        # del $CSVFile
    }
}  ## Save as output XLS Routine

#endregion ----- Data Conversion ----

#region ----- Backup.Management JSON Calls ----

Function Send-GetPartnerInfo ($PartnerName) { 
                
    $url = "https://api.backup.management/jsonapi"
    $data = @{}
    $data.jsonrpc = '2.0'
    $data.id = '2'
    $data.visa = $Script:visa
    $data.method = 'GetPartnerInfo'
    $data.params = @{}
    $data.params.name = [String]$PartnerName

    $webrequest = Invoke-WebRequest -Method POST `
        -ContentType 'application/json' `
        -Body (ConvertTo-Json $data -depth 5) `
        -Uri $url `
        -SessionVariable Script:websession `
        -UseBasicParsing
        #$Script:cookies = $websession.Cookies.GetCookies($url)
        $Script:websession = $websession
        $Script:Partner = $webrequest | convertfrom-json

    $RestrictedPartnerLevel = @("Root","Sub-root")

    if ($Partner.result.result.Level -notin $RestrictedPartnerLevel) {
        [String]$Script:Uid = $Partner.result.result.Uid
        [int]$Script:PartnerId = [int]$Partner.result.result.Id
        [String]$script:Level = $Partner.result.result.Level
        [String]$Script:PartnerName = $Partner.result.result.Name

        Write-Output $Script:strLineSeparator
        Write-output "  $PartnerName - $partnerId - $Uid"
        Write-Output $Script:strLineSeparator
        }else{
        Write-Output $Script:strLineSeparator
        Write-Host "  Lookup for $($Partner.result.result.Level) Partner Level Not Allowed"
        Write-Output $Script:strLineSeparator
        $Script:PartnerName = Read-Host "  Enter EXACT Case Sensitive Customer/ Partner displayed name to lookup i.e. 'Acme, Inc (bob@acme.net)'"
        Send-GetPartnerInfo $Script:partnername
        }

    if ($partner.error) {
        write-output "  $($partner.error.message)"
        $Script:PartnerName = Read-Host "  Enter EXACT Case Sensitive Customer/ Partner displayed name to lookup i.e. 'Acme, Inc (bob@acme.net)'"
        Send-GetPartnerInfo $Script:partnername

    }

}  ## get PartnerID and Partner Level    

Function CallJSON($url,$object) {

    $bytes = [System.Text.Encoding]::UTF8.GetBytes($object)
    $web = [System.Net.WebRequest]::Create($url)
    $web.Method = "POST"
    $web.ContentLength = $bytes.Length
    $web.ContentType = "application/json"
    $stream = $web.GetRequestStream()
    $stream.Write($bytes,0,$bytes.Length)
    $stream.close()
    $reader = New-Object System.IO.Streamreader -ArgumentList $web.GetResponse().GetResponseStream()
    return $reader.ReadToEnd()| ConvertFrom-Json
    $reader.Close()
}  ## Call JSON Web Request Function

Function Send-EnumeratePartners {
    # ----- Get Partners via EnumeratePartners -----
    
    # (Create the JSON object to call the EnumeratePartners function)
        $objEnumeratePartners = (New-Object PSObject | Add-Member -PassThru NoteProperty jsonrpc '2.0' |
            Add-Member -PassThru NoteProperty visa $Script:visa |
            Add-Member -PassThru NoteProperty method 'EnumeratePartners' |
            Add-Member -PassThru NoteProperty params @{
                                                        parentPartnerId = $PartnerId 
                                                        fetchRecursively = "true"
                                                        fields = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) 
                                                        } |
            Add-Member -PassThru NoteProperty id '1')| ConvertTo-Json -Depth 5
    
    # (Call the JSON Web Request Function to get the EnumeratePartners Object)
            [array]$Script:EnumeratePartnersSession = CallJSON $urlJSON $objEnumeratePartners
    
            $Script:visa = $EnumeratePartnersSession.visa
    
            #Write-Output    $Script:strLineSeparator
            #Write-Output    "  Using Visa:" $Script:visa
            #Write-Output    $Script:strLineSeparator
    
    # (Added Delay in case command takes a bit to respond)
            Start-Sleep -Milliseconds 100
    
    # (Get Result Status of EnumerateAccountProfiles)
            $EnumeratePartnersSessionErrorCode = $EnumeratePartnersSession.error.code
            $EnumeratePartnersSessionErrorMsg = $EnumeratePartnersSession.error.message
    
    # (Check for Errors with EnumeratePartners - Check if ErrorCode has a value)
            if ($EnumeratePartnersSessionErrorCode) {
                Write-Output    $Script:strLineSeparator
                Write-Output    "  EnumeratePartnersSession Error Code:  $EnumeratePartnersSessionErrorCode"
                Write-Output    "  EnumeratePartnersSession Message:  $EnumeratePartnersSessionErrorMsg"
                Write-Output    $Script:strLineSeparator
                Write-Output    "  Exiting Script"
    # (Exit Script if there is a problem)
    
                #Break Script
            }
                Else {
    # (No error)
    
            $Script:EnumeratePartnersSessionResults = $EnumeratePartnersSession.result.result | select-object Name,@{l='Id';e={($_.Id).tostring()}},Level,ExternalCode,ParentId,LocationId,* -ExcludeProperty Company -ErrorAction Ignore
            
            $Script:EnumeratePartnersSessionResults | ForEach-Object {$_.CreationTime = [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.CreationTime))}
            $Script:EnumeratePartnersSessionResults | ForEach-Object { if ($_.TrialExpirationTime  -ne "0") { $_.TrialExpirationTime = [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.TrialExpirationTime))}}
            $Script:EnumeratePartnersSessionResults | ForEach-Object { if ($_.TrialRegistrationTime -ne "0") {$_.TrialRegistrationTime = [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.TrialRegistrationTime))}}
        
            $Script:SelectedPartners = $EnumeratePartnersSessionResults | Select-object * | Where-object {$_.name -notlike "001???????????????- Recycle Bin"} | Where-object {$_.Externalcode -notlike '`[??????????`]* - ????????-????-????-????-????????????'}
                    
            $Script:SelectedPartner = $Script:SelectedPartners += @( [pscustomobject]@{Name=$PartnerName;Id=[string]$PartnerId;Level='<ParentPartner>'} ) 
            
            
            if ($AllPartners) {
                $script:Selection = $Script:SelectedPartners |Select-object id,Name,Level,CreationTime,State,TrialRegistrationTime,TrialExpirationTime,Uid | sort-object Level,name
                Write-Output    $Script:strLineSeparator
                Write-Output    "  All Partners Selected"
            }else{
                $script:Selection = $Script:SelectedPartners |Select-object id,Name,Level,CreationTime,State,TrialRegistrationTime,TrialExpirationTime,Uid | sort-object Level,name | out-gridview -Title "Current Partner | $partnername" -OutputMode Single
        
                if($null -eq $Selection) {
                    # Cancel was pressed
                    # Run cancel script
                    Write-Output    $Script:strLineSeparator
                    Write-Output    "  No Partners Selected"
                    Break
                }
                else {
                    # OK was pressed, $Selection contains what was chosen
                    # Run OK script
                    [int]$script:PartnerId = $script:Selection.Id
                    [String]$script:PartnerName = $script:Selection.Name
                }
            }

    }
    
}  ## EnumeratePartners API Call

Function Get-AccountInfoById {
    Param (
    [Parameter(Mandatory=$True)] [string]$accountid                        ## Account
    )
    #write-output $accountid
    $url = "https://api.backup.management/jsonapi"

    # GetAccountInfoById request
    $dataAccountInfo = @{
        method  = "GetAccountInfoById"
        params  = @{
            accountId = [int]$accountid
        }
        jsonrpc = "2.0"
        visa    = $visa
        id      = "jsonrpc"
    } | ConvertTo-Json -Depth 5

    $Script:responseAccountInfo = Invoke-RestMethod -Uri $url -Method POST -ContentType 'application/json' -Body $dataAccountInfo

    $Script:repsvr = $null
    $Script:repsvr = @{}
    $Script:repsvr.repurl = ("https://" + ($script:responseAccountInfo.result.homeNodeInfo.CommonInfo.Host -split ':')[0] + "/repserv_json").ToLower()
    $Script:repsvr.AccountId = $Script:responseAccountInfo.result.result.Id
    $Script:repsvr.Token = $Script:responseAccountInfo.result.result.Token
    $Script:repsvr.Name = $Script:responseAccountInfo.result.result.Name
    
    #$Script:repsvr
}  ## Get Account Info by ID

Function Get-Sessions {
    Param (
    [Parameter(Mandatory=$True)] [string]$accountid,                        ## Account ID
    [Parameter(Mandatory=$False)]
        [ValidateSet("ASC", "DESC")] [string]$Order = "ASC",                ## Order of Sessions to pull
    [Parameter(Mandatory=$False)] [Int]$Size = 10,                          ## Number of Sessions to pull
    [Parameter(Mandatory=$False)] [Int]$Unique = 1,                         ## Number of Unique Sessions per Datasource to return
    [Parameter(Mandatory=$False)] [String]$query,                           ## Query to filter Sessions
    [Parameter(Mandatory=$False)] [switch]$Gridview = $false                ## Include Oldest Sessions in the output
    )
    
    Get-AccountInfoById $accountid

    Write-Output "Pulling $Size ($unique/SRC) $order Sessions for | $SessionPartner | $($script:repsvr.Name) | $($script:repsvr.AccountId) | $($script:repsvr.Token) from $($script:repsvr.repurl)"
    
    <#

    This funcrtion contains multiple query definitions used to filter and retrieve device session statistics based on specific conditions.
    The queries are designed to filter session data based on various attributes such as status, flags, and action types. Below is an explanation of each query:

    1. `0 != 1 and Status == 5 and !((Flags & 2) == 2)`
        - Filters sessions where the status is 'Completed' (Status == 5) and the 'Cleaned' flag is not set (Flags & 2 != 2).

    2. `0 != 1 and (Status == 5 or Status == 8)`
        - Filters sessions where the status is either 'Completed' (Status == 5) or 'Completed with errors' (Status == 8).

    3. `0 != 1 and !((Flags & 2) == 2)`
        - Filters sessions where the 'Cleaned' flag is not set (Flags & 2 != 2).

    4. `0 != 1 and (Type & 1) == 1`
        - Filters sessions where the action type is 'Backup' (Type & 1 == 1).

    5. `0 != 1 and (Type & 1) == 1 and !((Flags & 2) == 2)`
        - Filters sessions where the action type is 'Backup' (Type & 1 == 1) and the 'Cleaned' flag is not set (Flags & 2 != 2).
    6. `0 != 1 and BackupStartTime >= 1750699080 and BackupStartTime <= 1750785540`
        - Filters sessions where the backup start time is between '23 Jun 2025, 13:18:00 PM' and '24 Jun 2025, 13:19:00 PM'.
    7. `0 != 1 and (Plugin == 4 or Plugin == 1 or Plugin == 14 or Plugin == 10 or Plugin == 6 or Plugin == 11 or (Plugin == 7 or Plugin == 18) or Plugin == 8 or Plugin == 15)`
        - Filters sessions where the data source is any of 'Exchange', 'Files and Folders', 'Hyper-V', 'MS SQL', 'Network Shares', 'SharePoint', 'System State', 'VMware', or 'MySQL'.
        # The query filters data based on the Plugin type. Below is the mapping of Plugin numbers to their respective data sources:
        # Plugin == 4   -> Exchange
        # Plugin == 1   -> Files and Folders
        # Plugin == 14  -> Hyper-V
        # Plugin == 10  -> MS SQL
        # Plugin == 6   -> Network Shares
        # Plugin == 11  -> SharePoint
        # Plugin == 7   -> System State
        # Plugin == 18  -> System State (alternative)
        # Plugin == 8   -> VMware
        # Plugin == 15  -> MySQL
        
    - The queries use bitwise operations to evaluate flags and action types.
    - Ensure that the data source being queried supports the syntax and logic used in these queries.

    #>
    #$query = "0 != 1 and Status == 5 and !((Flags & 2) == 2)"       # [Session status] Is 'Completed' And [Flags] Is none of('Cleaned')
    #$query = "0 != 1 and (Status == 5 or Status == 8)"              # [Session status] Is any of('Completed', 'Completed with errors')
    #$query = "0 != 1 and !((Flags & 2) == 2)"                       # [Flags] Is none of('Cleaned')
    #$query = "0 != 1 and (Type & 1) == 1"                           # [Action] Is any of('Backup')
    #$query = "0 != 1 and BackupStartTime >= 1750699080 and BackupStartTime <= 1750785540" # [Date] Is between('23 Jun 2025, 13:18:00 PM', '24 Jun 2025, 13:19:00 PM')
    #$query = "0 != 1 and (Plugin == 4 or Plugin == 1 or Plugin == 14 or Plugin == 10 or Plugin == 6 or Plugin == 11 or (Plugin == 7 or Plugin == 18) or Plugin == 8 or Plugin == 15)" # [Data source] Is any of('Exchange', 'Files and Folders', 'Hyper-V', 'MS SQL', 'Network Shares', 'SharePoint', 'System State', 'VMware', 'MySQL')
    #$query = "0 != 1 and (Type & 1) == 1 and !((Flags & 2) == 2)"   # [Action] Is any of('Backup') And [Flags] Is none of('Cleaned')

    # QuerySessions request
    $dataSessions = @{
        method  = "QuerySessions"
        params  = @{
            accountId = $script:repsvr.AccountId
            orderBy   = "Id $Order"
            query = $query
            range     = @{
                Offset = 0
                Size   = $Size
            }
            account = $Script:repsvr.Name 
            token   = $Script:repsvr.Token
        }
        jsonrpc = "2.0"
        visa    = $script:visa
        id      = "jsonrpc"
    }

    $responseSessions = Invoke-RestMethod -Uri $Script:repsvr.repurl -Method POST -ContentType 'application/json' -Body ($dataSessions | ConvertTo-Json -Depth 10)

    $Script:InitialSessions = $responseSessions.result.result | Where-Object {($_.Status -like "Completed*")} | Sort-Object {[int]$_.Id} |
        Select-Object @{
            Name='PartnerID';Expression={$SessionPartnerID}},
            @{Name='PartnerName';Expression={$SessionPartner}},
            @{Name='SessionId';Expression={[int]$_.Id}},
            AccountId,
            Name,
            PluginId,
            @{Name='Flags';Expression={[string]::Join(' ', $_.Flags)}},
            Status,
            CreationTime,
            StartTime,
            EndTime,
            DurationSec,
            Duration, 
            GBps,
            MBps,
            KBps,
            SelectedSizeGB,
            SelectedSize,
            ProcessedSizeGB,
            ProcessedSize,
            ChangedSizeGB,
            ChangedSize,
            SentSize,
            SelectedCount,
            ProcessedCount,
            ChangedCount,
            ErrorsCount,
            RemovedFilesCount,* -ExcludeProperty TransientValues,LicenceItemsCount -ErrorAction SilentlyContinue

    $Script:InitialSessions | ForEach-Object {
        $_.durationSec = ($_.EndTime - $_.StartTime)
        $_.duration = New-TimeSpan -Seconds ($_.EndTime - $_.StartTime) | ForEach-Object { "{0:D2}:{1:D2}:{2:D2}" -f ($_.Days * 24 + $_.Hours), $_.Minutes, $_.Seconds }
        $_.CreationTime = $SessionDeviceCreation
        if ($_.SelectedSize -gt 0) {$_.KBps = [math]::Round(($_.SelectedSize / 1KB / $_.durationSec), 2)}else { $_.KBps = 0 }
        if ($_.SelectedSize -gt 0) {$_.MBps = [math]::Round(($_.SelectedSize / 1MB / $_.durationSec), 2)} else { $_.MBps = 0 }
        if ($_.SelectedSize -gt 0) {$_.GBps = ($_.SelectedSize/ 1GB / $_.durationSec)} else { $_.GBps = 0 }
        $_.name = $script:repsvr.Name   
        $_.accountId = $script:repsvr.AccountId
        $_.starttime = Convert-UnixTimeToDateTime ($_.StartTime -join '')
        $_.endtime = Convert-UnixTimeToDateTime ($_.EndTime -join '')
        if ($_.SelectedSize -gt 0) { $_.SelectedSizeGB = [math]::Round(($_.SelectedSize / 1GB), 3) }else{ $_.SelectedSizeGB = 0 }
        if ($_.ProcessedSize -gt 0) { $_.ProcessedSizeGB = [math]::Round(($_.ProcessedSize / 1GB), 3) }else{ $_.ProcessedSizeGB = 0 }
        if ($_.ChangedSize -gt 0) { $_.ChangedSizeGB = [math]::Round(($_.ChangedSize / 1GB), 3) }else{ $_.ChangedSizeGB = 0 }
        } -erroraction silentlycontinue

    if ($gridview) { 
        $Script:InitialSessions | Group-Object -Property pluginid | ForEach-Object {
        $_.Group | Sort-Object -Property id | Select-Object -First $unique
        } | Out-gridview -Title "$Size ($Unique/SRC) $order Sessions for $($script:repsvr.Name) | $($script:repsvr.AccountId) | $($script:repsvr.Token) from $($script:repsvr.repurl)" -PassThru
    }else{
        $Script:InitialSessions | Group-Object -Property pluginid | ForEach-Object {
            $_.Group | Sort-Object -Property id | Select-Object -First $unique
        } | Export-Csv -Path $Script:csvoutputfile1 -Append -NoTypeInformation -Encoding UTF8 
        #$csvFileInfo = Get-Item -Path $Script:csvoutputfile1
        #Write-Output "Size of $Script:csvoutputfile1: $($csvFileInfo.Length / 1KB) KB"
    }

}  ## Get Sessions

Function Send-GetDevices {

    $url = "https://api.backup.management/jsonapi"
    $method = 'POST'
    $data = @{}
    $data.jsonrpc = '2.0'
    $data.id = '2'
    $data.visa = $Script:visa
    $data.method = 'EnumerateAccountStatistics'
    $data.params = @{}
    $data.params.query = @{}
    $data.params.query.PartnerId = [int]$PartnerId
    $data.params.query.Filter = $DeviceFilter
    $data.params.query.Columns = @("AU","AR","AN","TM","MN","AL","LN","OP","OI","OS","OT","PD","AP","PF","PN","CD","TS","TL","T3","US","TB","TM","I81","AA843","AA77","AA2048","AA124","AA125","AA58","FA","F3","F5","SA","S3","S5","ZA","Z3","Z5")
    $data.params.query.OrderBy = "AU ASC"
    $data.params.query.StartRecordNumber = 0
    $data.params.query.RecordsCount = $devicecount
    $data.params.query.Totals = @("COUNT(AT==1)","SUM(T3)","SUM(US)")

    $jsondata = (ConvertTo-Json $data -depth 6)

    $params = @{
        Uri         = $url
        Method      = $method
        Headers     = @{ 'Authorization' = "Bearer $Script:visa" }
        Body        = ([System.Text.Encoding]::UTF8.GetBytes($jsondata))
        ContentType = 'application/json; charset=utf-8'
    }  

    $Script:DeviceResponse = Invoke-RestMethod @params 

    $Script:DeviceDetail = @()
    
    ForEach ( $DeviceResult in $DeviceResponse.result.result ) {

        $Script:DeviceDetail += New-Object -TypeName PSObject -Property @{
            AccountID = [Int]$DeviceResult.AccountId;
            PartnerID        = [string]$DeviceResult.PartnerId;
            DeviceName       = $DeviceResult.Settings.AN -join '' ;
            ComputerName     = $DeviceResult.Settings.MN -join '' ;
            DeviceAlias      = $DeviceResult.Settings.AL -join '' ;
            PartnerName      = $DeviceResult.Settings.AR -join '' ;
            Reference        = $DeviceResult.Settings.PF -join '' ;
            Creation         = Convert-UnixTimeToDateTime ($DeviceResult.Settings.CD -join '') ;
            TimeStamp        = Convert-UnixTimeToDateTime ($DeviceResult.Settings.TS -join '') ;
            LastSuccess      = Convert-UnixTimeToDateTime ($DeviceResult.Settings.TL -join '') ;
            Last28Days       = (($DeviceResult.Settings.TB -join '')[-1..-28] -join '') -replace("8",[char]0x26a0) -replace("7",[char]0x23f9) -replace("6",[char]0x23f9) -replace("5",[char]0x2611) -replace("2",[char]0x274e) -replace("1",[char]0x2BC8) -replace("0",[char]0x274c) ;
            Last28           = (($DeviceResult.Settings.TB -join '')[-1..-28] -join '') -replace("8","!") -replace("7","!") -replace("6","?") -replace("5","+") -replace("2","-") -replace("1",">") -replace("0","X") ;
            SelectedGB       = (($DeviceResult.Settings.T3 -join '') / 1GB) ;
            UsedGB           = (($DeviceResult.Settings.US -join '') / 1GB) ;
            DataSources      = $DeviceResult.Settings.AP -join '' ;                                                                
            TotalM365        = $DeviceResult.Settings.TM -join '' ;   
            Account          = $DeviceResult.Settings.AU -join '' ;
            Location         = $DeviceResult.Settings.LN -join '' ;
            Notes            = $DeviceResult.Settings.AA843 -join '' ;
            GUIPassword      = $DeviceResult.Settings.AA2048 -join '' ;                                                                    
            companycc        = $DeviceResult.Settings.AA124 -join '' ;     
            contractcc       = $DeviceResult.Settings.AA125 -join '' ;     
            TempInfo         = $DeviceResult.Settings.AA77 -join '' ;
            Product          = $DeviceResult.Settings.PN -join '' ;
            ProductID        = $DeviceResult.Settings.PD -join '' ;
            Profile          = $DeviceResult.Settings.OP -join '' ;
            ProfileID        = $DeviceResult.Settings.OI -join '' ;
            OS               = $DeviceResult.Settings.OS -join '' ;                                                                
            OSType           = $DeviceResult.Settings.OT -join '' ;
            Physicality      = $DeviceResult.Settings.I81 -join '';
            FFDuration       = New-TimeSpan -Seconds ($DeviceResult.Settings.FA -join '') | ForEach-Object { "{0:D2}:{1:D2}:{2:D2}" -f ($_.Days * 24 + $_.Hours), $_.Minutes, $_.Seconds } ;
            FFSelected       = (($DeviceResult.Settings.F3 -join '') / 1GB) ;
            FFSent           = (($DeviceResult.settings.F5 -join '') / 1GB) ;
            SSDuration       = New-TimeSpan -Seconds ($DeviceResult.Settings.SA -join '') | ForEach-Object { "{0:D2}:{1:D2}:{2:D2}" -f ($_.Days * 24 + $_.Hours), $_.Minutes, $_.Seconds } ;
            SSSelected       = (($DeviceResult.Settings.S3 -join '') / 1GB) ;
            SSSent           = (($DeviceResult.Settings.S5 -join '') / 1GB) ;
            SQLDuration      = New-TimeSpan -Seconds ($DeviceResult.Settings.ZA -join '') | ForEach-Object { "{0:D2}:{1:D2}:{2:D2}" -f ($_.Days * 24 + $_.Hours), $_.Minutes, $_.Seconds } ;
            SQLSelected      = (($DeviceResult.Settings.Z3 -join '') / 1GB) ;
            SQLSent          = (($DeviceResult.Settings.Z5 -join '') / 1GB) 
        }
    } ## End ForEach Loop
}  ## EnumerateAccountStatistics API Call

#endregion ----- Backup.Management JSON Calls ----

#endregion ----- Functions ----

#region ----- Main Script ----

$switch = $PSCmdlet.ParameterSetName

Send-APICredentialsCookie

Write-Output $Script:strLineSeparator
Write-Output "" 

Send-GetPartnerInfo $Script:cred0

if ($AllPartners) {}else{Send-EnumeratePartners}

Send-GetDevices $partnerId

if ($AllDevices) {
    $script:SelectedDevices = $DeviceDetail | Select-Object PartnerId,PartnerName,Reference,AccountID,DeviceName,TotalM365,ComputerName,Creation,TimeStamp,LastSuccess,Last28Days,ProductId,Product,ProfileId,Profile,DataSources,SelectedGB,UsedGB,Location,OS,OSType,Physicality,companycc,contractcc,FFDuration,FFSelected,FFSent,SSDuration,SSSelected,SSSent,SQLDuration,SQLSelected,SQLSent
}else{
    $script:SelectedDevices = $DeviceDetail | Select-Object PartnerId,PartnerName,Reference,AccountID,DeviceName,TotalM365,ComputerName,Creation,TimeStamp,LastSuccess,Last28Days,ProductId,Product,ProfileId,Profile,DataSources,SelectedGB,UsedGB,Location,OS,OSType,Physicality,companycc,contractcc,FFDuration,FFSelected,FFSent,SSDuration,SSSelected,SSSent,SQLDuration,SQLSelected,SQLSent  | Out-GridView -title "Current Partner | $partnername" -OutputMode Multiple}

if($null -eq $SelectedDevices) {
    # Cancel was pressed
    # Run cancel script
    Write-Output    $Script:strLineSeparator
    Write-Output    "  No Devices Selected"
    Break
}else{
    # OK was pressed, $Selection contains what was chosen
    # Run OK script

    If ($Script:Export) {

        $Script:csvoutputfile = "$ExportPath\$($CurrentDate)_Statistics_$($Partnername -replace(`" \(.*\)`",`"`") -replace(`"[^a-zA-Z_0-9]`",`"`"))_$($PartnerId).csv"
        $Script:csvoutputfile1 = "$ExportPath\$($CurrentDate)_InitialSessions_$($Partnername -replace(`" \(.*\)`",`"`") -replace(`"[^a-zA-Z_0-9]`",`"`"))_$($PartnerId).csv"
        $Script:SelectedDevices | Select-object * | Export-CSV -path "$csvoutputfile" -delimiter "$Delimiter" -NoTypeInformation -Encoding UTF8}

        foreach ($device in $Script:SelectedDevices) {

            $SessionPartnerID = $device.PartnerId
            $SessionPartner = $device.PartnerName
            $SessionDeviceCreation = $device.Creation

            Get-Sessions -AccountID $device.AccountID -Order ASC -size $size -Unique $unique -query "0 != 1 and (Type & 1) == 1 and !((Flags & 2) == 2)"
            #Get-Sessions -AccountID $device.AccountID -Order DESC -size $size -Unique 1 
        
        } ##
}

## Generate XLS from CSV

if ($csvoutputfile) {
    $xlsoutputfile = $csvoutputfile.Replace("csv","xlsx")
    Save-CSVasExcel $csvoutputfile
}

if ($csvoutputfile1) {
    $xlsoutputfile1 = $csvoutputfile1.Replace("csv","xlsx")
    Save-CSVasExcel $csvoutputfile1
}

Write-output $Script:strLineSeparator
    
if ($Launch) {
    If (test-path HKLM:SOFTWARE\Classes\Excel.Application) { 
        Start-Process "$xlsoutputfile"
        Start-Process "$xlsoutputfile1"
        Write-output $Script:strLineSeparator
        Write-Output "  Opening XLS file"
        }else{
        Start-Process "$csvoutputfile"
        Start-Process "$csvoutputfile1"
        Write-output $Script:strLineSeparator
        Write-Output "  Opening CSV file"
        Write-output $Script:strLineSeparator            
        }
    } ## Launch CSV or XLS if Excel is installed  (Required -Launch Parameter)

Write-Output $Script:strLineSeparator
Write-Output "  CSV Path = $Script:csvoutputfile"
Write-Output "  XLS Path = $Script:xlsoutputfile"
Write-Output "  CSV Path = $Script:csvoutputfile1"
Write-Output "  XLS Path = $Script:xlsoutputfile1"
Write-Output ""

Start-Sleep -seconds 10
#endregion ----- Main Script ----
```

# Description



Retrieve device statistics, session data, and exports results to CSV/XLS files.