Atempo.Lina.psm1

<#
 .Synopsis
  Provides basic management CLI for Atempo Lina continuous data protection solution
 
 .Description
  Provides basic management CLI for Atempo Lina continuous data protection solution
 
 .Example
# You may have to set the execution policy before using the module
Set-ExecutionPolicy Unrestricted
 
Connect-LinaServer -Server "https://10.0.0.1:8181" -User "superadmin" -Password "MyPassword"
Get-LinaGlobalStats
Get-LinaAgent
Get-LinaAgentStats
Get-LinaCurrentTenant
Get-LinaStrategy
Get-LinaTenant
New-LinaAgent -Name "TEST-QA-API"
Get-LinaAgent -Name "TEST-QA-API" | Set-LinaAgent -Name "TEST-QA-API-RENAMED"
Get-LinaAgent -Name "TEST-QA-API*" | Set-LinaAgent -TenantName "BaasCustomer1"
Get-LinaAgent -Name "TEST-QA-API-RENAMED" | Remove-LinaAgent
Get-LinaTenant -Name "BaasCustomer1" | Set-LinaCurrentTenant
Get-LinaCurrentTenant
Disconnect-LinaServer
#>


<# TODO
    Group (hierarchies) management (Get-LinaAgentGroup / Set-LinaAgent -Group / New-LinaAgent -Group)
    improve agent stats
    manage display of replications
    missing objects (groups, paths, rules etc)
    Ability to pipe commands => should be done everywhere
    Improved error reporting (get error codes ?)
#>


<# Other URLS :
Global stats / info.xml : Objects in infolist
Protection / lst_dataprofiles.xml : Objects in HNConfig.DataProfileArray.DataProfile
Protection rules / lst_filters.xml : Objects in HNConfig.FilterRuleArray.FilterRule
Paths / lst_predefinedpaths.xml : Objects in HNConfig.PredefinedPathArray.PredefinedPath
File Types / lst_filetypes.xml : Objects in HNConfig.FileTypeArray.FileType
Agent groups / lst_hierarchies.xml : Objects in HNConfig.HierarchyArray.Hierarchy
User groups / ADM/list_user_group.json : Objects in user_groups
Groups to users / ADM/list_prof_ug_relation.json : Objects in relations
#>


# Needed for URLEncode
Add-Type -AssemblyName System.Web
$global:LoggedSession = $null
$global:GLOBAL_LINA_SERVER = $null
$global:LINA_TRANSLATIONS = $null
$global:GLOBAL_IGNORE_CERTIFICATES = $True
$global:LINA_DEBUG_MODE=$False


function Get-LinaHelp() {
<#
.SYNOPSIS
Opens the HTML help of this module.
.DESCRIPTION
Opens the HTML help file located in the module folder with default browser.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Get-LinaHelp
Opens the HTML help file located in the module folder with default browser.
#>

    $current_modulepath = Split-Path $script:MyInvocation.MyCommand.Path
    Start-Process "$current_modulepath\help.html"
}

function Disable-SslVerification{
    if (-not ([System.Management.Automation.PSTypeName]"TrustEverything").Type)
    {
        Add-Type -TypeDefinition  @"
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public static class TrustEverything
{
    private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain,
        SslPolicyErrors sslPolicyErrors) { return true; }
    public static void SetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = ValidationCallback; }
    public static void UnsetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = null; }
}
"@

    }
    [TrustEverything]::SetCallback()
}
function Enable-SslVerification {
    if (([System.Management.Automation.PSTypeName]"TrustEverything").Type)
    {
        [TrustEverything]::UnsetCallback()
    }
}

function CallAPI(){
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,
        [Parameter(Mandatory=$False,Position=1)]
        [ValidateNotNullOrEmpty()]
        [string]$ContentType,
        [Parameter(Mandatory=$False,Position=2)]
        [ValidateNotNullOrEmpty()]
        [string]$Body,                  
        [Parameter()]
        [switch]$FirstConnection
    )
    
    if ($Path -like "*.json*") {
        $ContentType="application/json"
    }else {
        # most content is XML so this is default
        $ContentType="application/xml"
    }

    if ($FirstConnection) {
        if ($PSVersionTable.PSVersion.Major -lt 6 -AND $GLOBAL_IGNORE_CERTIFICATES) {
            <# Disable Certificate checking for WebRequest (Pre-PowerShell 6.0) #>
            Disable-SslVerification
            $request = Invoke-RestMethod -Uri $GLOBAL_LINA_SERVER$Path -SessionVariable Currentsession
        } elseif ($GLOBAL_IGNORE_CERTIFICATES) {
            <# Disable Certificate checking for WebRequest (PowerShell >= 6.0) #>
            $request = Invoke-RestMethod -Uri $GLOBAL_LINA_SERVER$Path -SessionVariable Currentsession -SkipCertificateCheck
        }else {
            # Certificate check is enabled
            $request = Invoke-RestMethod -Uri $GLOBAL_LINA_SERVER$Path -SessionVariable Currentsession
        }
        Set-Variable -name LoggedSession -Scope global -Value $Currentsession

    } else {
        # Next connections reuse the Session Cookie set globally
        if ($PSVersionTable.PSVersion.Major -ge 6 -AND $GLOBAL_IGNORE_CERTIFICATES) {
            <# Disable Certificate checking for WebRequest (PowerShell >= 6.0) #>
            $request = Invoke-RestMethod -Uri $GLOBAL_LINA_SERVER$Path -ContentType $ContentType -Method "Post" -Body $Body -WebSession $LoggedSession -SkipCertificateCheck
        }else {
            # Same request if enabled or not. Checkingt is disabled globally on PowerShell > 6.0
            $request = Invoke-RestMethod -Uri $GLOBAL_LINA_SERVER$Path -ContentType $ContentType -Method "Post" -Body $Body -WebSession $LoggedSession
        }
    }
    if ($LINA_DEBUG_MODE) { Write-Host "Call API $Path : $request" }
    Return $request
}
function LinaToLocalTime ($lina_time) { 
    <# Lina Times are UTC based ? Not GMT ?#>
    $date=(Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds($lina_time/1000))
    $oFromTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("UTC")
    $oToTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById([System.TimeZoneInfo]::Local.Id)
    $utc = [System.TimeZoneInfo]::ConvertTimeToUtc($date, $oFromTimeZone)
    $newTime = [System.TimeZoneInfo]::ConvertTime($utc,$oToTimeZone)
    return $newTime
}

