Backing up a VM in windows can be done via powershell. The basic idea is:

  1. Stop the VM, so you free up the disk space which is used as virtual memory
  2. Create a VM checkpoint.
  3. Export the checkpoint to a temp folder, zip it as a archive file with the name of current date.
  4. Upload the Zip file to FTP server.
  5. Check the file names on FTP server, see if any are older than 30 days, delete if there are.
  6. Start the VM
  7. Delete the export folder, delete the archive file, delete the checkpoint.
  8. 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”