Backing up UCM & How to run a PowerCLI Script with Windows Task Scheduler

At my job, at the time of this writing, I'm the backup sysadmin in charge of our virtual infrastructure (Cisco UCS blades + vSphere 5.1). As my boss, who's the primary for these things, is taking on a new role (same company & still my boss), I'm on track to become primary for these things. As a part of that effort I've been playing catch-up to learn more about vSphere, PowerCLI & Powershell among several other things.
One of my projects as of late has been to use PowerCLI to create a VM-level backup solution for our UCM appliances that don't like to be backed up with our Unitrends Virtual Backup appliances. As a newer VMware Admin, I had no idea how to run PowerCLI scripts. I knew that PowerCLI is basically Powershell, which I know a little bit about. Figuring out how to delete a clone and then creating a clone placed on a specific host & datastore wasn't that hard. Perfecting how to run the script reliably and emailing off the results was more difficult.

First the why. Why am I doing this? This need came from our VM-level backup solution, UVB, interrupting the UCM (Unified Communication Manager) appliances. This meant our phone system going down for 10 minutes every night. For many businesses, having a brief phone outage at 1am is acceptable. We're a university housing department with area offices opened 24 hours so having something as essential as our phone system go down for even 10 minutes isn't acceptable. Why even take a VM-level backup when Cisco already has it's own DRS backup & restore feature? A fair question. The main reason is because restoring the whole VM is faster & easier than DRS. Restoring a borked UCM appliance via DRS takes about 4 hours (depending on the size of your database). Restoring from a UVB backup takes about 40 minutes. Restoring from a clone like we're doing here takes about 5 minutes. We called Unitrends to see if anyone has had this issue with their UCM appliances and Unitrends informed us that they can't help with that because Cisco only supports backups & restores done via DRS.

Now the how. There's 2 ways to run a PowerCLI script in Windows Task Scheduler. First, you can create a task to run your script that loads the PowerCLI Snapin like below.

Add-PSSnapin vmware.vimautomation.core

Secondly, and the way I'm going about it, is to call Powershell with the vim.psc1 console file from PowerCLI with the Windows Task Scheduler. That looks like below.

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -PSConsoleFile "C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\vim.psc1" "& "C:\Scripts\PowerCLI\UCM1Clone.ps1"

You might ask "What about permissions?" That's a great question because you still need to login to vSphere before you can do much of anything. You could store the credentials in a variable with Get-Cred but that would mean you'd have a password out there in plain-text which is just lazy. What I've done below is open PowerCLI as the user that will run this backup script, and run the following.

$cred = Get-Cred

This will create a variable with your credentials. If you want to be lazy about it, that's all you need to do. If you want to do it right, keep going and run

$cred.Password | ConvertFrom-Securestring | Set-Content C:\vSphere\scripts\powerclicred

This will create a hashed & salted credentials file you can use in your scripts. In our script you can put something like

$pwd = Get-Content C:\Scripts\PowerCLI\powerclicred | ConvertTo-SecureString

$cred = New-Object System.Management.Automation.PsCredential “domain\username“, $pwd

Connect-VIServer yourvcenter.whatever -Credential $cred

In a production environment, you'll want to be using a specially created service account to handle all of this. If you're just playing around in a home lab, a service account isn't necessary. From here, we'll setup some variables for naming our backup clone and for specifying the VM we're cloning.

$hostname = 'vm-you-want-to-backup'

$hostnm = get-vm -name $hostname

$server = $hostnm[0]

$goodbackupname = $hostname + "-Backup"

$bugn = $goodbackupname

After that we have the 2 commands that actually do the majority of the magic.

Get-VM -Name  $goodbackupname | Remove-VM -DeletePermanently -Confirm:$false
New-VM -Name $goodbackupname -VM $hostname -VMHost specific-host -Datastore specific-datastore