function InitTranslations() {
    Param(
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Lang
    )   
    $request = CallAPI "/Admin/locale/$lang.js"
    # Web request does not like the encoding on this one !
    $temp_utf = [System.Text.Encoding]::UTF8.GetString([System.Text.Encoding]::GetEncoding(28591).GetBytes($request.ToString()))
    $temp_split = $temp_utf -split '\n'
    $lines = ($temp_split | Select-String -Pattern "SERVER_NAME_","UNCAT_AGENTS_LABEL" ).line
    $translations = @{}
    foreach ($line in $lines) {
        $splitted = $line -split ': "'
        $key = $splitted[0].Trim().Replace('SERVER_NAME_','').Replace("__","_")
        $value = $splitted[1].Replace('",','').Trim()
        $translations.add($key,$value)
    }
    Set-Variable -name LINA_TRANSLATIONS -Scope global -Value $translations
}

function Translate() {
    Param(
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$ToTranslate
    )

    $ToTranslate = $ToTranslate.TrimStart("_").TrimEnd("_")

    if ($ToTranslate -eq "UNCAT") { $ToTranslate = "UNCAT_AGENTS_LABEL"; }
    $translated = $LINA_TRANSLATIONS[$ToTranslate.Replace("__","_")]


    if ($translated) {
        return $translated
    }else {
        # No translation available. Use original text.
        return $ToTranslate
    }
}


function LinaTimestamp {
    return [math]::Round((New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date)).TotalSeconds*1000)
}
function Connect-LinaServer {
<#
.SYNOPSIS
Connects to an Atempo Lina server.
.DESCRIPTION
Connects to an Atempo Lina server 5.0+ usually using superadmin credentials.
Select your locale using the -Locale switch. Locale is used only to display the default strategies and protections with localized names.
Please note : there is no support for multiple connections to different or same server.
.INPUTS
None
.OUTPUTS
None
.PARAMETER Server
Specify the IP address or the DNS name of the Lina server to which you want to connect.
Also specify the protocol and ports. For example : https://10.0.0.1:8181 or http://10.0.0.1:8181
.PARAMETER User
Specify the user name you want to use for authenticating with the server.
.PARAMETER Password
Specifies the password you want to use for authenticating with the server.
.PARAMETER Locale
Specifies the language that will be used for default strategies and protections labels.
Optional. Default value is English.
Possible values are : "en","fr","es","de"
.PARAMETER Tenant
Select a specific tenant for actions (listing, creation will be limited to this tenant)
Optional. Default value is -1 (All tenant / Global view)
.EXAMPLE
Connect-LinaServer -Server "https://mylinaserver.domain.com:8181" -User "superadmin" -Password "mypassword"
Connection to mylinaserver.domain.com using default locale and global view (no tenant).
.EXAMPLE
Connect-LinaServer -Server "https://mylinaserver.domain.com:8181" -User "superadmin" -Password "mypassword" -Locale "fr" -Tenant "MyTenant"
Connection to mylinaserver.domain.com using french language and filtered on the tenant MyTenant.
#>

    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName="Server",Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Server,
        [Parameter(ParameterSetName="Server",Position=1)]
        [ValidateNotNullOrEmpty()]
        [string]$User,
        [Parameter(ParameterSetName="Server",Position=2)]
        [ValidateNotNullOrEmpty()]
        [string]$Password,
        [Parameter(Mandatory=$false,ParameterSetName="Server",Position=3)]
        [ValidateSet("en","fr","es","de")] 
        [string]$Locale="en",        
        [Parameter(Mandatory=$false,ParameterSetName="Server",Position=4)]
        [ValidateNotNullOrEmpty()]
        [string]$Tenant
    )

    Set-Variable -name GLOBAL_LINA_SERVER -Scope global -Value $Server
    $userenc=[System.Web.HttpUtility]::UrlEncode($User)
    $passenc=[System.Web.HttpUtility]::UrlEncode($Password)
    Write-Host "Connecting to Lina server $GLOBAL_LINA_SERVER : " -NoNewline
    $request = CallAPI -Path "/ADE/login.html?user=$userenc&password=$passenc" -FirstConnection
    if ([string]$request -like "*- OK*") {
        InitTranslations($Locale)
        Write-Host "Successfully connected."
    } else {
        Write-Error "Error occurred trying to connect : \$request\"
    }    
}

function Disconnect-LinaServer {
<#
.SYNOPSIS
Disconnects from an Atempo Lina server.
.DESCRIPTION
Disconnects current session.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Disconnect-LinaServer
Disconnects current session.
#>

    Write-Host "Disconnecting from Lina server $GLOBAL_LINA_SERVER : " -NoNewline
    $request = CallAPI -Path "/ADE/logout.json"
    if ([string]$request -like "*- OK*") {
        Write-Host "Successfully disconnected."
    } else {
        Write-Error "Error occurred trying to disconnect : \$request\"
    }
}

function Get-LinaGlobalStats {
<#
.SYNOPSIS
Retrieves the global stats and configuration of the current Lina server.
.DESCRIPTION
Retrieves the global stats and configuration of the current Lina server such as the server version deduplication ratio, volume stored etc
.INPUTS
None
.OUTPUTS
LinaStats Object
.EXAMPLE
Get-LinaGlobalStats
Retrieve global infos of current Lina Server
.EXAMPLE
$lina_version = (Get-LinaGlobalStats).ALNVersion
Get current installed version of Lina sofware.
#>

    Write-Host "Getting global stats"
    $request = CallAPI -Path "/info.xml"
    $stats = ([xml]$request).infolist
    $LinaStats = [PSCustomObject]@{
        PSTypeName                  = 'LinaStats'
        ALNVersion                  = $stats.aln_version
        PercentDiskUse              = $stats.disk_use
        DedupRatio                  = $stats.dedup_ratio
        CompressionRatio            = $stats.cpr_ratio
        DiskAvailableGB             = [math]::Round(($stats.disk_avail)/(1024*1024*1024),2)

        DiskSavingGB                = [math]::Round(($stats.vol_protected-$stats.vol_stored)/(1024*1024*1024),1)
        VolumeProtectedGB           = [math]::Round(($stats.vol_protected)/(1024*1024*1024),1)
        VolumeRestoredGB            = [math]::Round(($stats.vol_restored)/(1024*1024*1024),1)
        VolumeStoredGB              = [math]::Round(($stats.vol_stored)/(1024*1024*1024),1)

        VolumeProtectedMB           = [math]::Round(($stats.vol_protected)/(1024*1024))
        VolumeRestoredMB            = [math]::Round(($stats.vol_restored)/(1024*1024))
        VolumeStoredMB              = [math]::Round(($stats.vol_stored)/(1024*1024))
    }
    Return $LinaStats
}

