Backing up a VM in windows can be done via powershell. The basic idea is:
- Stop the VM, so you free up the disk space which is used as virtual memory
- Create a VM checkpoint.
- Export the checkpoint to a temp folder, zip it as a archive file with the name of current date.
- Upload the Zip file to FTP server.
- Check the file names on FTP server, see if any are older than 30 days, delete if there are.
- Start the VM
- Delete the export folder, delete the archive file, delete the checkpoint.
- Email the job status to admin.
Module code:
#Add below file to powershell as a module, then use below script to backup the VMs to a ftp server: Function Add-VmBackup { param( [String]$vm = $null, [String]$backupLocation = $null, [String]$ftpBackupServer = $null, [Int32]$ftpBackupServerPort = 21, [String]$ftpUsername = $null, [String]$ftpPassword = $null, [String]$ftpBackupPath = "" ) function Archive($sourcedir, $zipFileName ) { Add-Type -Assembly System.IO.Compression.FileSystem; $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal; [System.IO.Compression.ZipFile]::CreateFromDirectory($sourcedir, $zipfilename, $compressionLevel, $false); } #local variables $ftpBackupServer = $ftpBackupServer.ToLower() if( -not $ftpBackupServer.StartsWith("ftp://") ) { $ftpBackupServer = "ftp://" + $ftpBackupServer.trim('/') } set ftpBackupPath ("/" + $ftpBackupPath.Trim('/') + "/" + $vm); set-location $backupLocation $backupDate = ([System.DateTime]::Now.ToString("yyyyMMdd")) $snapshotName = ("bu_" + $backupDate) set snapshotArchiveFilePath ([System.IO.Path]::Combine($backupLocation, ($vm + "-" + $backupDate + ".zip"))) set backupLocation ([System.IO.Path]::Combine($backupLocation, $vm + "-" + $backupDate)) try { if( test-path $backupLocation ) { remove-item -recurse -force $backupLocation } New-item $backupLocation -type Directory | out-null checkpoint-vm $vm -SnapshotName $snapshotName -Passthru | Export-VMSnapshot -path $backupLocation if( test-path $snapshotArchiveFilePath ) { del $snapshotArchiveFilePath } Archive $backupLocation $snapshotArchiveFilePath remove-item -Recurse -Force $backupLocation #FTP upload #Ftp $snapshotArchiveFilePath $ftpBackupServer $ftpBackupServerPort $ftpUsername $ftpPassword $ftpBackupPath ($backupDate + ".zip") #remove-item $snapshotArchiveFilePath Get-VMSnapshot -vmname $vm | Remove-VMSnapshot } catch { echo $_.Exception.Message; # handle error here } }
save it as C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Backup_VM.psm1
Use below code to backup your vm1 and vm2
On the FTP server, all VMs are stored under the folder VMs/vm_name/date.zip
##don't change this script, it is been tested working perfectly!!!### #change the vm names in the line 4, the script will loop in the order import-module C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Backup_VM.psm1 $vms=@("vm1","vm2") $ftpserver = "ftp://192.168.90.202" $local_tmp_folder = "E:\tmp\" $ftpUsername = "backup_admin" $ftpPassword = "yourpassword" # SMTP configuration: username, password & so on $email_username = "[email protected]"; $email_password = "yourpassword"; $email_smtp_host = "smtp.gmail.com"; $email_smtp_port = 587; $email_smtp_SSL = 1; $email_from_address = "[email protected]"; $email_to_addressArray = @("[email protected]"); $continueLoop = $true #$stoppedVm = @{} foreach ($vm in $vms) { try { if( $continueLoop ) { stop-vm $vm $continueLoop = !$LASTEXITCODE } } catch { throw $_ } } ##strat all the VMs $continueLoop = $true foreach ($vm in $vms) { try { if( $continueLoop ) { Add-VmBackup -vm $vm -backupLocation E:\tmp -ftpBackupServer $ftpserver -ftpUsername $ftpUsername -ftpPassword $ftpPassword -ftpBackupPath '/VMs/' start-vm $vm $continueLoop = !$LASTEXITCODE ##Upload the VM zip file to ftp server $backupDate =([System.DateTime]::Now.ToString("yyyyMMdd")) $client = New-Object System.Net.WebClient $client.Credentials = New-Object System.Net.NetworkCredential($ftpUsername, $ftpPassword) $ftppath =$ftpserver +"/VMs/" + $vm + "/" + $backupDate + ".zip" echo "The location to upload is : "$ftppath echo "`r`n" $local_vm_backup_zip = $local_tmp_folder + $vm + "-" + $backupDate + ".zip" echo "Local archive is: "$local_vm_backup_zip echo "`r`n" #$client.UploadFile($ftppath,[uri]::EscapeDataString($local_vm_backup_zip)) $client.UploadFile( (new-object System.Uri($ftppath)), (get-item $local_vm_backup_zip).FullName ) | Out-Null; #delete the zip file created remove-item $local_vm_backup_zip } } catch { throw $_ } } ##delete old backups if they are older than 30 days $deleteThreshhold= [System.DateTime]::Today.Subtract((new-object System.TimeSpan(30, 0, 0, 0))).ToString("yyyyMMdd") echo "Files before $deleteThreshhold should be deleted" $ftpfolderpath = $ftpserver + "/VMs/" + $vm +"/" #function to view the files in the FTP Server function View-Files-in-FTP-Server ($url,$credentials) { $request = [Net.WebRequest]::Create($url) $request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory if ($credentials) { $request.Credentials = $credentials } $response = $request.GetResponse() $reader = New-Object IO.StreamReader $response.GetResponseStream() $reader.ReadToEnd() $reader.Close() $response.Close() } $ListFiles = View-Files-in-FTP-Server -url $ftpfolderpath -credentials $client.Credentials #echo $ListFiles #will compare $deleteThreshhold and file name to determin if backup is old enough to delete $files = ($ListFiles -split "`r`n") # echo $files $Archive_uploaded = $false foreach($file in $files) { if($file){ $dateoffile = $file.substring(0,8) $vm_delete = [System.Net.FtpWebRequest]::create($ftpfolderpath + $file) $vm_delete.Credentials = New-Object System.Net.NetworkCredential($ftpUsername, $ftpPassword) if ($dateoffile -lt $deleteThreshhold){ echo "$file need to be deleted" echo "delting $file..." echo $vm_delete $vm_delete.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile $vm_delete.GetResponse() } else{ #echo "$file don't need to be deleted" } #Check if file is uploaded if ($dateoffile -eq $backupDate){ echo "$file is uploaded" $Archive_uploaded = $true } else{ #echo "$file don't need to be deleted" } } } ####### Email a notification to admins ######## ##Send success email if the archive zip file is uploaded. if($Archive_uploaded){ ########message object $message = new-object Net.Mail.MailMessage; $message.From = $email_from_address; foreach ($to in $email_to_addressArray) { $message.To.Add($to); } $message.Subject = ("[Notice] VM Backup on" + $env:computername + " Is completed and"); $message.Body = "Hello there, `r`n"; $message.Body += "This is an automatic e-mail message "; $message.Body += "sent by VMBackup Powershell script "; $message.Body += ("to inform you that below Virtual Machine(s): `r`n"+ $vms +" `r`n On the Hyper-V computer "+ $env:computername + " is completed! "); $message.Body += "----------------------------------------------"; $message.Body += "`r`n"; $message.Body += ("Machine HostName: " + $env:computername + " `r`n"); $message.Body += "`r`n"; $message.Body += "----------------------------------------------"; $message.Body += "`r`n"; $message.Body += " "; $smtp = new-object Net.Mail.SmtpClient($email_smtp_host, $email_smtp_port); $smtp.EnableSSL = $email_smtp_SSL; $smtp.Credentials = New-Object System.Net.NetworkCredential($email_username, $email_password); $smtp.send($message); $message.Dispose(); }
save this to C:\scripts\VM_backup.ps1
Cerate a schedule task to run the script
Actions: start a program,
- Program/script: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- Add arguments: -Command “& C:\scripts\VM_backup.ps1 -a 2 -b 3”