Once that's finished, we want to get information about our new clone and into a variable. This is where things got a bit more hairy.

 function Get-VMinventory {  
 function Get-RDMDisk {  
   [CmdletBinding()]  
   param (  
     [Parameter(Mandatory=$True)]  
     [string[]]$VMName  
     )  
         $RDMInfo = Get-VM -Name $VMName | Get-HardDisk -DiskType RawPhysical, RawVirtual  
         $Result = foreach ($RDM in $RDMInfo) {  
          "{0}/{1}/{2}/{3}"-f ($RDM.Name), ($RDM.DiskType),($RDM.Filename), ($RDM.ScsiCanonicalName)     
         }  
         $Result -join (", ")  
 }  
 function Get-vNicInfo {  
   [CmdletBinding()]  
   param (  
     [Parameter(Mandatory=$True)]  
     [string[]]$VMName  
     )  
         $vNicInfo = Get-VM -Name $VMName | Get-NetworkAdapter  
         $Result = foreach ($vNic in $VnicInfo) {  
           "{0}={1}"-f ($vnic.Name.split("")[2]), ($vNic.Type)  
         }  
         $Result -join (", ")  
 }  
 function Get-InternalHDD {  
   [CmdletBinding()]  
   param (  
     [Parameter(Mandatory=$True)]  
     [string[]]$VMName  
     )  
         $VMInfo = Get-VMGuest -VM $VMName # (get-vm $VMName).extensiondata  
         $InternalHDD = $VMInfo.ExtensionData.disk   
         $result = foreach ($vdisk in $InternalHDD) {  
           "{0}={1}GB/{2}GB"-f ($vdisk.DiskPath), ($vdisk.FreeSpace /1GB -as [int]),($vdisk.Capacity /1GB -as [int])  
         }  
         $result -join (", ")  
 }  
   foreach ($vm in (get-vm -name $bugn)) {  
     $props = @{'VMName'=$vm.Name;  
           'IP Address'= $vm.Guest.IPAddress[0]; #$VM.ExtensionData.Summary.Guest.IpAddress  
           'PowerState'= $vm.PowerState;  
           'Domain Name'= ($vm.ExtensionData.Guest.Hostname -split '\.')[1,2] -join '.';            
           'vCPU'= $vm.NumCpu;  
           'RAM(GB)'= $vm.MemoryGB;  
           'Total-HDD(GB)'= $vm.ProvisionedSpaceGB -as [int];  
           'HDDs(GB)'= ($vm | get-harddisk | select-object -ExpandProperty CapacityGB) -join " + "            
           'Datastore'= (Get-Datastore -vm $vm) -split ", " -join ", ";  
           'Partition/Size' = Get-InternalHDD -VMName $vm.Name  
           'Real-OS'= $vm.guest.OSFullName;  
           'Setting-OS' = $VM.ExtensionData.summary.config.guestfullname;  
           'EsxiHost'= $vm.VMHost;  
           'vCenter Server' = ($vm).ExtensionData.Client.ServiceUrl.Split('/')[2].trimend(":443")  
           'Hardware Version'= $vm.Version;  
           'Folder'= $vm.folder;  
           'MacAddress' = ($vm | Get-NetworkAdapter).MacAddress -join ", ";  
           'VMX' = $vm.ExtensionData.config.files.VMpathname;  
           'VMDK' = ($vm | Get-HardDisk).filename -join ", ";  
           'VMTools Status' = $vm.ExtensionData.Guest.ToolsStatus;  
           'VMTools Version' = $vm.ExtensionData.Guest.ToolsVersion;  
           'VMTools Version Status' = $vm.ExtensionData.Guest.ToolsVersionStatus;  
           'VMTools Running Status' = $vm.ExtensionData.Guest.ToolsRunningStatus;  
           'SnapShots' = ($vm | get-snapshot).count;  
           'DataCenter' = $vm | Get-Datacenter;  
           'vNic' = Get-VNICinfo -VMName $vm.name;  
           'PortGroup' = ($vm | Get-NetworkAdapter).NetworkName -join ", ";  
           'RDMs' = Get-RDMDisk -VMName $VM.name  
           #'Department'= ($vm | Get-Annotation)[0].value;  
           #'Environment'= ($vm | Get-Annotation)[1].value;  
           #'Project'= ($vm | Get-Annotation)[2].value;  
           #'Role'= ($vm | Get-Annotation)[3].value;  
           }  
     $obj = New-Object -TypeName PSObject -Property $Props  
     Write-Output $obj | select-object -Property 'VMName', 'IP Address', 'Domain Name', 'Real-OS', 'vCPU', 'RAM(GB)', 'Total-HDD(GB)' ,'HDDs(GB)', 'Datastore', 'Partition/Size', 'Hardware Version', 'PowerState', 'Setting-OS', 'EsxiHost', 'vCenter Server', 'Folder', 'MacAddress', 'VMX', 'VMDK', 'VMTools Status', 'VMTools Version', 'VMTools Version Status', 'VMTools Running Status', 'SnapShots', 'DataCenter', 'vNic', 'PortGroup', 'RDMs' # 'Folder', 'Department', 'Environment' 'Environment'  
   }  
 }  
$results = Get-VMinventory 

Now that we've gotten our clone details into a variable, we can email it out to the team to let them know the backup job has completed.

Clear-Content "C:\Scripts\PowerCLI\results.txt"
Out-File -InputObject $results -FilePath "C:\Scripts\PowerCLI\results.txt"
$OutputFile = "C:\Scripts\PowerCLI\results.txt"
$messageparam = @{


    Subject = "This backup job has completed for $hostname"
    Body =  (Get-Content $OutputFile| out-string )
    From = "from-address"
    To = "to-address"
    SmtpServer = "your-SMTP-server"
}

send-MailMessage @messageparam #-BodyAsHtml

A big thanks to Magnus Andersson, Alan Renouf & SB for their direct & indirect help.


Comments

Popular posts from this blog

Installing CentOS 7 on a Raspberry Pi 3

Modifying the Zebra F-701 & F-402 pens

How to fix DPM Auto-Protection failures of SQL servers