function Get-LinaAgent {
<#
.SYNOPSIS
Retrieves the agents on a Lina server.
.DESCRIPTION
Retrieves the agents on a Lina server. Returns a set of agents that correspond to the filter criteria provided
.INPUTS
None
.OUTPUTS
Array of LinaAgent Objects
.PARAMETER Name
Name of the agent(s) to retrieve. Wildcards can be used. For example : *DESKTOP*
.PARAMETER ID
ID of the agent to retrieve. For example : 134.
.EXAMPLE
Get-LinaAgent -Name "TEST"
Will retrieve the agent named "TEST" and display its properties.
.EXAMPLE
$agents_desktop = Get-LinaAgent -Name "*DESKTOP*"
Will retrieve all the agents with DESKTOP inside their names. The list returned can be passed to other functions.
.EXAMPLE
Get-LinaAgent -Name "TEST" | Remove-LinaAgent
Will get the agent named TEST then pass it to the next command for deletion.
#>

    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ParameterSetName="ByName",Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateRange(0,2147483647)]
        [int]$ID
    )
    Write-Verbose "Getting list of agents"
    $timestamp = LinaTimestamp
    $request = CallAPI -Path "/lst_clients.xml?timestamp=$timestamp&type=agent"
    $Items = ([xml]$request).HNConfig.ClientArray.Client
    $Tenants = Get-LinaTenant
    $LinaItems = @()
    foreach ($Item in $Items) {
        <# Filtering on ID or Name (only if provided) #>
        if ((!$Name -AND !$ID) -OR ( $Item.Name -like "$Name" -OR  $Item.ID -eq $ID)) {
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaAgent'
                Name                        = $Item.Name
                ID                          = $Item.ID
                Tenant                      = $Tenants | where-object {$_.TenantID -eq $Item.DomainID} | Select-Object -ExpandProperty Name
                AgentGroup                  = Translate($Item.AttachementArray.Attachement.Group.Name)
                Strategy                    = Translate($Item.ServiceProfile.Name)
                Protection                  = Translate($Item.DataProfile.Name)

                StrategyName                = $Item.ServiceProfile.Name
                ProtectionName              = $Item.DataProfile.Name

                TenantID                    = $Item.DomainID
                AgentGroupID                = $Item.AttachementArray.Attachement.Group.ID
                StrategyID                  = $Item.ServiceProfile.ID
                ProtectionID                = $Item.DataProfile.ID
            }
            $LinaItems += $CurrentLinaItem
        }
    }
    Return $LinaItems
}

function Get-LinaStrategy {
<#
.SYNOPSIS
Retrieves the strategies on a Lina server.
.DESCRIPTION
Retrieves the strategies on a Lina server. Returns a set of strategies that correspond to the filter criteria provided.
.INPUTS
None
.OUTPUTS
Array of LinaStrategy Objects
.PARAMETER Name
Optional : Name (label) of the strategy to retrieve. Wildcards can be used. For example : *STRAT*
.EXAMPLE
Get-LinaStrategy
Will retrieve all the strategies.
.EXAMPLE
Get-LinaStrategy -Name "*STRAT*"
Will retrieve all the strategies with STRAT inside their names.
.EXAMPLE
Get-LinaStrategy -Name "*STRAT*" | Select-Object -Property Name,RPOInMinutes
Displays the list of strategies and their configured RPO in minutes.
#>

    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName="Name")]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )    
    Write-Verbose "Getting list of strategies"
    $timestamp = LinaTimestamp
    $request = CallAPI -Path "/lst_serviceprofiles.xml?timestamp=$timestamp"
    $Items = ([xml]$request).HNConfig.ServiceProfileArray.ServiceProfile
    $LinaItems = @()
    foreach ($Item in $Items) {
        if (!$Name -OR $Item.name -like "$Name") {        
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaStrategy'
                Label                       = Translate($Item.Name)
                ID                          = $Item.ID
                Name                        = $Item.Name
                RPOInMinutes                = ($Item.ParamSchedule/60)
                RetentionInDays             = $Item.ParamDataAging
                AlertAfterDays              = ($Item.ParamAlertTime/(24*3600))
                ThroughputLimitKBps         = $Item.ParamThroughputLimit
                ReplicationTargets          = $Item.RepliTarget.ID
                Encryption                  = [bool]$Item.ParamEncryptionActivated
                WanMode                     = [bool]$Item.ParamWanActivated
                CompressionAlgo             = $Item.ParamCompression
                AllowClientRPO              = [bool]$Item.ParamAllowClientRPO
                AllowClientRules            = [bool]$Item.ParamAllowClientRules
                AllowClientPause            = [bool]$Item.ParamAllowClientPause
                AllowClientBoost            = [bool]$Item.ParamAllowClientBoost
                AllowClientNetworkParams    = [bool]$Item.ParamAllowClientNetworkParams
                AllowWebAccess              = [bool]$Item.ParamAllowWebAccess
                QuotaMaxProtSizeMB          = [math]::Round(($Item.ParamQuotaMaxProtSize)/(1024*1024))
                QuotaMaxProtObjs            = $Item.ParamQuotaMaxProtObjs
                QuotaMaxHistSizeMB          = [math]::Round(($Item.ParamQuotaMaxHistSize)/(1024*1024))
                QuotaMaxHistObjs            = $Item.ParamQuotaMaxHistObjs
            }
            $LinaItems += $CurrentLinaItem
        }
    }
    Return $LinaItems
}

