This artical will be published in English as well: http://www.cnblogs.com/LarryAtCNBlog/p/4613320.html
如果你的环境里刚好有VMware vCenter,里面加了一堆ESX(i)服务器的话,那以下的脚本是有帮助的。
vCenter本身是有计划备份的功能的,但是比较遗憾的是功能并不能满足我使用的需求,我也更偏向于自己做脚本控制。
以下是tree的一个sample输出,脚本只有3个,都以ps1结尾。
│ Backup-VM.ps1 │ Starter.ps1 │ └─vCenter01 _Configuration.ps1
下面是脚本的作用描述,
Starter.ps1 - 由task scheduler调用,在其所属的目录搜索所有_Configuration.ps1文件,然后异步调用Backup-VM.ps1,将_Configuration.ps1的目录传入 Backup-VM.ps1 - 接收唯一参数-vCenterFolder,指向_Configuration.ps1所在的文件夹 _Configuration.ps1 - 配置文件,可以有多个,依据其配置文件不同指向不同的vCenter server,备份不同的VM,多个vCenter的job建多个_Configuration.ps1即可
下面是脚本内容及解释
Starter.ps1
# 进入script所在的文件夹 Set-Location (Get-Item $MyInvocation.MyCommand.Definition).Directory # 搜索目录下所有的_Configuration.ps1文件 Get-ChildItem -Filter ‘_Configuration.ps1‘ -Recurse | %{ # 调用Backup-VM.ps1,将_Configuration.ps1的目录传入 Start-Process -FilePath ‘powershell.exe‘ -ArgumentList @(‘-File‘, ‘Backup-VM.ps1‘, ‘-vCenterFolder‘, "`"$($_.DirectoryName)`"") }
_Configuration.ps1
# 该_Configuration.ps1是否启用,置成$false脚本则不会执行 $Enable = $true # 下面只是一个VM配备格式的样本 # VM表示该VM在vCenter里的名字 # Host表示要把该VM往哪个ESX(i)上备份 # Datastore表示目标ESX(i)上的storage名称 <# Sample: $Entries = @( @{VM = ‘VMName01‘; Host = ‘TargetESXiServerName02‘; Datastore = ‘TargetESXiServerName02:storage1‘; Reserve = 1;}, @{VM = ‘VMName02‘; Host = ‘TargetESXiServerName01‘; Datastore = ‘TargetESXiServerName01:storage2‘; Reserve = 1;} ) #> # 实际数据填在下面,sample如上 $Entries = @( ) # vCenter服务器名或ip地址 $vCenter = ‘vCenter01‘ # 邮件设置部分,只有当出现需要关注的错误时,邮件部分才会被触发,正常备份完成不会触发 $From = "$($env:COMPUTERNAME)@test.com" $To = "[email protected]" $Subject = "VMs backup completed with errors - $vCenter" $SmtpServer = ‘mailgateway‘
Backup-VM.ps1 - 自动判断VM的网络,如果使用vSphere Distributed Switch,会切换成API备份,如果是普通的vSphere Standard Switch网络,用New-VM命令即可完成备份。(使用VDS后,New-VM命令会失败,除非目标ESXi服务器上的网络配置和当前ESXi包括同样的VDS)
PARAM( [parameter(Mandatory=$true)] [string]$vCenterFolder ) # 进入_Configuration.ps1所在的目录 Set-Location -Path $vCenterFolder $Date = Get-Date $strDate = $Date.ToString("yyyy-MM-dd") $strLogFile = "${strDate}.log" # 载入_Configuration.ps1里的variable . ‘.\_Configuration.ps1‘ # 定义一个写日志的函数 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‘ # 判断该_Configuration.ps1是否配置为启用 if(!$Enable) { Add-Log -Path $strLogFile -Value ‘Repository disabled‘ exit } # $vCenter是必要信息,没有它脚本没有办法执行 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]" } # 找必要的snapin,早期版本的PowerCli没有VDS的操作snapin,显示一些提示以确保运行环境正常 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: [\\Server\VMware\vSphere\VMware-PowerCLI-5.5.0-1295336.exe]‘ -Type Info exit } # 载入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 } } # 连接到vCenter,假如出现错误,把$Alert置成$true为最后发出告警邮件 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 = @() # 对每一项配置的VM备份做循环 foreach($e in $Entries) { Add-Log -Path $strLogFile -Value "Start doing backup for: [$($e.VM)]" # 添加新的VM名称为旧名+_ScriptBackup_+当时的日期 $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] # 执行到此处说明VM在vCenter里存在并唯一,脚本没有抓到多个VM $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] # 执行到此处说明VMHost在vCenter里存在并唯一,脚本没有抓到多个VMHost $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] # 执行到此处说明Datastore在vCenter里存在并唯一,脚本没有抓到多个Datastore Add-Log -Path $strLogFile -Value "INFO[OLDName][NewName][Host][Datastore]: [$($VM.Name)][$VMNew][$($VMHost.Name)][$($Datastore.Name)]" # 判断VM是否使用了VDS $VDS = $null $VDS = $VM | Get-VDSwitch -ErrorAction:SilentlyContinue if(!$VDS) { # 如果没有使用VDS,则可以直接用New-VM命令备份 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 { # 如果使用VDS,则需要使用API自行备份 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) # 在目标VMHost上搜索VDS,如果目标VMHost上没有VDS,clone没有办法继续 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] # 在目标VMHost上搜索到了VDS,抓取出port数量最多的一个 $TargetVDSGroup = @($TargetVDS | Get-VDPortgroup | Sort-Object NumPorts)[-1] Add-Log -Path $strLogFile -Value "Target VDS randomly picked [Name][Key]: [$($TargetVDS.Name)][$($TargetVDS.Key)]" # API必要参数,VMHost默认的Pool即可 $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 } # 抓取VM上的网卡对象,并对其修改,修改成为目标VMHost上的VDS $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必要参数 $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!‘ # 为了得到下面的API必要参数$Folder,需要先得到VMHost所在的Datacenter $Datacenter = $VMHost.Parent while($Datacenter.Id -notmatch ‘Datacenter‘) { $Datacenter = $Datacenter.Parent } # API必要参数 $Folder = $Datacenter | Get-Folder -Name ‘Discovered virtual machine‘ Add-Log -Path $strLogFile -Value ‘Did not fail into a dead loop!‘ # 调用API触发VM 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是异步进行,脚本每5分钟进行一次跟踪,由于vCenter默认完成的Task会在15分钟后清除 # 因此下面的sleep时间最好不要超过15分钟,否则有可能抓不到Task的成功失败的信息 # 调试的时候可以改成10秒一检测 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 } } ) } # 所有Task都完成后,脚本会开始校验VM是否成功备份 Add-Log -Path $strLogFile -Value ‘Verification start‘ foreach($e in $Entries) { if((Get-VM -Name $e.NewVM -ErrorAction:SilentlyContinue) -and $?) { # 备份在vCenter里找到,说明备份成功 Add-Log -Path $strLogFile -Value "[VM][NewVM]: [$($e.VM)][$($e.NewVM)] -- New VM found in vCenter" if($e.Reserve) { # 如果设置了Reserve的值,脚本会根据该值找到当前vCenter里所有的备份,然后删掉多余的备份VM $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 { # 备份没有在vCenter里找到,说明备份失败 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($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的参考如下,
https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/
当然,许久之前也参考过部分VMware社区的文章~
时间: 2024-10-15 08:24:15