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
    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
    )

    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()]
    Param(
        [Parameter(ParameterSetName="Name")]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )
    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 -OR $Item.name -like "$Name") {
            $CurrentLinaItem = [PSCustomObject]@{
                PSTypeName                  = 'LinaTenant'
                ID                          = $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]$Tenant        
    )
    Write-Host "Creating a Lina agent named $Name"
   
    if (!$Tenant) {
        <# Getting default Tenant infos #>
        $domain =  Get-LinaTenant | Where-Object {$_.IsDefault}
        $domain_id=[int]$domain.ID
        $domain_name=[string]$domain.Name
        Write-Host "No tenant selected, agent will be created in default tenant $domain_name (ID $domain_id)"
    }else {
        $domain =  Get-LinaTenant | Where-Object {$_.Name -eq $Tenant}
        $domain_id=[int]$domain.ID
        $domain_name=[string]$domain.Name
        if ( !($domain_id -gt 0) ) {
            Write-Host "Error : No tenant found with name $Tenant. 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
    if ([string]$request -like "*[0]*") {
        Write-Host "Agent $Name successfully created."
        Get-LinaAgent -Name $Name
        Return
    } else {
        Write-Host "Error occurred trying to create agent $Name : \$request\"
        Return
    }    
}

function Remove-LinaAgent {
    [cmdletbinding()]
    Param(
        [Parameter(ValueFromPipeline)]
        [pscustomobject]$lina_agent,
        [Parameter(ParameterSetName="Name")]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )
    if ($lina_agent) { $Name = $lina_agent.Name}
    Write-Host "Deleting Lina agent named $Name"

    
    # If Agent is provided as an object no need to search for an ID
    if (!$lina_agent) {
        $timestamp = LinaTimestamp
        $request = Invoke-RestMethod -Uri "$GLOBAL_LINA_SERVER/lst_clients.xml?timestamp=$timestamp" -ContentType "application/xml" -WebSession $LoggedSession
        $Items = ([xml]$request).HNConfig.ClientArray.Client
        foreach ($Item in $Items) {
            if ($Item.Name -eq $Name) {
                $delete_id=$Item.ID
                Write-Host "Found agent named $Name with ID $delete_id"
            }
        }
    } else {
        $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>'
        $request = Invoke-WebRequest -Uri "$GLOBAL_LINA_SERVER/mng_client.html" -Method "POST" -ContentType "application/xml" -Body $body -WebSession $LoggedSession
        if ([string]$request -like "*[0]*") {
            Write-Host "Agent $Name successfully deleted."
            Return
        } else {
            Write-Host "Error occurred trying to delete agent $Name : \$request\"
            Return
        }
    }
}

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
        }
    }
}