# encoding: ascii
# api: powershell
# title: Ashwin
# description: Some of commands get outdated and needs to get updated with latest powershell upgrade
# version: 0.1
# type: script
# author: Ashwin Rayaprolu
# license: CC0
# function: Backup-VMs
# x-poshcode-id: 6496
# x-archived: 2016-09-03T23:43:32
# x-published: 2016-08-31T01:48:00
#
# Added condition not to export VM which is already in progress of getting exported
# Remove Shutdown/Stop VM call as that is unnecessary in most cases
#
# Directions for use:
# Import this script using the Import-Module cmdlet
# All output is logged to the backup directory in the $($BackupDriveLetter):\VMBackup\Backup-VMs.log file
# Use the Backup-VMs cmdlet to begin the process
# Parameter BackupDriveLetter indicates the drive to put this backup onto. It must be mounted to the host running the script.
# Parameter VMHost defines the host that contains the VMs you want to back up. If it's blank, then it just targets the host the script is running on
# Parameter VMNames defines the specific VMs you wish to backup, otherwise it'll back up all of them on the target host
# Switch parameter ShutHostDownWhenFinished will cause the specified host (and any VMs running on it) to shut down upon completion of the backup
# Example:
# PS> Import-Module D:\Backup-VMs.ps1
# PS> Backup-VMs -BackupDriveLetter F -VMHost HyperVHost -VMNames mydevmachine,broker77
# This is modified version updated to work in win 2012 Datacenter environment
# ----------------------------------------------------------------------------
# Note that this script requires administrator privileges for proper execution
# ----------------------------------------------------------------------------
# Author: Ashwin Rayaprolu (Ashwin.rayaprolu@gmail.com)
# Note that this script requires the following:
# Original version is at http://poshcode.org/3927 and this is plagiarized version
# PowerShell Management Library for Hyper-V (for the Get-VM and Export-VM cmdlets)
# This installs itself wherever you downloaded it - make sure the HyperV folder finds its way to somewhere in $env:PSModulePath
# http://pshyperv.codeplex.com/downloads/get/219013
#
# Windows PowerShell Pack (for the Copy-ToZip cmdlet)
# This installs to $home\Documents\WindowsPowerShell\Modules, make sure that this path is in $env:PSModulePath
# http://archive.msdn.microsoft.com/PowerShellPack/Release/ProjectReleases.aspx?ReleaseId=3341
# our one global variable is for logging
$Logfile = ""
Function Backup-VMs
{
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[parameter(Mandatory = $true)]
[string]$BackupDriveLetter, # $BackupDriveLetter:\VMBackups\$backupDate
[ValidateNotNullOrEmpty()]
[string]$VMHost, # the host that holds the vms we wish to back up, otherwise the one running the script
[string[]]$VMNames, # if not specified, back up all of them
[switch]$ShutHostDownWhenFinished # when set, shuts down the target host, including any vms on it
)
process
{
# first, run a bunch of checks
#region checks
# check if the PowerShellPack modules are loaded. Not required
Write-Host -ForegroundColor Red "Processsing........"
# sanitize user input (F: will become F)
if ($BackupDriveLetter -like "*:")
{
$BackupDriveLetter = $BackupDriveLetter -replace ".$"
}
# check to make sure the user specified a valid backup location
if ((Test-Path "$($BackupDriveLetter):") -eq $false)
{
Write-Host -ForegroundColor Red "Drive $($BackupDriveLetter): does not exist - terminating backup script."
Break
}
# if host was not speicified, use the host running the script
if ($VMHost -eq "")
{
$VMHost = Hostname
}
# check to make sure the specified host is a vmhost
if (!(Get-VMHost) -icontains $VMHost)
{
Write-Host -ForegroundColor Red "Host $($VMHost) is not listed in Get-VMHost - terminating backup script."
Break
}
# check to make sure the specified host has any vms to back up
if (!(Get-VM -ComputerName $VMHost))
{
Write-Host -ForegroundColor Red "Host $($VMHost) does not appear to have any VMs running on it according to 'Get-VM -Server $($VMHost)'."
Write-Host -ForegroundColor Yellow "This can be occur if PowerShell is not running with elevated privileges."
Write-Host -ForegroundColor Yellow "Please make sure that you are running PowerShell with Administrator privileges and try again."
Write-Host -ForegroundColor Red "Terminating backup script."
Break
}
#endregion
#region directory business
# make our parent directory if needed
if ((Test-Path "$($BackupDriveLetter):\VMBackup") -eq $false)
{
$parentDir = New-Item -Path "$($BackupDriveLetter):\VMBackup" -ItemType "directory"
if ((Test-Path $parentDir) -eq $false)
{
Write-Host -ForegroundColor Red "Problem creating $parentDir - terminating backup script."
Break
}
}
# initialize our logfile
$Logfile = "$($BackupDriveLetter):\VMBackup\Backup-VMs.log"
if ((Test-Path $Logfile) -eq $false)
{
$newFile = New-Item -Path $Logfile -ItemType "file"
if ((Test-Path $Logfile) -eq $false)
{
Write-Host -ForegroundColor Red "Problem creating $Logfile - terminating backup script."
Break
}
}
$backupDate = Get-Date -Format "yyyy-MM-dd"
$destDir = "$($BackupDriveLetter):\VMBackup\$backupDate-$VMHost-backup\"
# make our backup directory if needed
if ((Test-Path $destDir) -eq $false)
{
$childDir = New-Item -Path $destDir -ItemType "directory"
if ((Test-Path $childDir) -eq $false)
{
Write-Host -ForegroundColor Red "Problem creating $childDir - terminating backup script."
Break
}
}
#endregion
Add-content -LiteralPath $Logfile -value "==================================================================================================="
Add-content -LiteralPath $Logfile -value "==================================================================================================="
# now that our checks are done, start backing up
T -text "Starting Hyper-V virtual machine backup for host $VMHost at:"
$dateTimeStart = date
T -text "$($dateTimeStart)"
T -text ""
# export the vms to the destination
ExportMyVms -VMHost $VMHost -Destination $destDir -VMNames $VMNames
T -text ""
T -text "Exporting finished"
#region compression
# get what we just backed up
$sourceDirectory = Get-ChildItem $destDir
if ($sourceDirectory)
{
# get the total size of all of the files we just backed up
$sourceDirSize = Get-ChildItem $destDir -Recurse | Measure-Object -property length -sum -ErrorAction SilentlyContinue
$sourceDirSize = ($sourceDirSize.sum / 1GB)
# get how much free space is left on our backup drive
$hostname = Hostname
$backupDrive = Get-WmiObject win32_logicaldisk -ComputerName $hostname | Where-Object { $_.DeviceID -eq "$($BackupDriveLetter):" }
$backupDriveFreeSpace = ($backupDrive.FreeSpace / 1GB)
# tell the user what we've found
$formattedBackupDriveFreeSpace = "{0:N2}" -f $backupDriveFreeSpace
$formattedSourceDirSize = "{0:N2}" -f $sourceDirSize
T -text "Checking free space for compression:"
T -text "Drive $($BackupDriveLetter): has $formattedBackupDriveFreeSpace GB free on it, this backup took $formattedSourceDirSize GB"
# check if we need to make any room for the next backup
$downToOne = $false
while (!$downToOne -and $sourceDirSize > $backupDriveFreeSpace)
{
# clear out the oldest backup if this is the case
$backups = Get-ChildItem -Path "$($BackupDriveLetter):\VMBackup\" -include "*-backup.zip" -recurse -name
$backups = [array]$backups | Sort-Object
# make sure we aren't deleting the only directory!
if ($backups.length -gt 1)
{
T -text "Removing the oldest backup [$($backups[0])] to clear up some more room"
Remove-Item "$($BackupDriveLetter):\VMBackup\$($backups[0])" -Recurse -Force
# now check again
$backupDrive = Get-WmiObject win32_logicaldisk -ComputerName $hostname | Where-Object { $_.DeviceID -eq "$($BackupDriveLetter):" }
$backupDriveFreeSpace = ($backupDrive.FreeSpace / 1GB)
$formattedBackupDriveFreeSpace = "{0:N2}" -f $backupDriveFreeSpace
T -text "Now we have $formattedBackupDriveFreeSpace GB of room"
}
else
{
# we're down to just one backup left, don't delete it!
$downToOne = $true
}
}
T -text "Compressing the backup..directory $destDir VMHost $VMHost."
# zip up everything we just did
ZipFolder -directory $destDir -VMHost $VMHost
$zipFileName = $destDir -replace ".$"
$zipFileName = $zipFileName + ".zip"
T -text "Backup [$($zipFileName)] created successfully"
$destZipFileSize = (Get-ChildItem $zipFileName).Length / 1GB
$formattedDestSize = "{0:N2}" -f $destZipFileSize
T -text "Uncompressed size:`t$formattedSourceDirSize GB"
T -text "Compressed size: `t$formattedDestSize GB"
}
#endregion
# delete the non-compressed directory, leaving just the compressed one
Remove-Item $destDir -Recurse -Force
T -text ""
T -text "Finished backup of $VMHost at:"
$dateTimeEnd = date
T -text "$($dateTimeEnd)"
$length = ($dateTimeEnd - $dateTimeStart).TotalMinutes
$length = "{0:N2}" -f $length
T -text "The operation took $length minutes"
if ($ShutHostDownWhenFinished -eq $true)
{
#No need to shutdown any VM or host
T -text "Attempting to shut down host machine $VMHost"
#ShutdownTheHost -HostToShutDown $VMHost
}
}
}
## this function will shut down any vms running on the host executing this script and then shut down said host
Function ShutdownTheHost
{
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[string]$HostToShutDown
)
process
{
## Get a list of all VMs on $HostToShutDown
$VMs = Get-VM -Server $HostToShutDown
## only run through the list if there's anything in it
if ($VMs)
{
## For each VM on Node, Save (if necessary), Export and Restore (if necessary)
foreach ($VM in @($VMs))
{
$VMName = $VM.ElementName
$summofvm = get-vmsummary $VMName
$summhb = $summofvm.heartbeat
$summes = $summofvm.enabledstate
## Shutdown the VM if HeartBeat Service responds
if ($summhb -eq "OK")
{
T -text ""
T -text "HeartBeat Service for $VMName is responding $summhb, saving the machine state"
Save-VM -VM $VMName -Server $VMHost -Force -Wait
}
## Checks to see if the VM is already stopped
elseif (($summes -eq "Stopped") -or ($summes -eq "Suspended"))
{
T -text ""
T -text "$VMName is $summes"
}
## If the HeartBeat service is not OK, aborting this VM
elseif ($summhb -ne "OK" -and $summes -ne "Stopped")
{
T -text
T -text "HeartBeat Service for $VMName is responding $summhb. Aborting save state."
}
}
T -text "All VMs on $HostToShutDown shut down or suspended."
}
T -text "Shutting down machine $HostToShutDown..."
#Stop-Computer -ComputerName $HostToShutDown
}
}
## the following three functions relating to zipping up a folder come from Jeremy Jameson
## http://www.technologytoolbox.com/blog/jjameson/archive/2012/02/28/zip-a-folder-using-powershell.aspx
## I have modified his approach to suit the multi-gigabyte files we'll be dealing with
function IsFileLocked(
[string] $path)
{
[bool] $fileExists = Test-Path $path
If ($fileExists -eq $false)
{
Throw "File does not exist (" + $path + ")"
}
[bool] $isFileLocked = $true
$file = $null
Try
{
$file = [IO.File]::Open(
$path,
[IO.FileMode]::Open,
[IO.FileAccess]::Read,
[IO.FileShare]::None)
$isFileLocked = $false
}
Catch [IO.IOException]
{
If ($_.Exception.Message.EndsWith("it is being used by another process.") -eq $false)
{
Throw $_.Exception
}
}
Finally
{
If ($file -ne $null)
{
$file.Close()
}
}
return $isFileLocked
}
function WaitForZipOperationToFinish(
[__ComObject] $zipFile,
[int] $expectedNumberOfItemsInZipFile)
{
T -text "Waiting for zip operation to finish on $($zipFile.Self.Path)..."
Start-Sleep -Seconds 5 # ensure zip operation had time to start
# wait for the operation to finish
# the folder is locked while we're zipping stuff up
[bool] $isFileLocked = IsFileLocked($zipFile.Self.Path)
while($isFileLocked)
{
Write-Host -NoNewLine "."
Start-Sleep -Seconds 5
$isFileLocked = IsFileLocked($zipFile.Self.Path)
}
T -text ""
}
function ZipFolder(
[IO.DirectoryInfo] $directory)
{
$backupFullName = $directory.FullName
T -text ("Creating zip file for folder ($backupFullName)...")
[IO.DirectoryInfo] $parentDir = $directory.Parent
[string] $zipFileName
If ($parentDir.FullName.EndsWith("\") -eq $true)
{
# e.g. $parentDir = "C:\"
$zipFileName = $parentDir.FullName + $directory.Name + ".zip"
}
Else
{
$zipFileName = $parentDir.FullName + "\" + $directory.Name + ".zip"
}
Set-Content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$shellApp = New-Object -ComObject Shell.Application
$zipFile = $shellApp.NameSpace($zipFileName)
If ($zipFile -eq $null)
{
T -text "Failed to get zip file object."
}
[int] $expectedCount = (Get-ChildItem $directory -Force -Recurse).Count
$expectedCount += 1 # account for the top-level folder
T -text "Copying $expectedCount items into file $zipFileName..."
$zipFile.CopyHere($directory.FullName)
# wait for CopyHere operation to complete
WaitForZipOperationToFinish $zipFile $expectedCount
T -text "Successfully created zip file for folder ($backupFullName)."
}
$lStr_GetSummaryInfoError = "Get Summary info error : {0}"
Function Get-VMSummary
{# .ExternalHelp MAML-VM.XML
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[parameter(ValueFromPipeline = $true)]
$VM="%" ,
[ValidateNotNullOrEmpty()]
$Server="." #May need to look for VM(s) on Multiple servers
)
Process {
if ($VM -is [String]) {$VM=(Get-VM -Name $VM -ComputerName $Server) }
if ($VM.count -gt 1 ) {[Void]$PSBoundParameters.Remove("VM") ; $VM | ForEach-object {Get-VMSummary -VM $_ @PSBoundParameters}}
if ($vm.__CLASS -eq 'Msvm_ComputerSystem') {
$VSMgtSvc=Get-WmiObject -computerName $Vm.__Server -NameSpace $HyperVNamespace -Class "MsVM_virtualSystemManagementService"
$result=$VSMgtSvc.GetSummaryInformation( @( (Get-VMSettingData $vm ).__Path ) , @(0,1,2,3,4,100,101,102,103,104,105,106,107,108))
if ($Result.ReturnValue -eq 0) {$result.SummaryInformation | foreach-object {
New-Object PSObject -Property @{
"Host" = $VM.__server ; "VMElementName" = $_.elementname
"Name" = $_.name ; "CreationTime" = $_.CreationTime
"EnabledState" = ([vmstate]($_.EnabledState)) ; "Notes" = $_.Notes
"CPUCount" = $_.NumberOfProcessors ; "CPULoad" = $_.ProcessorLoad
"CPULoadHistory" = $_.ProcessorLoadHistory ; "MemoryUsage" = $_.MemoryUsage
"GuestOS" = $_.GuestOperatingSystem ; "Snapshots" = $_.Snapshots.count
"Jobs" = $_.AsynchronousTasks ; "Uptime" = $_.UpTime
"UptimeFormatted" = $(if ($_.uptime -gt 0) {([datetime]0).addmilliseconds($_.UpTime).tostring("hh:mm:ss")} else {0} )
"Heartbeat" = $(if ($_.heartBeat) { [HeartBeatICStatus] $_.Heartbeat} else {$null} )
"FQDN" = ((get-vmkvp -vm $vm).FullyQualifiedDomainName)
"IpAddress" = ((Ping-VM $vm).NetworkAddress)}
}}
else {write-Warning ($lStr_GetSummaryInfo -f [ReturnCode]$result.returnValue )}
}
}
}
## Powershell Script to Shutdown and Export Hyper-V 2008 R2 VMs, one at a time.
## Written by Stan Czerno
## http://www.czerno.com/default.asp?inc=/html/windows/hyperv/cluster/HyperV_Export_VMs.asp
## I have modified his approach to suit our purposes
Function ExportMyVms
{
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[string]$Destination,
[string[]]$VMNames,
[string]$VMHost
)
process
{
## The script requires the PowerShell Management Library for Hyper-V for it to work.
## The PowerShell Management Library for Hyper-V can be downloaded at http://pshyperv.codeplex.com/
## Be sure to read the documentation before using:
## http://pshyperv.codeplex.com/releases/view/62842
## http://pshyperv.codeplex.com/releases/view/38769
## This is how I backup the VMs on my Two-Node Hyper-V Cluster. I can afford for my servers to be down while this is done and
## some of my other resources are clustered so there is minimum down time.
## I also do System State Backups, Exchange Backups and SQL Backups in addition.
## This script can be used on a Stand-Alone Hyper-V Server as well.
## Let me know if you have a better way of doing this as I am not a very good developer and new to Powershell.
## Get a list of all VMs on Node
#T -text "VM Names passed :$VMNames"
if ($VMNames)
{
if (($VMNames.Length) -gt 1)
{
# pass in a multiple-element string array directly
$VMs = Get-VM -Name $VMNames -ComputerName $VMHost
}
else
{
# turn a single-element string array back into a string
$VMNames = [string]$VMNames
#T -text "VM Names Resolving :$VMNames"
$VMs = Get-VM -Name "$VMNames" -ComputerName $VMHost
}
}
else
{
$VMs = Get-VM -ComputerName $VMHost
}
## only run through the list if there's anything in it
if ($VMs)
{
foreach ($VM in @($VMs))
{
$listOfVmNames += $VM.Name + ", "
}
$listOfVmNames = $listOfVmNames -replace "..$"
T -text "Attempting to backup the following VMs:"
T -text "$listOfVmNames"
T -text ""
Write-Host "Do not cancel the export process as it may cause unpredictable VM behavior" -ForegroundColor Yellow
## For each VM on Node, Save (if necessary), Export and Restore (if necessary)
foreach ($VM in @($VMs))
{
$VMName = $VM.Name
$summofvm = get-vm $VMName
$restartWhenDone = $false
$doexport = "yes"
$i = 1
if ($doexport -eq "yes")
{
$VMState = get-vm $VMName
#$VMEnabledState = $VMState.State
$VMExportingStatus = $VMState.Status
T -text "Exporting $VMName with state :$VMExportingStatus"
# Don't trigger export on already exporting VM's
if($VMExportingStatus -ne "Exporting virtual machine")
{
## If a folder already exists for the current VM, delete it.
if ([IO.Directory]::Exists("$($Destination)\$($VMName)"))
{
[IO.Directory]::Delete("$($Destination)\$($VMName)", $True)
}
T -text "Exporting $VMName"
## Begin export of the VM
export-vm $VMName -ComputerName $VMHost -path $Destination
## check to ensure the export succeeded
$exportedCount = (Get-ChildItem $Destination -Force -Recurse).Count
## there should be way more than 5 elements in the destination - this is to account for empty folders
if ($exportedCount -lt 5)
{
T -text "***** Automated export failed for $VMName *****"
T -text "***** Manual export advised *****"
}
}
else
{
T -text "Could not properly save state on VM $VMName, skipping this one and moving on."
}
}
}
}
else
{
T -text "No VMs found to back up."
}
}
}
## This is just a hand-made wrapper function that mimics the Tee-Object cmdlet with less fuss
## Plus, it makes our log file prettier
Function T
{
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[string]$text
)
process
{
Write-Host "$text"
$now = date
$text = "$now`t: $text"
Add-content -LiteralPath $Logfile -value $text
}
}
#Get-VMSummary -VM DEVELOPERIIS localhost
Backup-VMs -BackupDriveLetter Y -VMHost localhost -VMNames DEVELOPERIIS