function Get-LinaTenant {
<#
.SYNOPSIS
Retrieves the tenants (entities) on a Lina server.
.DESCRIPTION
Retrieves the tenants (entities) on a Lina server. Returns a set of tenants that correspond to the filter criteria provided.
.INPUTS
None
.OUTPUTS
Array of LinaTenant Objects
.PARAMETER Name
Optional : Name of the tenant to retrieve. Wildcards can be used. For example : *Tenant*
.PARAMETER ID
Optional : ID of the tenant to retrieve.
.EXAMPLE
Get-LinaTenant
Retrieve all tenants
.EXAMPLE
Get-LinaTenant -Name "BaasCustomer1"
Retrieve Tenant named BaasCustomer1
.EXAMPLE
Get-LinaTenant | where {$_.IsDefault}
Retrieve the default tenant.
#>

    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateNotNullOrEmpty()]
        [int]$ID
    )
    Write-Verbose "Getting list of tenants"
    $timestamp = LinaTimestamp
    $request = CallAPI -Path "/ADM/list_domain.json?timestamp=$timestamp"
    $Items = $request.domains
    $LinaItems = @()
    foreach ($Item in $Items) {
        if ((!$Name -AND !$ID) -OR $Item.name -like "$Name" -OR $Item.ID -eq $ID ) {
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaTenant'
                TenantID                    = $Item.id
                UUID                        = $Item.uuid
                Name                        = $Item.name
                Comment                     = $Item.comment
                IsDefault                   = [bool]$Item.def_domain
            }
            $LinaItems += $CurrentLinaItem
        }
        
    }
    Return $LinaItems
}

function Get-LinaAgentStats {
<#
.SYNOPSIS
Retrieves the agents statistics and advanced information on a Lina server.
.DESCRIPTION
Retrieves the agents statistics and advanced information on a Lina server. Statistics are only available for agent that have been online at least one time.
.INPUTS
None
.OUTPUTS
Array of LinaAgentInfos Objects
.PARAMETER Name
Name of the agent(s) to retrieve. Wildcards can be used. For example : *DESKTOP*
.PARAMETER ID
ID of the agent to retrieve. For example : 134.
.EXAMPLE
Get-LinaAgentStats
Retrieve all agent stats
.EXAMPLE
Get-LinaAgentStats | where {$_.LastLogon -like "*mike*"}
Retrieve agent info on agents where mike is logged in.
.EXAMPLE
Get-LinaAgentStats | where {$_.LastCompletedSession -le (Get-Date).AddDays(-7)}
Retrieve agent info on agents with no completed backups in last 7 days
#>

    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ValueFromPipeline)]
        [pscustomobject]$lina_agent,
        [Parameter(ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateRange(0,2147483647)]
        [int]$ID
    )
    Write-Verbose "Getting agents Statistics (only available if has been online)"
    $timestamp = LinaTimestamp
    $request = CallAPI -Path "/stats_clients.xml?timestamp=$timestamp"
    $Items = ([xml]$request).Stats.ClientArray.Client
    $LinaItems = @()
    foreach ($Item in $Items) {
        <# Filtering on ID or Name (only if provided) #>
        if ((!$Name -AND !$ID -AND !$lina_agent) -OR ($Item.AdminName -like "$Name" -OR  $Item.AdminID -eq $ID -OR $lina_agent.ID -eq $Item.AdminID ) ) {
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaAgentInfos'
                Name                        = $Item.AdminName
                ID                          = $Item.AdminID              
                ComputerName                = $Item.ComputerName
                System                      = $Item.System
                AgentVersion                = $Item.AgentVersion
                Strategy                    = Translate($Item.AdminServiceProfileName)
                Protection                  = Translate($Item.AdminDataProfileName)
                LastLogon                   = $Item.LastLogon
                Alert                       = $Item.Alert

                LastStartedSession          = LinaToLocalTime $Item.LastStartedSession
                LastCompletedSession        = LinaToLocalTime $Item.LastCompletedSession
                LastConnectionTime          = LinaToLocalTime $Item.LastConnectionTime
                LastSyncTime                = LinaToLocalTime $Item.LastSyncTime
                
                StrategyAdminName           = $Item.AdminServiceProfileName
                ProtectionAdminName         = $Item.AdminDataProfileName
            }
            $LinaItems += $CurrentLinaItem
        }
        
    }
    Return $LinaItems
}


