Atempo.Lina.psm1

<#
 .Synopsis
  This Windows PowerShell module contains PowerCLI Cloud Infrastructure Suite cmdlets.
 
 .Description
  Provide basic management of Lina Agents
 
 .Example
    # Getting list of agents
    Connect-LinaServer -Server "https://10.0.0.1:8181" -User "superadmin" -Password "MyPassword"
    Get-LinaAgent
    Disconnect-LinaServer
#>



<# TODO
    New-Lina Agent => use current tenant if not set
    Add an option for mass delete of agents and/or improve multiple objects in pipeline
    Set Silent returns in imbricated functions (see in New-LinaAgent for example)
    Get locales for default stragies names (locale/en.js for example)
    Ability to pipe commands
    Improved error reporting
#>


<# 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:GLOBAL_SKIP_CERT = ""
<# Disable Certificate checking for WebRequest (Pre-PowerShell 6.0) #>
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@

[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" 

<# Disable Certificate checking for WebRequest (PowerShell >= 6.0) #>
if ($PSVersionTable.PSVersion.Major -ge 6) {
    $GLOBAL_SKIP_CERT = "-SkipCertificateCheck"
} else {
    $GLOBAL_SKIP_CERT = ""
}

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 LinaTimestamp {
    return [math]::Round((New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date)).TotalSeconds*1000)
}
function Connect-LinaServer {
    [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)]
        [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"
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADE/login.html?user=$userenc&password=$passenc" -SessionVariable Currentsession
    Set-Variable -name LoggedSession -Scope global -Value $Currentsession 
    if ([string]$request -like "*- OK*") {
        Write-Host "Successfully connected."
    } else {
        Write-Host "Error occurred trying to connect : \$request\"
    }    
}

function Disconnect-LinaServer {
    Write-Host "Disconnecting from Lina server $GLOBAL_LINA_SERVER"
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADE/logout.json" -WebSession $LoggedSession
    if ([string]$request -like "*- OK*") {
        Write-Host "Successfully disconnected."
    } else {
        Write-Host "Error occurred trying to disconnect : \$request\"
    }
}

function Get-LinaGlobalStats {
    Write-Host "Getting global stats"
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/info.xml" -ContentType "application/xml" -WebSession $LoggedSession
    $stats = ([xml]$request).infolist
    $LinaStats = [PSCustomObject]@{
        PSTypeName                  = 'LinaStats'
        ALNVersion                  = $stats.aln_version
        PercentDiskUse              = $stats.disk_use
        DedupRatio                  = $stats.dedup_ratio
        DiskAvailableGB             = [math]::Round(($stats.disk_avail)/(1024*1024*1024),2)

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

        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 {
    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateRange(0,2147483647)]
        [int]$ID
    )
    Write-Host "Getting list of agents"
    $timestamp = LinaTimestamp
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/lst_clients.xml?timestamp=$timestamp&type=agent" -ContentType "application/xml" -WebSession $LoggedSession
    $Items = ([xml]$request).HNConfig.ClientArray.Client
    $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
                TenantID                    = $Item.DomainID
                Strategy                    = $Item.ServiceProfile.Name
                Protection                  = $Item.DataProfile.Name         
            }
            $LinaItems += $CurrentLinaItem
        }
    }
    Return $LinaItems
}

function Get-LinaStrategy {
    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName="Name")]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )    
    Write-Host "Getting list of strategies"
    $timestamp = LinaTimestamp
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/lst_serviceprofiles.xml?timestamp=$timestamp" -ContentType "application/xml" -WebSession $LoggedSession
    $Items = ([xml]$request).HNConfig.ServiceProfileArray.ServiceProfile
    $LinaItems = @()
    foreach ($Item in $Items) {
        if (!$Name -OR $Item.name -like "$Name") {        
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaStrategy'
                ID                          = $Item.ID
                Name                        = $Item.Name
                RPOInMinutes                = ($Item.ParamSchedule/60)
                RetentionInDays             = $Item.ParamDataAging
            }
            $LinaItems += $CurrentLinaItem
        }
    }
    Return $LinaItems
}

function Get-LinaTenant {
    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateNotNullOrEmpty()]
        [int]$ID
    )
    Write-Host "Getting list of tenants"
    $timestamp = LinaTimestamp
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADM/list_domain.json?timestamp=$timestamp" -ContentType "application/json" -WebSession $LoggedSession
    $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 {
    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ValueFromPipeline)]
        [pscustomobject]$lina_agent,
        [Parameter(ParameterSetName="ByName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="ByID")]
        [ValidateRange(0,2147483647)]
        [int]$ID
    )
    Write-Host "Getting agents info"
    $timestamp = LinaTimestamp
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/stats_clients.xml?timestamp=$timestamp" -ContentType "application/xml" -WebSession $LoggedSession
    $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                    = $Item.AdminServiceProfileName
                Protection                  = $Item.AdminDataProfileName
                LastLogon                   = $Item.LastLogon
                Alert                       = $Item.Alert
                LastStartedSession          = LinaToLocalTime $Item.LastStartedSession
                LastCompletedSession        = LinaToLocalTime $Item.LastCompletedSession
                LastConnectionTime          = LinaToLocalTime $Item.LastConnectionTime
                LastSyncTime                = LinaToLocalTime $Item.LastSyncTime           
            }
            $LinaItems += $CurrentLinaItem
        }
        
    }
    Return $LinaItems
}


