Public/New-AGMLibVMwareVMDiscovery.ps1

# Copyright 2022 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

<#
.SYNOPSIS
Uses specified vCenter ID, appliance ID or a pre-prepared CSV list of vCenter IDs, appliance IDs, to discover new VMWare VMs.

.DESCRIPTION
This routine needs a well formatted CSV file that contains applianceid and vcenterid
Note the column order is not important.
Here is an example of such a file:

applianceid,vcenterid
143112195179,2110151
143112195179,2110122
143112195179,2110114

To learn applianceid (clusterid), use Get-AGMAppliance

The default is to fetch 5 Instances at a time. You can change this with -limit.
You can also manually supply applianceid and vcenterid rather than using a CSV file

If sltid/sltname and sltname/slpname are not specified, it will default to -nobackup option.
-nobackup option is mutually exclusive with sltid/sltname or sltname/slpname
If -nobackup is specified then discovery will occur with no backup plans being applied.

If the following are specified in combination then all instances will have a backup plan applied to it:
-backup -sltname "<name1>" -slpname "<name2>"
-backup -sltid <slt ID learned with Get-AGMSLT> -slpid <slp ID learned with Get-AGMSLP>

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -discoveryfile discovery.csv -username user-01@abc.com

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -discoveryfile discovery.csv -nobackup -username user-01@abc.com

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -discoveryfile discovery.csv -sltid 2121511 -slpid 6006 -username user-01@abc.com -passfilepath '.vcenterpass'

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -discoveryfile discovery.csv -sltname LocalSnap -slpname LocalProfile -username user-01@abc.com -passfilepath '.vcenterpass'

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -applianceid 143112195179 -vcenterid 2110151 -username user-01@abc.com -passfilepath '.vcenterpass'

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -applianceid 143112195179 -vcenterid 2110151 -nobackup -username user-01@abc.com -passfilepath '.vcenterpass'

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -applianceid 143112195179 -vcenterid 2110151 -sltid 2121511 -slpid 6006 -username user-01@abc.com -passfilepath '.vcenterpass'

.EXAMPLE
New-AGMLibVMwareVMDiscovery -vmtag mytag -applianceid 143112195179 -vcenterid 2110151 -sltname LocalSnap -slpname LocalProfile -username user-01@abc.com -passfilepath '.vcenterpass'
#>

