#!/usr/bin/perl -w # # # vmbk.pl Version 2.13 # by Massimiliano Daneri mcse vcp # email: m.daneri@evoluzione.com # # This script take all of active virtual machines # on the server specified by hostname and copy it on disk. # you can override for each running virtual # machine in the target VM list with "backup = none|manual" # in its configuration file is powered on or resumed. The script # Version 2.13 # Skip raw disk if vm running # modified scirpt structure # Version 2.12 # Ftp bug fix # Debug modified # Version 2.11 # Bug Fix # Version 2.1 # Add support to undoable nonpersistent disk # Add backup vm config file # Version 2.0 # Add support to vmhba partition without label # Version 1.91 # Add support to ftp # Add chose to select from dsk to vmdk # Add option to hide header show # Version 1.90 #first public version BEGIN { if ($^O eq "MSWin32") { @INC = ( # Set the path to your VmPerl Scripting directory if different 'C:\Program Files\VMware\VMware VmPerl Scripting API\perl5\site_perl\5.005', 'C:\Program Files\VMware\VMware VmPerl Scripting API\perl5\site_perl\5.005\MSWin32-x86'); }else { push(@INC, ("/usr/lib/perl5/site_perl/5.6.0/i386-linux/", "/usr/lib/perl5/5.6.1", ".")); } } use VMware::Control; use VMware::Control::Server; use VMware::Control::VM; #use VMware::VmPerl; #use VMware::VmPerl::VM; #use VMware::VmPerl::Server; #use VMware::VmPerl::ConnectParams; use Term::ANSIColor; use strict; use Getopt::Std; use Net::FTP; use File::Copy; my $isdisplay= 0; my $version = "2.13"; #vstring my $diskextension1 = "dsk"; my $diskextension2 = "vmdk"; my $vmfsdir = "/vmfs/"; my $prenamedir = "vm-"; my $isdebug =0; my $isdebug2 =0; sub readVMConfigs { my(@vmconfigFiles) = (); my($vmlistFile) = pop(@_); if (open(VMLISTFILE, "<$vmlistFile")) { while () { next if /^$/; s/\#.*//; my(@arr) = split; if ($#arr >= 1 && $arr[0] eq "config") { my($cfgfileName) = $arr[1]; # drop any leading and trailing quotes, confuses open $cfgfileName =~ s/^([^"]*)\"(.*)\"([^"]*)$/$1$2$3/; push(@vmconfigFiles, $cfgfileName); } } close(VMLISTFILE); } else { print STDERR "Can't open VM list file \"$vmlistFile\"\n"; } return @vmconfigFiles; } sub debug { my($var) = pop(@_); my($varname) = pop(@_); if($isdebug) { print STDERR "\n$varname : $var\n"; } if ($isdebug2) { my $continue= ; if ($continue eq "n"){ exit; } } } # # backup vmx + cmos file # sub backupcfgfile{ my($vmhostbk) = pop(@_); my($vmhostcfg) = pop(@_); my $cfgfile=substr($vmhostcfg,rindex($vmhostcfg,'/')); my $cfgdirectory=substr($vmhostcfg,0,rindex($vmhostcfg,'/')); print "\t\tCopy config file\t\t\t\t"; if (copy($vmhostcfg,$vmhostbk."/".$cfgfile)) { ok(); } else{ ko();} print "\t\tCopy NVRAM file\t\t\t\t\t"; if (copy($cfgdirectory."/nvram",$vmhostbk."/nvram") ){ ok(); } else{ ko(); } } # # ftpconnection:export file to ftp server # sub ftpconnection{ my($exdsktype) = pop(@_); my($vmhostbk) = pop(@_); my($password) = pop(@_); my($user) = pop(@_); my($remoteftpdir) = pop(@_); my($ftpremlogdir) = pop(@_); my($ftp) = pop(@_); my $ftpcon = Net::FTP->new($ftp); die "\t\t\tCouldn't FTP to \n" unless($ftp); print "\t\t\tConnect to ".$ftp." server\t\t"; if ($ftpcon->login($user,$password)) { ok(); } else { ko(); } print "\t\t\tChange remote to base directory \t"; if ($ftpcon->cwd($ftpremlogdir)){ ok(); } else { ko(); } print "\t\t\tCreate remote vm directory \t\t"; if ($ftpcon->mkdir($remoteftpdir)){ ok(); } else { ko(); } print "\t\t\tChange remote to vm directory \t\t"; if ($ftpcon->cwd($remoteftpdir)){ ok(); } else { ko(); } chdir($vmhostbk); my @list = glob("*.".$exdsktype); print "\t\t\tSet Binary mode on ".$ftp." server\t"; if ($ftpcon->binary){ ok(); } else { ko(); } print "\t\tTransfer file \n"; for my $file (@list) { $ftpcon->delete($file); #print "Delete file ".$file." on ftp server \n"; print "\t\t\t".$file."\t"; if ($ftpcon->put($file,$file)) { ok(); unlink $file; #print "Delete file ".$file." on local \n"; }else { ko(); } } $ftpcon->quit; rmdir($vmhostbk); print "\t\t\tDisconnect from ".$ftp." server\t\t"; ok(); } # # findOption: look in the configfile for $cfgoption = $val # return $val if found, empty string otherwise. # sub findOption { my($val); my($cfgoption) = pop(@_); my($cfgfilename) = pop(@_); if (open(CFGFILE, "<$cfgfilename")) { # look for "$string = " in the config file while () { next if /^$/; s/\#.*//; my($status, $name, $value, $start, $end) = &parseLine($_); if (defined($status) && $status == 1) { $name =~ tr/A-Z/a-z/; if ($name eq $cfgoption) { $val = $value; $val =~ tr/A-Z/a-z/; last; } } } close(CFGFILE); } return $val; } # # parseLine: parse a line read from the configuration file. Adapted from # bora/apps/hconfig/perlroot/Config.pm. # sub parseLine { $_ = pop(@_); if (/^\s*(\#.*)?$/) { return (0); } elsif (/^((\s*(\S+)\s*=\s*)(([\"]([^\"]*)[\"])|(\S+)))\s*(\#.*)?$/) { my $prefix1 = $2; my $prefix2 = $1; my $name = $3; my $value; if (defined($6)) { $value = $6; } else { $value = $7; } return (1, $name, $value, length($prefix1), length($prefix2)); } return (undef); } # # substspace: parse a line to subst space with underline # # sub substspace { my($string) = pop(@_); my $newstring = undef; # subst space with underline foreach ($newstring,$string) { $string=~s/ /_/; #printf $vmhostbk ."\n"; } $string=~s/ /_/; return ($string); #print "\n the string is ".$string."\n"; } # # substhba: parse a line to subst : with \: # # sub substhba { my($string) = pop(@_); # subst : with \: $string=~s/:/\\:/g ; # print "\n the string is ".$string."\n"; return ($string); } # # closeVMconnection: close connection to vm machine # sub closeVMconnection { my($vm) =pop(@_); print "\tClose connection to guest \t\t\t\t"; ok(); $vm->disconnect(); } sub setcolor { my ($color) =@_; if ($isdisplay) { print color($color); } } # # ok: show ok in green # # sub ok { setcolor("green"); print "\t[ OK ]\n"; setcolor("reset"); } sub ko { setcolor("red"); print "\t[ KO ]\n"; setcolor("reset"); } sub header{ setcolor("BLUE"); print "vmbk.pl Version ".$version."\n"; print "Massimiliano Daneri mcse\n"; print "for support send email to\n"; print "m.daneri\@evoluzione.com\n"; setcolor("reset"); } sub findOptionBackup{ my($cfg) =pop(@_); #check for backup option my($cfgval) = &findOption($cfg, "backup"); if (defined $cfgval) { if ($cfgval eq "none" || ($cfgval eq "manual" and not $isdisplay )) { print "\tSkipping backup, option '$cfgval' detected\t\t\t"; ko(); return 1; } else { print "\tBackup option '$cfgval' detected, backup starting..\t"; ok(); } } return 0; } # # help : display help message # # sub help { header; print <connect()) { my ($errorNumber, $errorString) = $server->get_last_error(); ko(); print "\nCannot connect to server: Error $errorNumber: $errorString\n"; exit; } else { ok(); } # Obtain a list containing every config file path registered with the # server. print "\nEnumerating Guest on Server ", $servername,"\t\t\t\t"; my @list = $server->enumerate(); if ($list[1] ) { ok(); } else { ko(); } my @vmdobackup; my $ind=0; if($isinteractive) { NEXTGUESTINT: foreach my $cfg (@list) { $ind=$ind+1; $vmdobackup[$ind]=0; my $vm = VMware::Control::VM::new($server, $cfg); if (!$vm->connect()) { my ($errorNumber, $errorString) = $vm->get_last_error(); ko(); print "VM connect error: ",$errorNumber, $errorString, "\n"; next NEXTGUEST; } print $ind ,") ",$vm->get("Config.displayName") ."\t\t\t\t\t"; setcolor("green"); print "[ ",uc($vm->get("Status.power"))," ]\n"; setcolor("reset"); } setcolor("yellow"); print "Select each vm do you have to backup"; print " in the following form a b c "; print "\nwhere a b c are number of vm"; print "\nWhich VM (q for exit)? "; setcolor("reset"); my $opt = ; if ($opt eq "q\n"){ exit; } my @indici = split(/ /,$opt); foreach my $indopt(@indici){ $vmdobackup[$indopt]=1; } } $ind=0; NEXTGUEST: foreach my $cfg (@list) { $ind=$ind+1; if ($isinteractive and $vmdobackup[$ind]==0){ next NEXTGUEST; } print "\tConnecting to Guest with config file \n\t\t"; print $cfg; print "\n\t\t\t\t\t\t\t\t"; my $vm = VMware::Control::VM::new($server, $cfg); if (!$vm->connect()) { my ($errorNumber, $errorString) = $vm->get_last_error(); ko(); print "VM connect error: ",$errorNumber, $errorString, "\n"; next NEXTGUEST; }else { ok(); } #check for backup option if (findOptionBackup($isinteractive,$cfg)==1) { closeVMconnection($vm); next NEXTGUEST; } my $displayname = $vm->get("Config.displayName"); #Define backup directory my $vmhostbk = $vmbk.$prenamedir.$displayname."/" ; $vmhostbk=substspace($vmhostbk); debug("displayname",$displayname); debug("vmhostbk",$vmhostbk); print "\tVirtual Machine Name :\t"; setcolor("yellow"); print $displayname."\n"; setcolor("reset"); #make folder and erase file print "\t\tCheck backup destination directory.....\t\t"; # export directory if (!mkdir($vmhostbk)) { ok(); print "\t\tErase old backup file .....\t\t\t "; #delete previous backup file if ($istest){ print color("BLUE")."\n\t\t****** TEST MODE ******\t\t\t\t".color("reset");; }else { chdir($vmhostbk); unlink <*>; } ok(); }else { ko(); print "\t\tCreate backup destination directory\t\t"; ok(); } #backup vmx and cmos file if ($backupconfig){ print "\tBackup Configuration files :\n"; backupcfgfile($cfg,$vmhostbk); } print "\tStatus: \t\t\t\t\t\t\t"; if (($vm->get("Status.power") eq "on" and not $isinteractive) or ($isinteractive and $vmdobackup[$ind]) or ($backupall)) { setcolor("green"); print "[ ",uc($vm->get("Status.power"))," ]\n"; setcolor("reset"); print "\tEnumerating SCSI Disk\t\t\t\t\t"; my $noscsidevice = 0; my @devices = @{ $vm->get("Status.devices") }; my $inc=-1; NEXTDISK: foreach $disk (@devices) { if ($disk =~ "ethernet" || $disk !~ ":"){ ++$noscsidevice; next; } debug("Disk ", $disk); # one scsi disk found if ($noscsidevice == 1 ) { ok(); $noscsidevice = 100; #this because you cannot have 100 device } my $vmhbadiskfile = undef; my $vmhbaredofile = undef; #my $diskfileext =undef; my $redo = undef; my $redoredo = undef; my $redofile = undef; my $redoredofile = undef; my $redodir = undef; my $dskname = undef; my $vmhba = undef; my $redofilenosubsthba = undef; my $redoredofilenosubsthba = undef; my $vmhbadiskfilenoext = undef; my $vmhbadiskfileext = undef; if ( ($vm->get("Config.$disk.deviceType") eq "scsi-hardDisk")){ my $vmhbadiskfile = $vm->get("Config.$disk.name"); debug("mvhbadiskfile",$vmhbadiskfile); my $diskmode = $vm->get("Config.$disk.mode"); debug("Disk Mode ", $diskmode); my $vmhbaredofile =$vmhbadiskfile .".REDO"; debug("mvhbaredodiskfile",$vmhbaredofile); $vmhba = substr ($vmhbadiskfile,0,rindex($vmhbadiskfile,":")); debug("vmhba",$vmhba); $redodir = substhba($vmhba); debug("redodir",$redodir); $dskname=substr ($vmhbadiskfile,rindex($vmhbadiskfile,":")+1); debug("dskname",$dskname); $vmhbadiskfilenoext= substr ($dskname,0,rindex($dskname,".")); debug("vmhbadiskfilenoext",$vmhbadiskfilenoext); $vmhbadiskfileext= substr ($dskname,rindex($dskname,".")+1); debug("vmhbadiskfileext",$vmhbadiskfileext); $redofile=$dskname.".REDO"; debug("redofile",$redofile); $redoredofile=$redofile.".REDO"; debug("redoredofile",$redoredofile); $redo = $vmfsdir.$redodir."/".$redofile; debug("Redo",$redo); $redoredo = $vmfsdir.$redodir."/".$redoredofile; debug("RedoRedo",$redoredo); $redofilenosubsthba =$vmfsdir.$vmhba."/".$redofile; debug("redofilenosubsthba",$redofilenosubsthba); $redoredofilenosubsthba =$vmfsdir.$vmhba."/".$redoredofile; debug("redoredofilenosubsthba",$redoredofilenosubsthba); #undoable #nonpersistent #persistent if ($vm->get("Config.$disk.deviceType") eq "scsi-hardDisk") { print "\t\tVMDisk $disk \n"; if ($vm->get("Status.power") eq "on") { if (($vmhbadiskfileext ne $diskextension1) && ($vmhbadiskfileext ne $diskextension2)) { print "\t\t*********Raw Partition Disk \n\t\t*********Skip backup\t\t\t\t"; ko(); next NEXTDISK; } print "\t\tAdd REDO log \t\t\t\t\t"; my $mount= substr($vmhbadiskfile,0,rindex($vmhbadiskfile,':')); if (!(-e $vmfsdir.$mount) ){ print "\t\tMount ".$mount. " on ". $vmfsdir.$mount."\t\t"; print "\n"; system("mount-vmfs ".$mount); } # check if disk is redoable if ((!(-e $redofilenosubsthba) && ($diskmode eq "persistent")) || (!(-e $redoredofilenosubsthba) && $backupredo && ($diskmode ne "persistent"))) { if (!$vm->add_redo($disk)) { my ($errorNumber, $errorString) = $vm->get_last_error(); ko(); print "VM Add Redo error: $errorNumber: $errorString \n",color("reset"); #closeVMconnection($vm); next NEXTDISK; } else { ok(); } }else { ko(); } } sleep(3); print "\t\tCheck backup destination directory.....\t\t"; # export directory ok(); my $destfile = $vmhostbk.$vmhbadiskfilenoext; debug("destfile", $destfile); print "\t\tStarting backup on directory \n\t\t"; my $sysstr = undef; #build command line for backup if ($backupredo && ($diskmode ne "persistent")) { print "Disk contain redo log file\n\t\t"; $sysstr = "/usr/sbin/vmkfstools -e \"$destfile.".$exdsktype."\" \"$vmhbaredofile\""; debug(" sysstr ", $sysstr); }else{ $sysstr = "/usr/sbin/vmkfstools -e \"$destfile.".$exdsktype."\" \"$vmhbadiskfile\""; debug(" sysstr" ,$sysstr); } printf "%26s.. \n\t\t",$destfile; #Esegue il backup if ($istest){ print color("BLUE")."****** TEST MODE ******".color("reset"); }else { system($sysstr); } print "\n\t\tStatus Backup \t\t\t\t\t"; ok(); if ($vm->get("Status.power") eq "on") { print "\t\tCommit REDO log \t\t\t\t"; # Commit REDO on Disk # check if a REDO log esist if (((-e $redofilenosubsthba) && ($diskmode eq "persistent")) || ((-e $redoredofilenosubsthba) && $backupredo)) { if (!$vm->commit($disk,0,0,1)) { my ($errorNumber, $errorString) = $vm->get_last_error(); ko(); die "VM commit error: $errorNumber: $errorString \n",color("reset"); # closeVMconnection($vm); next NEXTDISK; } else { ok(); } }else { ko(); } } if (defined($ftp) ){ print "\t\tStarting ftp Connection \n"; ftpconnection($ftp,$ftpremlogdir,$prenamedir.substspace($displayname),$user,$passwd ,$vmhostbk,$exdsktype); print "\t\tEnding ftp Transfer \t\t\t\t"; ok(); #my $sysstr = "ftp -s "$destfile.dsk" "$vmhbadiskfile\""; #da completare } } } } #if no scsi disk ko if ($noscsidevice < 100 ) { ko(); } } else { setcolor("red"); print "[ ",uc($vm->get("Status.power"))," ]"; setcolor("reset"); } print "\n"; closeVMconnection($vm); } # Kill the connection with the server. fine: $server->disconnect(); print "Disconnected from Server", $servername,"\t\t\t\t\t"; ok(); if (defined($nfs) ){ system("umount -v $nfs "); } if (defined($smb) ){ system("umount -v $smb "); } print "\n"; system ("date"); print "\n";