function New-LinaAgent {
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,ParameterSetName="Name")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(Mandatory=$false,ParameterSetName="Name")]
        [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
        }
    }
    Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADM/domain_set_current.json?domain_id=$domain_id" -ContentType "application/json" -WebSession $LoggedSession | Out-Null
    
    $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>ADD</Func><Client><Name>'+$Name+'</Name></Client></HNConfig>'
    $request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
    $going_back = $current_tenant | Set-LinaCurrentTenant 6>$null 
    if ([string]$request -like "*[0]*") {
        Write-Host "Agent $Name successfully created."
        Return Get-LinaAgent -Name $Name
    } else {
        Write-Host "Error occurred trying to create agent $Name : \$request\"
        Return $null
    }    
}
function Get-LinaCurrentTenant {
    Write-Host "Getting current tenant"
    $timestamp = LinaTimestamp
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADE/check_session.xml?timestamp=$timestamp" -ContentType "application/xml" -WebSession $LoggedSession
    $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 (ID = -1 or 1). Global view on all tenants."
        Return $current_tenant_id
    }elseif ($current_tenant_id -gt 1) {
        Write-Host "Current tenant ID is $current_tenant_id"
        Return Get-LinaTenant -ID $current_tenant_id
    } else {
        Write-Host "Error occurred trying to get current tenant : \$request\"
        Return $null
    }
}
function Set-LinaCurrentTenant {
    [cmdletbinding(DefaultParameterSetName="ByName")]
    Param(
        [Parameter(ValueFromPipeline,ParameterSetName="ByObject")]
        [pscustomobject]$lina_tenant,
        [Parameter(Mandatory=$true,ParameterSetName="ByID")]
        [ValidateNotNullOrEmpty()]
        [string]$ID,
        [Parameter(Mandatory=$false,ParameterSetName="All")]
        [ValidateNotNullOrEmpty()]
        [switch]$All        
    )
    
    # All tenants is 1 or -1 (no tenant selected, global view)
    if ($All) {
        $ID=1
    }elseif ($lina_tenant -AND $lina_tenant.TenantID -ge 1) {
        $ID=$lina_tenant.TenantID
    }elseif (!$ID) {
        $ID=1
    }
    Write-Host "Setting current tenant to Tenant ID $ID"
    $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/ADM/domain_set_current.json?domain_id=$ID" -ContentType "application/json" -WebSession $LoggedSession
    if ($request.status -eq 0) {
        Write-Host "Current tenant has been set to tenant ID $ID"
        Return
    } else {
        Write-Host "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)
.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
Remove-LinaAgent -Name "AGENT1*" -Bulk -WhatIf
Simulates deletion of a multiple agents by name (WhatIf mode)
.EXAMPLE
Remove-LinaAgent -Name "AGENT1*" -Bulk
Real deletion of a multiple agents by name
.EXAMPLE
Get-LinaAgent -ID 164 | Remove-LinaAgent -WhatIf
Real deletion of a single agent by piping it from a Get
.EXAMPLE
Get-LinaAgent -Name "TEST1*" | Remove-LinaAgent -Bulk -WhatIf
Simulates deletion of multiple agents by piping them from a Get (WhatIf mode)
#>

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

        [Parameter(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 {
        if (!$LinaAgents) { 
            $LinaAgents = Get-LinaAgent -Name $Name 6>$null
        }
        $names=$LinaAgents.Name
        Write-Host "Deleting Lina agent(s) : $names"}
    PROCESS {
        $nb_of_agents_deleted=0
        <#
        if (!$LinaAgents) {
            $LinaAgents = Get-LinaAgent -Name $Name 6>$null
        }#>

         
        foreach ($lina_agent in $LinaAgents) {
           
            $Name = $lina_agent.Name
            $delete_id = $lina_agent.ID
            
            
            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
                }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-Host "Error occurred trying to delete agent $Name : \$request\"
                }
            }
            $nb_of_agents_deleted +=1
            if ($nb_of_agents_deleted -ge 1 -AND !$Bulk) {
                Write-Host "WARNING : Bulk mode has not been enabled, only one agent will be deleted for security."
                Return
            }
        }
    }
    END {}

}

function Set-LinaAgent {
    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName="UpdateName")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(ParameterSetName="UpdateName")]
        [ValidateNotNullOrEmpty()]
        [string]$Newname            
    )
    Write-Host "Renaming Lina agent named $Name to new name $Newname"
    $Items = Get-LinaAgent
    foreach ($Item in $Items) {
        if ($Item.Name -eq $Name) {
            $rename_id=$Item.ID
            Write-Host "Found agent named $Name with ID $rename_id"
        }
    }
    if (!$rename_id) {
        Write-Host "WARNING : No agent found with this name."
        return
    }else {
        $body = 'data=<?xml version="1.0" encoding="UTF-8"?><HNConfig><Func>REN</Func><Client><ID>'+$rename_id+'</ID><Name>'+$Newname+'</Name></Client></HNConfig>'
        $timestamp = LinaTimestamp
        $request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html?timestamp=$timestamp" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
        if ([string]$request -like "*[0]*") {
            Write-Host "Agent $Name successfully renamed to $Newname."
            Return
        } else {
            Write-Host "Error occurred trying to delete agent $Name : \$request\"
            Return
        }
    }
}