function Get-LinaAgentGroup {
<#
.SYNOPSIS
Retrieves the agent groups
.DESCRIPTION
Retrieves the agent groups (Main view of agents)
.INPUTS
None
.OUTPUTS
Array of LinaAgentGroup Objects
.PARAMETER Name
Name of the Group to retrieve. Wildcards can be used. For example : *UNCATEGOR*
.PARAMETER ID
ID of the group retrieve. For example : 4.
.PARAMETER TenantName
Name of the Tenant hosting the groups
Can also be used in conjunction with -Name to filter by name AND Tenant.
Can be useful if multiple groups have the same name in multiple tenants.
.EXAMPLE
Get-LinaAgentGroup
Retrieve all agent groups
.EXAMPLE
Get-LinaAgentGroup -ID 131
Retrieve agent group by it ID. Will return a unique object.
.EXAMPLE
Get-LinaAgentGroup -Name "Group1"
Retrieve agent group by its Name. Wildcards can be used.
Warning : result may not be unique even without using wildcards. Group names may not be unique across tenants.
.EXAMPLE
Get-LinaAgentGroup -TenantName "MyTenant"
Retrieve agent groups of the Tenant MyTenant
.EXAMPLE
Get-LinaAgentGroup -Name "Group1" -TenantName "BaasCustomer1"
Filter agent group by name and by Tenant MyTenant. If not using wildcards in name, return will be unique.
#>

        [cmdletbinding(DefaultParameterSetName='ByNothing')]
        Param(
            [Parameter(Mandatory=$true,ParameterSetName="ByName")]
            [Parameter(Mandatory=$True,ParameterSetName="ByNameAndTenant")]
            [Parameter(Mandatory=$false,ParameterSetName="ByNothing")]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
            [Parameter(Mandatory=$true,ParameterSetName="ByID")]
            [ValidateRange(0,2147483647)]
            [int]$ID,
            [Parameter(Mandatory=$True,ParameterSetName="ByTenant")]
            [Parameter(Mandatory=$True,ParameterSetName="ByNameAndTenant")]
            [Parameter(Mandatory=$false,ParameterSetName="ByNothing")]
            [ValidateNotNullOrEmpty()]
            [string]$TenantName
        )
        if ($TenantName) {
            $TenantID=(Get-LinaTenant -Name $TenantName).TenantID
            Write-Verbose "Getting agent groups for Tenant $TenantName (ID $TenantID)"
        }else {
            Write-Verbose "Getting agent groups"
        }
        
        $timestamp = LinaTimestamp
        $request = CallAPI -Path "/lst_hierarchies.xml?timestamp=$timestamp"
        $Items = ([xml]$request).HNConfig.HierarchyArray.Hierarchy.GroupArray.Group
        $LinaItems = @()
        foreach ($Item in $Items) {
            # PreFiltering By Tenant (Groups in tenant 0 are for all Tenants, example : Unsorted agents)
            if (!$TenantID -OR $Item.DomainID -eq $TenantID -OR $Item.DomainID -eq 0 ) {
            <# Filtering on ID or Name (only if provided) #>
                $name_translation = Translate($Item.Name) 
                if ((!$Name -AND !$ID) -OR ( $name_translation -like "$Name" -OR  $Item.ID -eq $ID) ) {
                    $CurrentLinaItem = [PSCustomObject]@{
                        PSTypeName                  = 'LinaAgentGroup'
                        Name                        = $name_translation
                        ID                          = $Item.ID
                        TenantID                    = $Item.DomainID
                        NbAgents                    = $Item.NbRefs 
                        InternalName                = $Item.Name 
                    }
                    $LinaItems += $CurrentLinaItem
                }
            }
            
        }
        Return $LinaItems
}


function New-LinaAgent {
<#
.SYNOPSIS
Creates a new agent.
.DESCRIPTION
Creates a new agent with the provided name.
.INPUTS
None
.OUTPUTS
LinaAgent Object
.PARAMETER Name
Name of the agent to create
.PARAMETER TenantName
Optional : Name of the tenant where the agent should be created.
If not provided, agent will be created in the current tenant.
In global view (no tenant selected), agent will be created in the default tenant.
.EXAMPLE
New-LinaAgent -Name "TEST-CREATE"
Creates a new agent in current tenant.
.EXAMPLE
New-LinaAgent -Name "TEST-CREATE2" -TenantName "MyTenant"
Creates a new agent in tenant MyTenant
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,ParameterSetName="Name",Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(Mandatory=$false,ParameterSetName="Name",Position=1)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName        
    )
    Write-Host "Creating a Lina agent named $Name"
    $current_tenant = Get-LinaCurrentTenant 6>$null
    if (!$TenantName) {
        if (!$current_tenant -OR $current_tenant.TenantID -le 1) {
            # No current tenant (global view) => using default tenant
            $domain =  Get-LinaTenant 6>$null | Where-Object {$_.IsDefault} 
            $domain_id=[int]$domain.TenantID
            $domain_name=[string]$domain.Name
            Write-Host "No tenant selected, agent will be created in default tenant $domain_name (ID $domain_id)"
        } else {
            # Using Current tenant
            $domain =  Get-LinaCurrentTenant 6>$null
            $domain_id=[int]$domain.TenantID
            $domain_name=[string]$domain.Name            
            Write-Host "No tenant selected, agent will be created in current tenant $domain_name (ID $domain_id)"
        }
    }else {
        $domain =  Get-LinaTenant 6>$null | Where-Object {$_.Name -eq $TenantName}
        $domain_id=[int]$domain.TenantID
        $domain_name=[string]$domain.Name
        if ( !($domain_id -gt 0) ) {
            Write-Host "Error : No tenant found with name $TenantName. Please input the exact name of the Tenant"
            Return
        }
    }
    
    Set-LinaCurrentTenant -ID $domain_id 6>$null

    $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>ADD</Func><Client><Name>'+$Name+'</Name></Client></HNConfig>'
    $request = CallAPI -Path "/mng_client.html" -ContentType "application/xml" -Body $body
    
    # Setting back the current tenant to what it was before the New-LinaAgent command
    $current_tenant | Set-LinaCurrentTenant 6>$null
    if ($request -like "*[0]*") {
        Write-Host "Agent $Name successfully created in Tenant $domain_name"
        $agent_created = Get-LinaAgent -Name $Name
        Return $agent_created
    } else {
        Write-Error "Error occurred trying to create agent $Name : \$request\"
        Return $null
    }    
}

