zoukankan      html  css  js  c++  java
  • Use VMware vCenter to backup VMs via script

    If your environment just got vCenter from VMware, and again, just you have a lots of ESXi servers, below script should be helpful.

    Welcome email to: larry.song@outlook.com

    vCenter itself has functions to clone VM as scheduled, but pity, not enough for my purpose, I also prefer use script to do the job.

    Blow is a tree command output, only 3 scripts end with PS1.

    │  Backup-VM.ps1
    │  Starter.ps1
    │
    └─vCenter01
            _Configuration.ps1

    Script functionalities described:

    Starter.ps1 - Invoked by task scheduler, in its path, it will scan all _Configuration.ps1 files, and launch Backup-VM.ps1 asynchronously.
    Backup-VM.ps1 - Owns a sole parameter called "-vCenterFolder", pointing to a directory of _Configuration.ps1.
    _Configuration.ps1 - Configuration file, any number of it is ok, based on the contents, do different VM backup jobs on different vCenter servers.

    Script contents and description:

    Starter.ps1

    # Enter directory of script
    Set-Location (Get-Item $MyInvocation.MyCommand.Definition).Directory
    
    # Scan all _Configuration.ps1 files
    Get-ChildItem -Filter '_Configuration.ps1' -Recurse | %{
        # Launch Backup-VM.ps1 and pass _Configuration.ps1's path in
        Start-Process -FilePath 'powershell.exe' -ArgumentList @('-File', 'Backup-VM.ps1', '-vCenterFolder', "`"$($_.DirectoryName)`"")
    }

    _Configuration.ps1

    $Enable = $true
    
    # Below is a sample to backup VMs
    # VM - the VM name in vCenter
    # Host - VMHost name in vCenter
    # Datastore - Datastore name in vCenter in Host
    <#
    Sample:
    $Entries = @(
    @{VM = 'VMName01'; Host = 'TargetESXiServerName02'; Datastore = 'TargetESXiServerName02:storage1'; Reserve = 1;},
    @{VM = 'VMName02'; Host = 'TargetESXiServerName01'; Datastore = 'TargetESXiServerName01:storage2'; Reserve = 1;}
    )
    #>
    
    # Actual data filled in
    $Entries = @(
    
    )
    
    # vCenter server name or IP address
    $vCenter = 'vCenter01'
    
    # Mail setting part, only there are errors needs manual work will be triggered. normal backup job will not trigger the alert.
    $From =  "$($env:COMPUTERNAME)@test.com"
    $To = "AlertNeedsToSend@test.com"
    $Subject = "VMs backup completed with errors - $vCenter"
    $SmtpServer = 'mailgateway'

    Backup-VM.ps1 - Automatically judge which backup way to use, if VM uses VDS (vSphere Distributed Switch), script will choose API backup, if VM uses normal vSphere Standard Switch, script can use New-VM to clone VM. (After VDS, New-VM will fail, unless both original and target ESXi servers have the same VDS settings)

    PARAM(
        [parameter(Mandatory=$true)]
        [string]$vCenterFolder
    )
    
    # Enter directory of _Configuration.ps1
    Set-Location -Path $vCenterFolder
    
    $Date = Get-Date
    $strDate = $Date.ToString("yyyy-MM-dd")
    $strLogFile = "${strDate}.log"
    
    # Import _Configuration.ps1 variables
    . '.\_Configuration.ps1'
    
    # Define a logging function
    function Add-Log
    {
        PARAM(
            [String]$Path,
            [String]$Value,
            [String]$Type = 'Info'
        )
        $Type = $Type.ToUpper()
        $Date = Get-Date
        Write-Host "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ForegroundColor $(
            switch($Type)
            {
                'WARNING' {'Yellow'}
                'Error' {'Red'}
                default {'White'}
            }
        )
        if($Path){
            Add-Content -LiteralPath $Path -Value "$($Date.ToString('[HH:mm:ss] '))[$Type] $Value" -ErrorAction:SilentlyContinue
        }
    }
    
    Add-Log -Path $strLogFile -Value 'New backup started'
    # Whether the configuration is enabled or not
    if(!$Enable)
    {
        Add-Log -Path $strLogFile -Value 'Repository disabled'
        exit
    }
    
    # $vCenter is necessary, without it, script doesn't know where to connect to
    if(!$vCenter)
    {
        Add-Log -Path $strLogFile -Value 'vCenter variable is null, can not continue' -Type Error
        $Alert = $true
    }
    else
    {
        Add-Log -Path $strLogFile -Value "vCenter: [$vCenter]"
    }
    
    # Looking for necessary snapin, early version of PowerCli has no snapin for VDS, output some information to ensure the environment of the script
    if(!(Get-PSSnapin -Name '*VMware.VimAutomation.Vds*' -Registered -ErrorAction:SilentlyContinue))
    {
        Add-Log -Path $strLogFile -Value 'This script is built from [VMware vSphere PowerCLI 5.5], suggest to run on the version' -Type Error
        Add-Log -Path $strLogFile -Value 'PSSnapin [VMware.VimAutomation.Vds] is not found, which could cause backup failure' -Type Error
        Add-Log -Path $strLogFile -Value 'Installer path: [\ServerVMwarevSphereVMware-PowerCLI-5.5.0-1295336.exe]' -Type Info
        exit
    }
    
    # Add snapin
    if(!(Get-PSSnapin '*vmware*' -ErrorAction:SilentlyContinue))
    {
        Add-PSSnapin *vmware*
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to add vmware pssnapin' -Type Error
            Add-Log -Path $strLogFile -Value $Error[0] -Type Error
            exit
        }
    }
    
    # Connect to vCenter,if error, set $Alert to $true to trigger alert at last
    Connect-VIServer -Server $vCenter -Force
    if(!$?)
    {
        Add-Log -Path $strLogFile -Value 'Failed to connect to vCenter, cause:' -Type Error
        Add-Log -Path $strLogFile -Value $Error[0] -Type Error
        $Alert = $true
    }
    
    $Tasks = @()
    # Loop every VM backup item
    foreach($e in $Entries)
    {
        Add-Log -Path $strLogFile -Value "Start doing backup for: [$($e.VM)]"
        # Add a new VM name as "%OLDVMName%_ScriptBackup_%CurrentDate%"
        $VMNew = "$($e.VM)_ScriptBackup_$strDate"
        $e.NewVM = $VMNew
        $VM = $null
        $VM = @(Get-VM -Name $e.VM -ErrorAction:SilentlyContinue)
        if(!$VM)
        {
            Add-Log -Path $strLogFile -Value 'Capture none VM, does the VM exists?' -Type Warning
            continue
        }
        if($VM.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($VM.Count)] VM, duplicated VMs?: [$(($VM | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $VM = $VM[0]
    
        # only one VM captured, no confuse to script
        $VMHost = $null
        $VMHost = @(Get-VMHost -Name $e.Host -ErrorAction:SilentlyContinue)
        if(!$VMHost)
        {
            Add-Log -Path $strLogFile -Value "Capture none VMHost, does the VMHost exists?: [$($e.Host)]" -Type Warning
            continue
        }
        if($VMHost.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($VMHost.Count)] VMHost, duplicated VMHosts?: [$(($VMHost | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $VMHost = $VMHost[0]
    
        # only one VMHost captured, no confuse to script
        $Datastore = $null
        $Datastore = @($VMHost | Get-Datastore -Name $e.Datastore -ErrorAction:SilentlyContinue)
        if(!$Datastore)
        {
            Add-Log -Path $strLogFile -Value "Capture none Datastore, does the Datastore exists on VMHost?: [$($e.Datastore)]" -Type Warning
            continue
        }
        if($Datastore.Count -ge 2)
        {
            Add-Log -Path $strLogFile -Value "Capture [$($Datastore.Count)] Datastore, duplicated Datastores?: [$(($Datastore | %{$_.Id}) -join '], [')]" -Type Warning
            continue
        }
        $Datastore = $Datastore[0]
    
        # only one Datastore captured, no confuse to script
        Add-Log -Path $strLogFile -Value "INFO[OLDName][NewName][Host][Datastore]: [$($VM.Name)][$VMNew][$($VMHost.Name)][$($Datastore.Name)]"
    
        # Whether the VM is using VDS
        $VDS = $null
        $VDS = $VM | Get-VDSwitch -ErrorAction:SilentlyContinue
        if(!$VDS)
        {
            # if VDS is not placed, use New-VM to clone
            Add-Log -Path $strLogFile -Value 'VDSwitch not found on the VM, use commandlet [New-VM] to clone'
            $Task = $null
            $Task = New-VM -Name $VMNew -VM $VM -VMHost $VMHost -Datastore $Datastore -RunAsync -ErrorAction:SilentlyContinue
            if(!$?)
            {
                Add-Log -Path $strLogFile -Value 'New-VM failed, cause:' -Type Warning
                Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
                continue
            }
            Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Id)]"
            $Tasks += $Task.Id
        }
        else
        {
            # using VDS, use API to clone
            Add-Log -Path $strLogFile -Value 'VDSwitch found on the VM, need to use 2nd way to clone VM'
            Add-Log -Path $strLogFile -Value "VDS [Name][KEY]: [$(($VDS | %{$_.Name}) -join ';')][$(($VDS | %{$_.Key}) -join ';')]"
            $TargetVDS = $null
            $TargetVDS = @($VMHost | Get-VDSwitch -ErrorAction:SilentlyContinue)
            # on target VMHost search VDS, if target ESXi has no VDS, clone will fail
            if(!$TargetVDS)
            {
                Add-Log -Path $strLogFile -Value 'Target VMHost server has no VDSwitch, VM which uses a VDS is unable to clone to the VMHost' -Type Warning
                continue
            }
            $TargetVDS = $TargetVDS[-1]
            # capture VDS and pick the one owns most number of ports
            $TargetVDSGroup = @($TargetVDS | Get-VDPortgroup | Sort-Object NumPorts)[-1]
            Add-Log -Path $strLogFile -Value "Target VDS randomly picked [Name][Key]: [$($TargetVDS.Name)][$($TargetVDS.Key)]"
    
            # API nesessary, use default resource pool of ESXi server is fine
            $Pool = $null
            $Pool = @($VMHost | Get-ResourcePool)[0]
            if(!$Pool)
            {
                Add-Log -Path $strLogFile -Value 'No resource pool found from VMHost, please use Get-ResourcePool to find resource pool on the VMhost' -Type Warning
                continue
            }
    
            # Capture VM's network adapter, change it to use VDS on target ESXi
            $VMNic = $null
            $VMNic = $VM.ExtensionData.Config.Hardware.Device | ?{$_.DeviceInfo.Label -imatch 'Network adapter'}
            if($VMNic.Count -ge 2)
            {
                Add-Log -Path $strLogFile -Value 'The VM has more than 2 network adapters, not supported' -Type Warning
                continue
            }
            $VMNicBacking = $VMNic.Backing
            $VMNicBacking.Port.SwitchUuid = $TargetVDS.Key
            $VMNicBacking.Port.PortgroupKey = $TargetVDSGroup.Key
            $VMNicBacking.Port.PortKey = ''
            $VMNicBacking.Port.ConnectionCookie = ''
            
            # API necessary
            $spec = New-Object VMware.Vim.VirtualMachineCloneSpec
            $spec.Config = New-Object VMware.Vim.VirtualMachineConfigSpec
            $nicDev = New-Object VMware.Vim.VirtualDeviceConfigSpec
            $nicDev.Operation = 'edit'
            $nicDev.Device = $VMNic
            $nicDev.Device.Backing = $VMNicBacking
            $spec.Config.DeviceChange = $nicDev
            $spec.Config.DeviceChange[0].Device.Backing.Port.PortKey = ''
            $spec.Location = New-Object VMware.Vim.VirtualMachineRelocateSpec
            $spec.Location.Host = $VMHost.ExtensionData.MoRef
            $spec.Location.Datastore = $Datastore.ExtensionData.MoRef
            $spec.Location.Pool = $Pool.ExtensionData.MoRef
            $spec.PowerOn = $false
            $spec.Template = $false
    
            Add-Log -Path $strLogFile -Value 'Trying to get datacenter object, this could potentially cause a dead loop!'
            # to get the $Folder for API, must get datacenter of ESXi first
            $Datacenter = $VMHost.Parent
            while($Datacenter.Id -notmatch 'Datacenter')
            {
                $Datacenter = $Datacenter.Parent
            }
            # API necessary
            $Folder = $Datacenter | Get-Folder -Name 'Discovered virtual machine'
            Add-Log -Path $strLogFile -Value 'Did not fail into a dead loop!'
    
            # use API to launch clone task
            $Task = $null
            $Task = $VM.ExtensionData.CloneVM_Task($Folder.ExtensionData.MoRef, $VMNew, $spec)
            if(!$?)
            {
                Add-Log -Path $strLogFile -Value 'CloneVM_Task failed, cause:' -Type Warning
                Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
                continue
            }
            Add-Log -Path $strLogFile -Value "Task launched: [$($Task.Type)-$($Task.Value)]"
            $Tasks += "$($Task.Type)-$($Task.Value)"
        }
    }
    
    $Tasks = @($Tasks | ?{$_})
    Add-Log -Path $strLogFile -Value "Waiting for tasks to complete, count: [$($Tasks.Count)]"
    while($Tasks)
    {
        # clone is running asynchronously, so script needs to trace all tasks every 5 minutes
        # The sleep time better not exceed 15 minutes, due to vCenter will clean completed tasks after 15 minutes
        # when debugging, change the time to 10 seconds is fine
        Start-Sleep -Seconds 300
        $Tasks = Get-Task -Id $Tasks -ErrorAction:SilentlyContinue
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to refresh Task states, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
        }
        $Tasks = @(
            $Tasks | %{
                if($_.State -ne 'Running')
                {
                    Add-Log -Path $strLogFile -Value "Task completed [ID][State]: [$($_.Id)][$($_.State)]"
                    if($_.State -eq 'Error')
                    {
                        Add-Log -Path $strLogFile -Value $_.ExtensionData.Info.Error.LocalizedMessage -Type Warning
                    }
                }
                else
                {
                    Add-Log -Path $strLogFile -Value "Task running [ID][% Complete]: [$($_.Id)][$($_.PercentComplete)%]"
                    $_.Id
                }
            }
        )
    }
    
    # script will start verification for VM backups after all Tasks done
    Add-Log -Path $strLogFile -Value 'Verification start'
    foreach($e in $Entries)
    {
        if((Get-VM -Name $e.NewVM -ErrorAction:SilentlyContinue) -and $?)
        {
            # Backup VM found in vCenter, suggest the backup was succeed
            Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM found in vCenter"
            if($e.Reserve)
            {
                # If Reserve's valud is set, script will find all backups for the VM, and remove addtionals
                $VMBackups = $null
                $VMBackupsRemoval = $null
                $VMBackups = Get-VM -Name "$($e.VM)_ScriptBackup_*" | ?{$_.Name -imatch '_ScriptBackup_d{4}-d{2}-d{2}$'} -ErrorAction:SilentlyContinue
                $VMBackups = @($VMBackups | Sort-Object 'Name')
                Add-Log -Path $strLogFile -Value "Old VM backups captured: [$($VMBackups.Count)][$(($VMBackups | %{$_.Name}) -join ';')]"
                $i = $VMBackups.Count - $e.Reserve
                if($i -le 0)
                {
                    $i = 0
                    Add-Log -Path $strLogFile -Value 'No old backups available to be removeds'
                }
                if($i -gt 0)
                {
                    Add-Log -Path $strLogFile -Value "VM old backups can be removed count: [$i]"
                    $VMBackupsRemoval = $VMBackups[0..(--$i)]
                    Remove-VM -VM $VMBackupsRemoval -DeletePermanently -Confirm:$false
                }
            }
        }
        else
        {
            # backup VM not found in vCenter which means backup failed, better do nothing
            Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM not found in vCenter, backup failure?" -Type Warning
            $Alert = $true
        }
    }
    
    Add-Log -Path $strLogFile -Value 'All done!'
    
    # If there is error needs manual work, email will be triggered
    if($Alert)
    {
        Send-MailMessage -To $To -From $From -SmtpServer $SmtpServer -Subject $Subject -Attachments $strLogFile
        if(!$?)
        {
            Add-Log -Path $strLogFile -Value 'Failed to send email, cause:' -Type Warning
            Add-Log -Path $strLogFile -Value $Error[0] -Type Warning
        }
    }

    API reference,

    https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/

  • 相关阅读:
    [转]Lucene 性能优化带数据
    Lucene Document getBoost(float) 和 setBoost(float)
    几种Lucene.Net打开IndexReader的方式
    JSON 省市数据包括港澳
    Lucene Boost 精度表
    Dot NET 内存泄漏
    对《LINQ能不能用系列(一)数组筛选效率对比》中测试的几个问题
    售前工程师的成长一个老员工的经验之谈(三)(转载)
    yum使用简介
    Hadoop源代码分析 HDFS(转载)
  • 原文地址:https://www.cnblogs.com/LarryAtCNBlog/p/4613320.html
Copyright © 2011-2022 走看看