function New-AGMLibVMwareVMDiscovery {
    [CmdletBinding()]
    param (
        # The user name for logging into the vCenter
        [Parameter(Mandatory = $true)]
        [string]
        $username,

        # File that saves the encrypted password
        [Parameter(Mandatory = $false)]
        [string]
        $passfilepath,

        # The file path for the discovery file.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileNoBackup')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseName')]
        [string]
        $discoveryfile,

        # The applianceid attribute of an appliance.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceNoBackup')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseName')]
        [string]
        $applianceid,

        # The id of the vCenter host.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceNoBackup')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseName')]
        [string]
        $vcenterid,
        
        # Mutually exclusive with -backup option, this option is enabled by default.
        # Enable this option will not apply SLAs to the new applications.
        [Parameter(Mandatory = $false, ParameterSetName = 'UseDiscoveryFileNoBackup')]
        [Parameter(Mandatory = $false, ParameterSetName = 'UseSpecifiedApplianceNoBackup')]
        [switch]
        $nobackup,

        # Mutually exclusive with -nobackup option, this option has to be enabled explicitly.
        # Enable this option will apply SLAs to the new applications.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseName')]
        [switch]
        $backup,

        # The VM tag that want to discover in the vCenter.
        [Parameter(Mandatory = $true)]
        [string]
        $vmtag,

        # The id of the SLT
        # Mutually exclusive with -sltname and -slpname options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseId')]
        [int]
        $sltid,

        # The id of the SLP
        # Mutually exclusive with -sltname and -slpname options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseId')]
        [int]
        $slpid,

        # The name of the SLT
        # Mutually exclusive with -sltid and -slpid options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseName')]
        [string]
        $sltname,

        # The name of the SLP
        # Mutually exclusive with -sltid and -slpid options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseDiscoveryFileBackupUseName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'UseSpecifiedApplianceBackupUseName')]
        [string]
        $slpname,

        # The maximum number of VMs will be affected at the same time.
        # Default value is 5
        [Parameter(Mandatory = $false)]
        [int]
        $parallelism = 5,

        # Timeout (seconds) for waiting applications being created.
        # Default value is 600 (seconds)
        # You may need to increase this value when you have numerous VMs with the tag.
        [Parameter(Mandatory = $false)]
        [int64]
        $timeout = 600
    )

    if ( (!($AGMSESSIONID)) -or (!($AGMIP)) ) {
        Get-AGMErrorMessage -messagetoprint "Not logged in or session expired. Please login using Connect-AGM"
        return
    }

    Write-Verbose "$(Get-Date) Starting New-AGMLibVMwareVMDiscovery function`n"

    $nobackup = $backup ? $false : $true

    $session_test = Get-AGMVersion
    if ($session_test.errormessage) {
        $session_test
        return
    }

    Write-Verbose "$(Get-Date) Session test passed`n"

    if ($discoveryfile) {
        if ( Test-Path -Path $discoveryfile ) {
            Write-Output "Reading applianceid and vcenterid list from the discovery file.`n"
            $search_list = Import-Csv -Path $discoveryfile
        }
        else {
            Get-AGMErrorMessage -messagetoprint "The specified discovery file does not exist.`n"
            return
        }
    }
    else {
        Write-Output "Using the user supplied applianceid and vcenterid.`n"
        $search_list = @(
            [PSCustomObject]@{
                applianceid = $applianceid
                vcenterid   = $vcenterid
            }
        )
    }

    if ($sltid -and $slpid) {
        Invoke-ValidatePrameters -SltId $sltid -SlpId $slpid -ErrorAction Stop
    }
    elseif ($sltname -and $slpname) {
        Invoke-ValidatePrameters -SltName $sltname -SlpName $slpname -ErrorAction Stop
    }

    # Loop over the applianceid and vcenterid list, apply SLA to all the tagged VMs.
    $search_list | ForEach-Object {
        $appliance_id = $_.applianceid
        $vcenter_id = $_.vcenterid

        Write-Output "Discovering VMs with applianceid=$appliance_id, vcenterid=$vcenter_id ..."

        $vcenter_hostname = Find-vCenterHostname -vCenterId $vcenter_id
        if (!$vcenter_hostname) {
            Get-AGMErrorMessage -messagetoprint "Cannot find the vCenter with specified vcenter_id $vcenter_id!"
            return
        }

        Connect-vCenter -vCenterHostName $vcenter_hostname -UserName $username -PassFilePath $passfilepath 
        
        $appliance = Get-AGMAppliance -filtervalue "clusterid=$appliance_id"

        $vcenter_vms_to_protect = Find-vCenterTaggedVMs -VmTag $vmtag
        $vms_to_protect = @()
        New-AGMVMDiscovery -vCenterId $vcenter_id | ForEach-Object {
            # Make sure the discovered VMs contains the VM we want to protect.
            # Skip those VMs that have been already protected
            if (!$vcenter_vms_to_protect.uuid.Contains($_.uuid)) {
                return
            }

            $vms_to_protect += $_
        }

        Write-Output "Discovering VMs - Done`n"

        # Group VMs by clustername
        $vms_grouped_by_clustername = $vms_to_protect | Group-Object -Property "clustername"
        
        Write-Output "Clusters need to be processed: $($vms_grouped_by_clustername.Name)"
        
        # Loop over groupped VMs to create app and apply SLA
        $vms_grouped_by_clustername | ForEach-Object {
            $cluster_name = $_.Name
            $vms_group = $_.Group

            Write-Output "Now processing the cluster: $cluster_name"

            $vms_to_create_app = @()
            $vms_already_created_app = @()
            $vms_group | ForEach-Object {
                if ($_.exists -eq $true) {
                    $vms_already_created_app += $_
                }
                else {
                    $vms_to_create_app += $_
                }
            }

            if ($vms_already_created_app.count -gt 0) {
                Write-Output "VMs have already created an application: $($vms_already_created_app.vmname)`n"
            }
    
            if ($vms_to_create_app.count -gt 0) {
                Write-Output "Creating applications for the VMs: $($vms_to_create_app.vmname)`n"
    
                New-AGMVMApp -vCenterId $vcenter_id -Cluster $appliance.id -ClusterName $cluster_name -VmUUIDs $vms_to_create_app.uuid
    
                Invoke-WaitingAppsCreationCompleted -VmsToCreateApp $vms_to_create_app
            }
            else {
                Write-Output "Do not need to create applications for the tagged VMs.`n"
            }

            if ($nobackup) {
                Write-Output "-nobackup option enabled, won't apply SLA to applications.`n"
                return
            }
            
            $apps_filter = $vms_to_protect | Join-String -Property uuid -OutputPrefix "apptype=VMBackup&" -Separator "&" -FormatString "uniquename={0}"
            $all_apps = Get-AGMApplication -filtervalue $apps_filter
    
            $apps_protectable = $all_apps | Where-Object { ($_.isprotected -eq $false) -and ($_.protectable -eq 1) }
            $apps_already_protected = $all_apps | Where-Object { $_.isprotected -eq $true }
    
            if ($apps_already_protected.count -gt 0) {
                Write-Output "$($apps_already_protected.count) applications have already been protected: $($apps_already_protected.appname)`n"
            }
    
            if ($apps_protectable.count -le 0) {
                Write-Warning "No protectable applications.`n"
                return
            }

            Write-Output "Protecting applications: $($apps_protectable.appname)`n"
    
            Write-Output "Fetching SLA list...`n"
    
            $new_sla_list = @()
            $apps_protectable | ForEach-Object {
                $new_sla_list += [PSCustomObject]@{
                    appid = $_.id
                    sltid = $sltid
                    slpid = $slpid
                }
            }
    
            Write-Output "SLA list:"
            Write-Output $new_sla_list
    
            Write-Output "`nApplying SLA to all protectable applications...`n"
            $new_sla_list | ForEach-Object -ThrottleLimit $parallelism -Parallel {
                $VerbosePreference = $using:VerbosePreference
                $agmip = $using:agmip
                $AGMSESSIONID = $using:AGMSESSIONID
                $AGMToken = $using:AGMToken
    
                $new_sla_cmd = 'New-AGMSLA -appid ' + $_.appid + ' -sltid ' + $_.sltid + ' -slpid ' + $_.slpid
                Write-Verbose "$(Get-Date) Running $new_sla_cmd`n"
    
                New-AGMSLA -appid $_.appid -sltid $_.sltid -slpid $_.slpid > $null
                Start-Sleep -Seconds 5
            }
    
            Write-Output "Successfully protected tagged VMs for Cluster: $cluster_name, vCenter ID: $vcenter_id, Appliance applianceid: $appliance_id, Appliance Name: $($appliance.name)!`n"
        }
    }

    Write-Output "Successfully protected all tagged VMs!`n"
}

