HyperV-Backup-Utility
HyperV-Backup-Utility copied to clipboard
Some feature requests
First of all let me say i really like you're way of scripting and love to see your enthusiasm in your work.
Last year i've made my own version of you script then because i needed to be more lightweight and there were a few things not available then like the -LowDisk
switch. But today i've check what you've been working at and i'm impressed, it almost fits my need and i know it's a lot to ask (since i can do it myself) but could you consider to add these features?
- [x] Switch called
-SendStatusUpdate
: this features send after every VM a email with the status of that VM export. For example, in my case i have a few VM's that are TB's and the script runs at night. When it's morning i can see due to the emails where the backup script is in the process. I simply put aSendEmail
at the end of theForEach VM
(Check second code block below) - [x] Switch calles
-OptimizeVHD
: this features uses theOptimize-VHD
function before copying it to the backup destination. Example of the code i use inside theForEach VM
:
#Optimze the VHDX
Try {
Write-Log -Type Info -Evt "Optimizing VHD(s)..."
$VMVHDS = Get-VHD -Path $($Vm | Get-VMHardDiskDrive | Select-Object -ExpandProperty "Path")
#Loop through each VHD(X) file of an VM an optimize it
ForEach ($VHD in $VMVHDS) {
#Now optimize the VHD to save space and therefore shorten the copy time
Write-Log -Type Info -Evt "Used space before optimizing VHD [$($VHD.Path)] = $([math]::ceiling((Get-VHD -Path $VHD.Path).FileSize / 1GB )) GB"
Optimize-VHD -Path "$($VHD.Path)" -Mode Full
Write-Log -Type Info -Evt "Used space after optimizing VHD [$($VHD.Path)] = $([math]::ceiling((Get-VHD -Path $VHD.Path).FileSize / 1GB )) GB"
$intTotalDisksSize += (Get-VHD -Path $VHD.Path).FileSize
}
Write-Log -Type Info -Evt "Done optimizing VHD(s)"
}
Catch {
Write-Log -Type Err -Evt "Error during Optimize-VHD: $($_.Exception.Message)"
}
- [x] Print the duration of how long it took per VM to back. In my script i simply compare time from the beginning of the
ForEach VM
and in one of the last lines print the duration. This gives me a pretty good idea how long it takes per VM. For example:
Write-Log -Type Info ""
Write-Log -Type Info "-------------------------- Processing VM: $Vm --------------------------------"
$StartTime = $(get-date)
... do stuff ...
$elapsedTime = $(get-date) - $StartTime
$totalTime = "{0:HH:mm:ss}" -f ([datetime]$elapsedTime.Ticks)
SendMail "VM $Vm done" "Processed VM: $Vm in $totalTime"
Write-Log -Type Info "-------------------------- Done processing VM: $Vm in $totalTime --------------------------------"
Write-Log -Type Info ""
- [x] Switch -PrintVMInfo: this features simply printout all info it could find. For example what i use:
## Print info per VM.
ForEach ($Vm in $Vms) {
#Get SystemInfo
Try {
#Log VM specs
$VhdSize = Get-VHD -Path $($Vm | Get-VMHardDiskDrive | Select-Object -ExpandProperty "Path") | Select-Object @{Name = "FileSizeGB"; Expression = { [math]::ceiling( $_.FileSize / 1GB ) } }, @{Name = "M
Write-Output "VM [$Vm] has [$((Get-VMProcessor $Vm).Count)] CPU cores, [$([math]::ceiling((Get-VMMemory $Vm).Startup / 1gb))GB] RAM and Storage [CurrentFileSizeGB = $($VhdSize.FileSizeGB)GB - MaxSizeG
}
Catch {
Write-Error "Error during Systeminfo: $($_.Exception.Message)"
}
}
- [ ] Extra funtions start Start and Stop a backupserver. This is something i have in my script because when the backuopserver isn't needed it is shutdown. (For security and power reasons). Snippit of the code:
function Invoke-WakeOnLan {
param
(
# one or more MACAddresses
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
# mac address must be a following this regex pattern:
[ValidatePattern('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$')]
[string[]]
$MacAddress
)
begin {
# instantiate a UDP client:
# Support from PowerShell V5.X $UDPclient = [System.Net.Sockets.UdpClient]::new()
$UDPclient = New-Object -TypeName System.Net.Sockets.UdpClient
}
process {
foreach ($_ in $MacAddress) {
try {
$currentMacAddress = $_
# get byte array from mac address:
$mac = $currentMacAddress -split '[:-]' |
# convert the hex number into byte:
ForEach-Object {
[System.Convert]::ToByte($_, 16)
}
#region compose the "magic packet"
# create a byte array with 102 bytes initialized to 255 each:
$packet = [byte[]](, 0xFF * 102)
# leave the first 6 bytes untouched, and
# repeat the target mac address bytes in bytes 7 through 102:
6..101 | Foreach-Object {
# $_ is indexing in the byte array,
# $_ % 6 produces repeating indices between 0 and 5
# (modulo operator)
$packet[$_] = $mac[($_ % 6)]
}
#endregion
# connect to port 400 on broadcast address:
$UDPclient.Connect(([System.Net.IPAddress]::Broadcast), 4000)
# send the magic packet to the broadcast address:
$null = $UDPclient.Send($packet, $packet.Length)
Write-Log -Type Info -Evt "Sent magic packet to $currentMacAddress..."
}
catch {
Write-Log -Type Err -Evt "Unable to send ${mac}: $_"
}
}
}
end {
# release the UDF client and free its memory:
$UDPclient.Close()
$UDPclient.Dispose()
}
}
... later on ...
# Let's start the BackupServer if it's not running
If (Test-Connection -ComputerName "IP/Hostname" -Count 2 -Delay 5 -Quiet ) {
#Backup server responded meaning it's already online.
Write-Log -Type Info -Evt "BackupServer is online, waiting 30 seconds just to be sure..."
Start-Sleep 30
}
else {
Write-Log -Type Info -Evt "Sending magic packet to BackupServer [$BackupServerMacAddress]"
Invoke-WakeOnLan -MacAddress "$BackupServerMacAddress" -Verbose
#Now wait until backupserver is online
Write-Log -Type Info -Evt "Waiting for BackupServer to come online..."
If (Test-Connection -ComputerName "IP/Hostname" -Count 12 -Delay 10 -Quiet ) {
#Backupserver responded now give some extra time for Windows to fully be up and running
Write-Log -Type Info -Evt "BackupServer is online, waiting 2 more minutes for Windows to complete..."
Start-Sleep 120
}
else {
Write-Log -Type Err -Evt "Backupserver failed to respond within 2 minutes. BACkUP SCRIPT WILL NOT CONTINUE"
$MailBody = Get-Content -Path $Log | Out-String
SendMail "VM Backup: Failed to start backupserver" $MailBody
Write-Log -Type Info -Evt "Log finished"
Exit
}
}
... When done, shutdown ...
## Now shutdown the backup server
Try {
Write-Log -Type Info -Evt "Waiting for BackupServer to shutdown..."
Stop-Computer -ComputerName "IP/Hostname" -Credential $credObject -Force
#Wait until backupserver is down
While ( Test-Connection "IP/Hostname" -Quiet -Count 1) {
Start-Sleep -Seconds 10
#Check if we are waiting more than two minutes
if ($timer.Elapsed.TotalSeconds -gt 120) {
Write-Log -Type Err -Evt "Backupserver shutdown did not complete before timeout period."
Break
}
}
#Double check if server is down or Watchdog has kicked in
if (-Not (Test-Connection "IP/Hostname" -Quiet -Count 2)) {
Write-Log -Type Info -Evt "BackupServer succesfully shutdown"
}
}
catch {
Write-Log -Type Err -Evt "Error backup shutdown: $($_.Exception.Message)"
}
Those are the points i have, not to bad right ;-)
Again, thanks for all your effort so far!