function New-LinaAgentGroup {
    <#
    .SYNOPSIS
    Creates a new agent group
    .DESCRIPTION
    Creates a new agent group with the provided name.
    .INPUTS
    None
    .OUTPUTS
    LinaAgentGroup Object
    .PARAMETER Name
    Name of the agent group to create
    .PARAMETER TenantName
    Optional : Name of the tenant where the agent group should be created.
    If not provided, agent group will be created in the current tenant.
    In global view (no tenant selected), agent group will be created in the default tenant.
    .EXAMPLE
    New-LinaAgentGroup -Name "Test-Group"
    Creates a new agent group in current tenant.
    .EXAMPLE
    New-LinaAgent -Name "Test-Group" -TenantName "MyTenant"
    Creates a new agent group in tenant MyTenant
    #>

        [cmdletbinding()]
        Param(
            [Parameter(Mandatory=$true,ParameterSetName="Name",Position=0)]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
            [Parameter(Mandatory=$false,ParameterSetName="Name",Position=1)]
            [ValidateNotNullOrEmpty()]
            [string]$TenantName        
        )
        Write-Host "Creating a Lina agent group named $Name"
        $current_tenant = Get-LinaCurrentTenant 6>$null
        if (!$TenantName) {
            if (!$current_tenant -OR $current_tenant.TenantID -le 1) {
                # No current tenant (global view) => using default tenant
                $domain =  Get-LinaTenant 6>$null | Where-Object {$_.IsDefault} 
                $domain_id=[int]$domain.TenantID
                $domain_name=[string]$domain.Name
                Write-Host "No tenant selected, agent group will be created in default tenant $domain_name (ID $domain_id)"
            } else {
                # Using Current tenant
                $domain =  Get-LinaCurrentTenant 6>$null
                $domain_id=[int]$domain.TenantID
                $domain_name=[string]$domain.Name            
                Write-Host "No tenant selected, agent group will be created in current tenant $domain_name (ID $domain_id)"
            }
        }else {
            $domain =  Get-LinaTenant 6>$null | Where-Object {$_.Name -eq $TenantName}
            $domain_id=[int]$domain.TenantID
            $domain_name=[string]$domain.Name
            if ( !($domain_id -gt 0) ) {
                Write-Host "Error : No tenant found with name $TenantName. Please input the exact name of the Tenant"
                Return
            }
        }
        
        Set-LinaCurrentTenant -ID $domain_id 6>$null
        # Needs to clarify if ID of hierarchy can be anything else than 1 or not (<ID>1</ID>)
        $body ='data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>ADDG</Func><Hierarchy><ID>1</ID><Group><Name>'+$Name+'</Name></Group></Hierarchy></HNConfig>'
        $request = CallAPI -Path "/mng_hierarchy.html" -ContentType "application/xml" -Body $body
        
        
        # Setting back the current tenant to what it was before the New-LinaAgentGroup command
        $current_tenant | Set-LinaCurrentTenant 6>$null
        if ($request -like "*[0]*") {
            Write-Host "Agent group $Name successfully created in Tenant $domain_name (ID $domain_id)"
            $agent_created = Get-LinaAgentGroup -Name $Name -TenantName $domain_name
            Return $agent_created
        } else {
            Write-Error "Error occurred trying to create agent group $Name : \$request\"
            Return $null
        }    
    }

function Get-LinaCurrentTenant {
<#
.SYNOPSIS
Get the current tenant
.DESCRIPTION
Get the current tenant. If current tenant is the Global View (no tenant selected), it will return 1 or -1.
.INPUTS
None
.OUTPUTS
Current tenant ID
.EXAMPLE
$current_tenant_id = Get-LinaCurrentTenant
Get the current tenant ID.
#>

    Write-Verbose "Getting current tenant"
    $timestamp = LinaTimestamp
    $request = CallAPI -Path "/ADE/check_session.xml?timestamp=$timestamp"
    $current_tenant_id = [int](([xml]$request).session.effective_domain_id)
    if ($current_tenant_id -eq -1 -OR $current_tenant_id -eq 1) {
        Write-Host "No current tenant selected (ID = -1 or 1). Global view on all tenants."
        Return $current_tenant_id
    }elseif ($current_tenant_id -gt 1) {
        $current_tenant = Get-LinaTenant -ID $current_tenant_id
        $current_tenant_name = $current_tenant.Name 
        Write-Host "Current tenant is $current_tenant_name (ID $current_tenant_id)"
        Return $current_tenant
    } else {
        Write-Host "Error occurred trying to get current tenant : \$request\"
        Return $null
    }
}

function Set-LinaCurrentTenant {
<#
.SYNOPSIS
Set the current tenant
.DESCRIPTION
Set the current tenant. When set, all listings and actions will be executed and filtered inside this tenant.
To set the global view (view across all tenants), use the -All switch.
.INPUTS
LinaTenant Object
.OUTPUTS
None
.PARAMETER ID
ID of the tenant to set
.PARAMETER All
Switch to set the global view (view across all tenants). Available for superadmin only.
.EXAMPLE
Get-LinaTenant -Name "MyTenant" | Set-LinaCurrentTenant
Set the current tenant to MyTenant
.EXAMPLE
Set-LinaCurrentTenant -All
Enable global view across all tenants (superadmin only)
.EXAMPLE
Set-LinaCurrentTenant -ID 2
Set the current tenant to Tenant with ID 2
#>

    [cmdletbinding(DefaultParameterSetName="ByID")]
    Param(
        [Parameter(ValueFromPipeline,ParameterSetName="ByObject")]
        [pscustomobject]$lina_tenant,
        [Parameter(Mandatory=$true,ParameterSetName="ByID")]
        [ValidateNotNullOrEmpty()]
        [string]$ID,
        [Parameter(Mandatory=$true,ParameterSetName="All")]
        [ValidateNotNullOrEmpty()]
        [switch]$All        
    )
    $global_view=""
    # All tenants is 1 or -1 (no tenant selected, global view)
    if ($All) {
        $ID=1
        $tenant_name="Global View / All Tenants"
    }elseif ($lina_tenant -AND $lina_tenant.TenantID -ge 1) {
        $ID=$lina_tenant.TenantID
        $tenant_name=$lina_tenant.Name
    }elseif (!$ID) {
        $ID=1
        $tenant_name="Global View / All Tenants"
    }
    Write-Verbose "Setting current tenant to Tenant ID $ID"
    $request = CallAPI -Path "/ADM/domain_set_current.json?domain_id=$ID"
    if ($request.status -eq 0) {
        Write-Host "Current tenant has been set to $tenant_name (ID $ID)"
        Return
    } else {
        Write-Error "Error occurred trying to set current tenant : \$request\"
        Return
    }
}

