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 aSendEmailat the end of theForEach VM(Check second code block below) - [x] Switch calles
-OptimizeVHD: this features uses theOptimize-VHDfunction 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 VMand 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!
Thank you for this, very interesting! I'll work on getting these enhancements implemented in future.
I'm adding all the suggestions here except for the Wake On Lan option for the time being. WOL feels a little out of scope but I'm willing to add it if there's a demand.
Closing for my sanity.