function Invoke-WaitingAppsCreationCompleted {
    [CmdletBinding()]
    param (
        # VMs that need to create an application for.
        [Parameter(Mandatory)]
        [PSCustomObject[]]
        $VmsToCreateApp
    )

    # Waiting for all apps being added
    # Check it every 10 seconds until all apps are added
    $start_ts = [int64](Get-Date -UFormat %s)
    $apps_filter = $VmsToCreateApp | Join-String -Property uuid -OutputPrefix "apptype=VMBackup&" -Separator "&" -FormatString "uniquename={0}"
    while ($true) {
        $all_apps = Get-AGMApplication -filtervalue $apps_filter

        # Show progress
        $progress = 100 * ($all_apps.count / $VmsToCreateApp.count)
        Write-Progress -Activity "Applications creation in progress" -Status $("{0:F2}% Completed" -f $progress) -PercentComplete $progress

        if ($all_apps.count -eq $VmsToCreateApp.count) {
            Write-Progress "Applications creation - Done" "Done" -Completed
            Write-Output "All applications have been created successfully.`n"
            break
        }

        if (([int64](Get-Date -UFormat %s) - $start_ts) -ge $timeout) {
            Write-Warning "Timeout ($timeout secs) while waiting for all applications being created.`n"
            Write-Warning "Applications have been created so far: $($all_apps.appname)`n"
            break
        }

        Start-Sleep -Seconds 10
    }
}

function Invoke-ValidatePrameters {
    [CmdletBinding()]
    param (
        # The id of the SLT
        # Mutually exclusive with -sltname and -slpname options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseId')]
        [int]
        $SltId,

        # The id of the SLP
        # Mutually exclusive with -sltname and -slpname options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseId')]
        [int]
        $SlpId,

        # The name of the SLT
        # Mutually exclusive with -sltid and -slpid options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseName')]
        [string]
        $SltName,

        # The name of the SLP
        # Mutually exclusive with -sltid and -slpid options.
        [Parameter(Mandatory = $true, ParameterSetName = 'UseName')]
        [string]
        $SlpName
    )

    # Get SLT and SLP ids if sltname and slpname are passed in
    $slt_filter = "id=$SltId"
    $slp_filter = "id=$SlpId"
    if ($SltName -and $SlpName) {
        $slt_filter = "name=$SltName"
        $slp_filter = "name=$SlpName"
    }

    $slt = Get-AGMSLT -filtervalue $slt_filter
    $slp = Get-AGMSLP -filtervalue $slp_filter

    if (!$slt.id -or !$slp.id) {
        throw "The specified slt id/name or the slp id/name does not exist."
    }

}