function Remove-LinaAgent {
<#
.SYNOPSIS
Deletes a Lina Agent from a Lina Server.
.DESCRIPTION
Deletes a Lina Agent. Unique data will be reclaimed automatically by server after some time (configurable).
.INPUTS
Accept pipelining of LinaAgent objects (from Get-LinaAgent for example)
.OUTPUTS
None
.PARAMETER Name
Name of the agent to delete. Wildcards are accepted (don't forget to enable Bulk mode for actions on multiple agents).
.PARAMETER WhatIf
No actual deletion will happen if set. It will only display what agents would be deleted.
Sometimes also called "Dry Run mode"
.PARAMETER Bulk
Security parameter to enable deletion of multiple agents. If not set only one agent will be deleted.
.EXAMPLE
Remove-LinaAgent -Name "AGENT132"
Real deletion of a single agent by name
.EXAMPLE
Get-LinaAgent -Name "AGENT132" | Remove-LinaAgent
Same example as #1 but using piped commands
.EXAMPLE
Get-LinaAgent -Name "AGENT1*" | Remove-LinaAgent -Bulk -WhatIf
Simulates deletion of a multiple agents by name (WhatIf mode)
.EXAMPLE
Get-LinaAgent -Name "AGENT1*" | Remove-LinaAgent -Bulk
Real deletion of a multiple agents by name
.EXAMPLE
Get-LinaAgent -ID 164 | Remove-LinaAgent -WhatIf
Simulates deletion of a single agent by ID
#>

    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ValueFromPipeline=$True,Mandatory=$True,ParameterSetName="ByPipeline")]
        [pscustomobject[]]$LinaAgents,

        [Parameter(Mandatory=$True,ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        # Do not delete, only show will be done
        [Parameter(Mandatory=$False,ParameterSetName="ByPipeline")]
        [Parameter(Mandatory=$False,ParameterSetName="ByName")]
        [switch]$WhatIf,

        # Enable deleting multiple agents
        [Parameter(Mandatory=$False,ParameterSetName="ByPipeline")]
        [Parameter(Mandatory=$False,ParameterSetName="ByName")]        
        [switch]$Bulk
    )
    BEGIN {
        $nb_of_agents_deleted=0
        if ($Name) {
            $LinaAgents = Get-LinaAgent -Name $Name
        }
    }
    PROCESS {
        foreach ($LinaAgent in $LinaAgents) {
            # Should be useless now
            If(!$LinaAgent.ID) { return }

            $Name = $LinaAgent.Name
            $delete_id = $LinaAgent.ID

            if ($nb_of_agents_deleted -ge 1 -AND !$Bulk) {
                Write-Host "WARNING : Bulk mode has not been enabled. Agent $Name will not be deleted."
                return
            }

            
            if (!$delete_id) {
                Write-Host "WARNING : No agent found with this name."
                return
            }else {
                $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>DEL</Func><Client><ID>'+$delete_id+'</ID></Client></HNConfig>'
                if (!$WhatIf) {
                    #$request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
                    $request = CallAPI -Path "/mng_client.html" -ContentType "application/xml" -Body $body 
                }else {
                    # WhatIf mode enabled => does not delete agent
                    $request = "WHATIF"
                }
                if ([string]$request -like "*[0]*") {
                    Write-Host "Agent $Name successfully deleted."
                } elseif ($request -eq "WHATIF") {
                    Write-Host "Agent $Name will be deleted if not using 'WhatIf' mode"
                }else {
                    Write-Error "Error occurred trying to delete agent $Name : \$request\"
                }
            }
            $nb_of_agents_deleted +=1
 
        }
    }
    END {}

}
function Remove-LinaAgentGroup {
    <#
    .SYNOPSIS
    Deletes a Lina Agent Group from a Lina Server.
    .DESCRIPTION
    Deletes a Lina Agent Group. Agents in this group will go to the "unsorted" group.
    .INPUTS
    Accept pipelining of LinaAgentGroup objects (from Get-LinaAgentGroup for example)
    .OUTPUTS
    None
    .PARAMETER Name
    Name of the agent group to delete. Wildcards are accepted (don't forget to enable Bulk mode for actions on multiple groups).
    .PARAMETER WhatIf
    No actual deletion will happen if set. It will only display what agent groups would be deleted.
    Sometimes also called "Dry Run mode"
    .PARAMETER Bulk
    Security parameter to enable deletion of multiple agent group. If not set only one group will be deleted.
    .EXAMPLE
    Remove-LinaAgentGroup -Name "Group132"
    Real deletion of a single agent group by name
    .EXAMPLE
    Get-LinaAgentGroup -Name "Group132" | Remove-LinaAgentGroup
    Same example as #1 but using piped commands
    .EXAMPLE
    Get-LinaAgentGroup -Name "Group1*" | Remove-LinaAgentGroup -Bulk -WhatIf
    Simulates deletion of a multiple agent groups by name (WhatIf mode)
    .EXAMPLE
    Get-LinaAgentGroup -Name Group1*" | Remove-LinaAgentGroup -Bulk
    Real deletion of a multiple agent groups by name
    .EXAMPLE
    Get-LinaAgentGroups -ID 164 | Remove-LinaAgentGroups -WhatIf
    Simulates deletion of a single agent group by ID
    #>

        [cmdletbinding(DefaultParameterSetName="ByName")]
        Param(
            [Parameter(ValueFromPipeline=$True,Mandatory=$True,ParameterSetName="ByPipeline")]
            [pscustomobject[]]$LinaAgentGroups,
    
            [Parameter(Mandatory=$True,ParameterSetName="ByName")]
            [ValidateNotNullOrEmpty()]
            [string]$Name,
    
            # Do not delete, only show will be done
            [Parameter(Mandatory=$False,ParameterSetName="ByPipeline")]
            [Parameter(Mandatory=$False,ParameterSetName="ByName")]
            [switch]$WhatIf,
    
            # Enable deleting multiple groups
            [Parameter(Mandatory=$False,ParameterSetName="ByPipeline")]
            [Parameter(Mandatory=$False,ParameterSetName="ByName")]        
            [switch]$Bulk
        )
        BEGIN {
            $nb_of_agentgroups_deleted=0
            if ($Name) {
                $LinaAgentGroups = Get-LinaAgentGroup -Name $Name
            }
        }
        PROCESS {
            foreach ($LinaAgentGroup in $LinaAgentGroups) {
                # Should be useless now
                If(!$LinaAgentGroup.ID) { return }
    
                $Name = $LinaAgentGroup.Name
                $delete_id = $LinaAgentGroup.ID
                
                If($LinaAgentGroup.InternalName -eq "UNCAT") {
                    Write-Host "WARNING : The default agent group ($Name) cannot be removed."
                    return
                }

                if ($nb_of_agentgroups_deleted -ge 1 -AND !$Bulk) {
                    Write-Host "WARNING : Bulk mode has not been enabled. Agent group $Name will not be deleted."
                    return
                }
    
                
                if (!$delete_id) {
                    Write-Host "WARNING : No agent group found with this name."
                    return
                }else {
                    $body='data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>DELG</Func><Hierarchy><ID>1</ID><Group><ID>'+$delete_id+'</ID></Group></Hierarchy></HNConfig>'
                    if (!$WhatIf) {
                        #$request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
                        $request = CallAPI -Path "/mng_hierarchy.html" -ContentType "application/xml" -Body $body 
                    }else {
                        # WhatIf mode enabled => does not delete agent
                        $request = "WHATIF"
                    }
                    if ([string]$request -like "*[0]*") {
                        Write-Host "Agent group $Name successfully deleted."
                    } elseif ($request -eq "WHATIF") {
                        Write-Host "Agent group $Name will be deleted if not using 'WhatIf' mode"
                    }else {
                        Write-Error "Error occurred trying to delete agent group $Name : \$request\"
                    }
                }
                $nb_of_agentgroups_deleted +=1
     
            }
        }
        END {}
}
    
function Set-LinaAgent {
<#
.SYNOPSIS
Changes the configuration of an agent.
.DESCRIPTION
Changes the configuration of an agent for example its name.
.INPUTS
Array of LinaAgent Objects
.OUTPUTS
The modified LinaAgent Objects
.PARAMETER Name
New name for the agent. If multiple agent are passed, only the first one will be renamed as names have to be unique.
.PARAMETER TenantName
Move the agent to this tenant.
Cannot be used with the -Name argument.
.PARAMETER GroupName
Move the agent to this agent group.
Note : Agent group has to be in th same tenant as the agent.
Cannot be used with the -Name argument or -TenantName
.EXAMPLE
Get-LinaAgent -Name "TEST1" | Set-LinaAgent -Name "TEST2"
Changes the name of the agent from TEST1 to TEST2
.EXAMPLE
Get-LinaAgent -Name "TEST1" | Set-LinaAgent -TenantName "MyTenant"
Moves the agent TEST1 to the Tenant MyTenant
.EXAMPLE
Get-LinaAgent -Name "TEST1" | Set-LinaAgent -GroupName "MyGroup"
Moves the agent TEST1 to the agent group MyGroup in the same tenant as the agent
.EXAMPLE
Get-LinaAgent -Name "TEST1" | Set-LinaAgent -TenantName "MyTenant" | Set-LinaAgent -GroupName "MyGroup"
Moves the agent TEST1 to the tenant MyTenant then to the agent group MyGroup
#>

    [cmdletbinding(DefaultParameterSetName='UpdateName')]
    Param(
        [Parameter(ValueFromPipeline=$True,Mandatory=$true,ParameterSetName="UpdateName")]
        [Parameter(ValueFromPipeline=$True,Mandatory=$true,ParameterSetName="UpdateTenant")]
        [Parameter(ValueFromPipeline=$True,Mandatory=$true,ParameterSetName="UpdateGroup")]
        [pscustomobject[]]$LinaAgents,
        [Parameter(Mandatory=$true,ParameterSetName="UpdateName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(Mandatory=$true,ParameterSetName="UpdateTenant")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,
        [Parameter(Mandatory=$true,ParameterSetName="UpdateGroup")]
        [ValidateNotNullOrEmpty()]
        [string]$GroupName
    )
    BEGIN {
        $lina_agents_processed=0
    }
    PROCESS {

        foreach($LinaAgent in $LinaAgents) {
            $agent_name = $LinaAgent.Name
            $agent_id = $LinaAgent.ID
            $agent_tenantname = $LinaAgent.Tenant
            
            if ($Name) {
                # Renaming Agent => can only be done on one agent at a time
                if ($lina_agents_processed -ge 1) {
                    Write-Host "WARNING : You cannot rename multiple agents with the same name. Agent $agent_name will be ignored."
                    Return
                }else {
                    Write-Host "Renaming Lina agent named $agent_name to new name $Name"
                }
                $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>REN</Func><Client><ID>'+$agent_id+'</ID><Name>'+$Name+'</Name></Client></HNConfig>'
                $lina_agents_processed++
            }elseif ($TenantName) {
                # Moving to new tenant
                $tenant_id = (Get-LinaTenant -Name $TenantName).TenantID
                if ($tenant_id -le 1) { 
                    Write-Error "ERROR : No tenant found with this name."
                    Return
                }
                $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>MOV</Func><Client><ID>'+$agent_id+'</ID><DomainID>'+$tenant_id+'</DomainID><Name>@auto</Name></Client></HNConfig>'
                Write-Host "Moving Lina agent named $agent_name to Tenant $TenantName"
            }else {
                # Moving to group, filtering to group in the same tenant as the agent to move
                $group_id = (Get-LinaAgentGroup -Name $GroupName -TenantName $agent_tenantname).ID 
                if ($group_id -le 0) { 
                    Write-Error "ERROR : No group found with this name in the same tenant as the agent."
                    Return
                }
                $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>SETG</Func><Client><ID>'+$agent_id+'</ID><AttachementArray><Attachement><HierarchyID>1</HierarchyID><GroupID>'+$group_id+'</GroupID></Attachement></AttachementArray></Client></HNConfig>'
                Write-Host "Moving Lina agent named $agent_name to agent group $GroupName"
            }

            $timestamp = LinaTimestamp
            #$request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html?timestamp=$timestamp" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
            $request = CallAPI -Path "/mng_client.html?timestamp=$timestamp" -ContentType "application/xml" -Body $body
            if ([string]$request -like "*[0]*") {
                if ($TenantName) {
                    Write-Host "Agent $agent_name successfully moved to Tenant $TenantName."
                }elseif ($Name) {
                    Write-Host "Agent $agent_name successfully renamed to $Name."
                }else {
                    Write-Host "Agent $agent_name successfully moved to agent group $GroupName in Tenant $agent_tenantname."
                }
                Return Get-LinaAgent -ID $agent_id
            } else {
                Write-Error "ERROR occurred trying to modify agent $Name : \$request\"
                Return
            }
        }
    }
    END{ }
}