#!/usr/bin/perl -w # # # vmbk.pl Version 2.0 # by Massimiliano Daneri mcse # 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.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 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 $isdiplay= undef; my $version = "2.1"; my $diskextension1 = "dsk"; my $diskextension2 = "vmdk"; my $vmfsdir = "/vmfs/"; 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; } # # backup vmx + cmos file # sub backupcfgfile{ my($isdisplay) = pop(@_); 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($isdisplay); } else{ ko($isdisplay);} print "\t\tCopy NVRAM file\t\t\t\t\t"; if (copy($cfgdirectory."/nvram",$vmhostbk."/nvram") ){ ok($isdisplay); } else{ ko($isdisplay); } } # # ftpconnection:export file to ftp server # sub ftpconnection{ my($isdisplay) = pop(@_); 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($isdisplay); } else { ko($isdisplay); } print "\t\t\tChange remote to base directory \t"; if ($ftpcon->cwd($ftpremlogdir)){ ok($isdisplay); } else { ko($isdisplay); } print "\t\t\tCreate remote vm directory \t\t"; if ($ftpcon->mkdir($remoteftpdir)){ ok($isdisplay); } else { ko($isdisplay); } print "\t\t\tChange remote to vm directory \t\t"; if ($ftpcon->cwd($remoteftpdir)){ ok($isdisplay); } else { ko($isdisplay); } print "\t\t\tChange remote directory \t\t"; if ($ftpcon->cwd($ftpremlogdir)){ ok($isdisplay); } else { ko($isdisplay); } chdir($vmhostbk); my @list = glob("*.".$exdsktype); print "\t\t\tSet Binary mode on ".$ftp." server\t"; if ($ftpcon->binary){ ok($isdisplay); } else { ko($isdisplay); } 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($isdisplay); unlink $file; #print "Delete file ".$file." on local \n"; }else { ko($isdisplay); } } $ftpcon->quit; rmdir($vmhostbk); print "\t\t\tDisconnect from ".$ftp." server\t\t"; ok($isdisplay); } # # 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); } # # help : display help message # # sub help { my ($isdisplay) =@_; header($isdisplay); print <disconnect(); } sub setcolor { my ($color,$isdisplay) =@_; if ($isdisplay) { print color($color); } } # # ok: show ok in green # # sub ok { my ($isdisplay) =@_; setcolor("green",$isdisplay); print "\t[ OK ]\n"; setcolor("reset",$isdisplay); } sub ko { my ($isdisplay) =@_; setcolor("red",$isdisplay); print "\t[ KO ]\n"; setcolor("reset",$isdisplay); } sub header{ my ($isdisplay) =@_; setcolor("BLUE",$isdisplay); 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",$isdisplay); } sub findOptionBackup{ my($cfg) =pop(@_); my($isdisplay)=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($isdisplay); return 1; } else { print "\tBackup option '$cfgval' detected, backup starting..\t"; ok($isdisplay); } } return 0; } getopts( 'lihat1234s:u:p:d:n:f:r:', \my %options ); #-s Server #-u user #-p password #-d destination directory #-o port #-d display #-l log #-t test #-n nfs #-i interactive #-a backup all #-a backup all my $isdisplay = ! exists $options{l} ; if ( exists $options{h} ) { help($isdisplay); } my $vmbk = undef; #if not specified -d parameter exit if ( exists $options{d} ) { $vmbk = $options{d}; } else { help($isdisplay); exit; } #reinserire if ((exists $options{s} || exists $options{f} ) && (!exists $options{u})){ help($isdisplay); exit; } my $smb =( exists $options{s} ) ? $options{s} : undef; my $nfs =( exists $options{n} ) ? $options{n} : undef; my $ftp =( exists $options{f} ) ? $options{f} : undef; my $disk = undef; my $user =( exists $options{u} ) ? $options{u} : undef; #my $servername = ( exists $options{s} ) ? $options{s}: "local"; my $servername = "local"; # s{s} my $passwd =( exists $options{p} ) ? $options{p} : ""; if (exists $options{u} && ! exists $options{p} ) { # ReadMode('noecho'); $passwd =""; } my $backupredo =exists $options{3} ; my $ftpremlogdir =( exists $options{r} ) ? $options{r} : undef; my $port = ( exists $options{o} ) ? $options{o} : 902; my $backupall = exists $options{a} ; my $istest = exists $options{t} ; my $isinteractive = exists $options{i} ; my $exdsktype = ( exists $options{1} ) ? "vmdk" : "dsk"; my $isdebug = exists $options{2}; my $noshowheader = exists $options{n} ; my $backupconfig = exists $options{4} ; if ($isdisplay) { system("clear"); print color("reset"); } print "Process started at "; system ("date"); if ($istest) { print color("BLUE")."****** TEST MODE ******".color("reset"); } print "\n"; my $mountcmd =undef; my $mount =""; if (defined($nfs) ){ $mountcmd = "mount -v -t nfs $nfs $vmbk"; $mount = qx/$mountcmd/; #print $mount,"\n"; #print length($mount),"\n"; #print substr($mount,1,4); if (length($mount)==0) { print "\nCannot mount NFS\n"; print $mountcmd,"\t"; ko($isdisplay); exit; } } if (defined($smb) ){ $mountcmd = "mount -t smbfs -o username=$user,password=$passwd $smb $vmbk "; $mount = qx/$mountcmd/; if (length($mount)!=0){ print "\nCannot mount smb\n"; print $mountcmd,"\t"; ko($isdisplay); exit; } if (length($mount)!=0){ print $mount,"\n"; } #sleep; } # Create a new VMware::Control::Server to connect to a remote server # To interact with a local server use: # # # which connects to localhost, using current logged-on user to port 902 # if ($noshowheader) { header($isdisplay); } print "Connection to Server ", $servername,"\t\t\t\t\t"; my $server = $servername ne "local" ? VMware::Control::Server::new($servername,$port,$user,$passwd): VMware::Control::Server::new(); # Establish a persistent connection with server if (!$server->connect()) { my ($errorNumber, $errorString) = $server->get_last_error(); ko($isdisplay); print "\nCannot connect to server: Error $errorNumber: $errorString\n"; exit; } else { ok($isdisplay); } # 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($isdisplay); } else { ko($isdisplay); } 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($isdisplay); print "VM connect error: ",$errorNumber, $errorString, "\n"; next NEXTGUEST; } print $ind ,") ",$vm->get("Config.displayName") , "\n"; } setcolor("yellow",$isdisplay); 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",$isdisplay); 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($isdisplay); print "VM connect error: ",$errorNumber, $errorString, "\n"; next NEXTGUEST; }else { ok($isdisplay); } #check for backup option if (findOptionBackup($isinteractive,$cfg)==1) { closeVMconnection($isdisplay,$vm); next NEXTGUEST; } my $displayname = $vm->get("Config.displayName"); #Define backup directory my $vmhostbk = $vmbk."vm-".$displayname."/" ; $vmhostbk=substspace($vmhostbk); print "\tVirtual Machine Name :\t"; setcolor("yellow",$isdisplay); print $displayname."\n"; setcolor("reset",$isdisplay); #backup vmx and cmos file if ($backupconfig){ print "\tBackup Configuration files :\n"; backupcfgfile($cfg,$vmhostbk,$isdisplay); } 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",$isdisplay); print "[ ",uc($vm->get("Status.power"))," ]\n"; setcolor("reset",$isdisplay); 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; } # one scsi disk found if ($noscsidevice == 1 ) { ok($isdisplay); $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; if ( ($vm->get("Config.$disk.deviceType") eq "scsi-hardDisk")){ my $vmhbadiskfile = $vm->get("Config.$disk.name"); if ($vm->get("Config.$disk.deviceType") eq "scsi-hardDisk") { #&& ((substr($vmhbadiskfile,(length($vmhbadiskfile)-3)) eq $diskextension1) || (substr($vmhbadiskfile,(length($vmhbadiskfile)-4)) eq $diskextension2))) { my $diskmode = $vm->get("Config.$disk.mode"); my $vmhbaredofile =$vmhbadiskfile .".REDO"; $vmhba = substr ($vmhbadiskfile,0,rindex($vmhbadiskfile,":")); $redodir = substhba($vmhba); $dskname=substr ($vmhbadiskfile,rindex($vmhbadiskfile,":")+1); $vmhbadiskfilenoext= substr ($dskname,0,rindex($dskname,".")); $redofile=$dskname.".REDO"; $redoredofile=$redofile.".REDO"; $redo = $vmfsdir.$redodir."/".$redofile; $redoredo = $vmfsdir.$redodir."/".$redoredofile; $redofilenosubsthba =$vmfsdir.$vmhba."/".$redofile; $redoredofilenosubsthba =$vmfsdir.$vmhba."/".$redoredofile; #undoable #nonpersistent #persistent if($isdebug) { print "\nDisk : $disk\n"; print "\nDisk Mode : $diskmode\n"; print "\nmvhbadiskfile is ".$vmhbadiskfile."\n"; print "\nmvhbaredodiskfile is ".$vmhbaredofile."\n"; print "\nredofile is ".$redofile."\n"; print "\nvmhba is ".$vmhba."\n"; print "\nredodir is ".$redodir."\n"; print "\ndskname is ".$dskname."\n"; print "\nRedo is ".$redo."\n"; print "\nRedo.Redo is ".$redoredo."\n"; print "\nredofile is ".$redofile."\n"; print "\nredoredofile is ".$redoredofile."\n"; print "\nredofilenosubsthba is ".$redofilenosubsthba."\n"; print "\nredoredofilenosubsthba is ".$redoredofilenosubsthba."\n"; print "\nvmhbadiskfilenoext is ".$vmhbadiskfilenoext."\n"; print "\n backupredo is $backupredo\n" } if ($vm->get("Status.power") eq "on") { 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)) { if (!$vm->add_redo($disk)) { my ($errorNumber, $errorString) = $vm->get_last_error(); ko($isdisplay); print "VM Add Redo error: $errorNumber: $errorString \n",color("reset"); #closeVMconnection($isdisplay,$vm); next NEXTDISK; } else { ok($isdisplay); } }else { ko($isdisplay); } } sleep(3); print "\t\tCheck backup destination directory.....\t\t"; # export directory ok($isdisplay); #printf $undelinedisplayname."\n"; #my $destfile = $vmhostbk.substspace($displayname).++$inc; my $destfile = $vmhostbk.$vmhbadiskfilenoext; #crea cartella backup se già esiste cancella il contenuto if (!mkdir($vmhostbk)) { 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 { unlink <$destfile*>; } ok($isdisplay); } print "\t\tStarting backup on directory \n\t\t"; my $sysstr = undef; #build command line for backup if ($backupredo) { print "Disk contain redo log file\n\t\t"; $sysstr = "/usr/sbin/vmkfstools -e \"$destfile.".$exdsktype."\" \"$vmhbaredofile\""; }else{ $sysstr = "/usr/sbin/vmkfstools -e \"$destfile.".$exdsktype."\" \"$vmhbadiskfile\""; } 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($isdisplay); 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($isdisplay); die "VM commit error: $errorNumber: $errorString \n",color("reset"); # closeVMconnection($isdisplay,$vm); next NEXTDISK; } else { ok($isdisplay); } }else { ko($isdisplay); } } if (defined($ftp) ){ print "\t\tStarting ftp Connection \n"; ftpconnection($ftp,$ftpremlogdir,"vm-".substspace($displayname),$user,$passwd ,$vmhostbk,$exdsktype,$isdisplay); print "\t\tEnding ftp Transfer \t\t\t\t"; ok($isdisplay); #my $sysstr = "ftp -s "$destfile.dsk" "$vmhbadiskfile\""; #da completare } } } } #if no scsi disk ko if ($noscsidevice < 100 ) { ko($isdisplay); } } else { setcolor("red",$isdisplay); print "[ ",uc($vm->get("Status.power"))," ]"; setcolor("reset",$isdisplay); } print "\n"; closeVMconnection($isdisplay,$vm); } # Kill the connection with the server. fine: $server->disconnect(); print "Disconnected from Server", $servername,"\t\t\t\t\t"; ok($isdisplay); if (defined($nfs) ){ system("umount -v $nfs "); } if (defined($smb) ){ system("umount -v $smb "); } print "\n"; system ("date"); print "\n";