VMware Cloud Community
ChrisInTexas
Contributor
Contributor

Free ESXi Backup Solution for Windows

I have spent the last few days trying to find a free backup solution to the newly free ESXi for windows only enviroments (in particular Windows XP). The solution for me was the following:

1. Installing Windows Services for UNIX (WSFU)

2. Copying the ESXi Server password and group files to Windows

3. Configuring WSFU for accepting ESX Server connections

4. Sharing the Windows folder for NFS compatibility

5. Configuring the ESXi Server to mount the Window NFS Share as Datastore.

6. Setup Backup Script

Attached is the complete steps.

I take NO credit for any of this. This is just a complation of others work formated to suit my needs and felt others could benift from it as I have.

by Jason Mattox from Vizioncore (direct copy of his work, I just added more information to make it work in Windows XP)

(NFS Server port information)

by robink (The backup script)

(ssh on ESXi)

Tags (3)
0 Kudos
522 Replies
Dave_Mishchenko
Immortal
Immortal

oem.tgz along with the other tgz files are extracted during the initial Loading VMware Hypervisor screen so it'l long before crond is started.

0 Kudos
lamw
Community Manager
Community Manager

The script has now been updated to allow the names of the Virtual Machines to contain spaces:

http://communities.vmware.com/docs/DOC-8760

Hopefully you'll start to use dashes and underscores in the future and refrain from adding spaces into filenames/directories.

0 Kudos
JoSte
Contributor
Contributor

I have found one bug and some enhancement ideas.

I think in the new script this line:

 for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`;  

Should be:

 for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[:blank:]*//;s/[:blank:]*$//'`;  	

Else you are going to just remove any "t","\" or spaces at beginning or end.

While at that, maybe this lines:

VMX_DIR=`echo ${VMX_CONF%.vmx}`     
VMX_DIR=`echo ${VMX_DIR%/*}`

Could be combined to:

VMX_DIR=`echo ${VMX_CONF%/*.vmx}` 	

At least in my tests it produced the same output that way ...

Then all

grep -E "${VM_NAME}" /tmp/vms_list | ...

should be changed to:

grep -E \"${VM_NAME}\" /tmp/vms_list | ...

So that the " is part of the search pattern. Else if input file contains 'test', it would also match for VMs that are called 'test1' or 'test vm' or anything else containing that word. Now that we have the " in the vms_list we should take benefit from it Smiley Happy

Regarding the crontab:

I think it should be enough to do a

mkdir /etc/rc.early.d

and then create a script file in it:

echo 'echo "01 05 * * 1-5 /opt/Backup/ghettoVCBni.sh  /opt/Backup/VMs2Backup" >>/var/spool/cron/crontabs/root' > /etc/rc.early.d/AddBackup2Crontab.sh
chown root:root /etc/rc.early.d/AddBackup2Crontab.sh
chmod 774 /etc/rc.early.d/AddBackup2Crontab.sh

I still need to test this, but I would think this should work.

-JoeSt

0 Kudos
kpc
Contributor
Contributor

Hi William

Is there any special reason why there needs to be an input file that contains the display names of the VM's you want to back up? I tried your first version of the script and I'm sure it didn't do this (could have dreamt it Smiley Happy however the new one seems to require this, is it becuase of the spaces in display names fix?

Good work on the script though...

0 Kudos
carpenike
Enthusiast
Enthusiast

Hello,

I'm wondering how difficult it would be to add a line to the script that copies week old backups to a different location prior to deleting them? Say I want to keep 7 backups and take one of those off-site per week and automate it all. What I could see doing is have the script copy the file that is 7 days old to an external hard drive and then delete it.

Thanks,

Ryan

0 Kudos
alusrc
Contributor
Contributor

lamw,

So adding a crontab to oem.tgz works like a charm. Didn't have to start crond like Dave mentioned, and the backups ran. However, when the jobs ran last night, I got a message for one of my VMs:

"Cannot open the disk '<location of disk on my host>.vmdk' or one of the snapshot disks it depends on. Reason: Device or resource busy."

Then,

"Failed to reopen disk '(null)'"

Then,

"Error encountered while restarting virtual machine after taking snapshot. The virtual machine will be powered off."

The VM just stayed off and we have to power it back on manually. Do you have any idea why that happened?

0 Kudos
alusrc
Contributor
Contributor

Should we be able to mount these vmdk files with the VMWare DiskMount utility? I'm trying to figure out if I can eliminate file level restores by just mounting the disk files and pulling data rather than turning the machine on through another host or something similar. I get an error using the VMWare utility.

0 Kudos
lamw
Community Manager
Community Manager

kpc,

Both version 1 and 2 of the script has always worked in that fashion. You would feed in a list of the Virtual Machines you would like backed up and they're queried and backed up. How else would you do it Smiley Wink

The reason for the display name, which I think most user's have both the display name and the actual name of their directory/etc all match up, is that VMware allows you to have a different display name than the actual directory and possibly some of the other attributes. The queries that are return by using either "vmware-cmd -l" or "vmware-vim-cmd vmsvc/getallvms" show only display name

Hopefully this answered your question.

0 Kudos
lamw
Community Manager
Community Manager

It would not be too hard, but you'll have to modify this for your use case. You can set the rotation variable to go back at least 7 iterations and instead of "rm -rf" that backup, you could specify a new variable at the top and have it copy that off to say NFS datastore #2 and that can be used to be shipped offsite, then purge from that current backup datastore so you don't keep a duplicated copy unless that is you goal.

good luck

0 Kudos
lamw
Community Manager
Community Manager

great to hear.

Did you use v1 or v2 of the script? Usually when it says it can't open the disk, the snapshot has not been taking and there is till a VMFS lock on the master VMDK you're trying to access. You'll usually get promoted with device or resource busy. Not sure about the second set of messages and what was done afterwords. Have you tried to run a backup on this VM before? Perhaps try to run a manual backup or run the script only on this VM and see if you run into any odd results.

0 Kudos
depping
Leadership
Leadership

lamw, great script. I wrote a short article to refer people to the solution you created:

http://www.yellow-bricks.com/2008/11/26/backup-your-esxi-vms/

Duncan

Blogging: http://www.yellow-bricks.com

If you find this information useful, please award points for "correct" or "helpful".

0 Kudos
lamw
Community Manager
Community Manager

Hi JoSte,

Thanks for your feedback,

I've uploaded a small test script to answer some of your questions regarding section 1 (disagree, you'll see from sample what I mean)

and

section 2 (agree that the test script works, but for some reason when enabling the devel mode to print out the variables, there is discrepancy and I need to do more testing before making that change). I try to think of all the possible use cases where an end user could be providing bad input, say "spaces" in their names and try to take into account. The sed command that I had setup works as expected.

I agree with your section 3, I must have missed that and yes it would not do an exact match. I've modified the code but have not updated the doc, I'll do that later today.

Let me know if you have any questions regarding the "test.sh"

0 Kudos
lamw
Community Manager
Community Manager

Thank's Duncan, very honored.

Love your blog by the way.

0 Kudos
aremmes
Enthusiast
Enthusiast

I think in the new script this line:

>  for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`;  
> 

Should be:

>  for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[:blank:]*//;s/[:blank:]*$//'`;  	
> 

Else you are going to just remove any "t","\" or spaces at beginning or end.

"\t" is the tab character, so it'd remove either spaces or tabs. Also, your sed command wouldn't work; you'd need to recode it as:

for VM_NAME in `cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`

Or:

cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//' | while read VM_NAME;

Note the quotes around $; without them, any spaces in the variable would break the script (the shell would see them as argument separators).

While at that, maybe this lines:

> VMX_DIR=`echo ${VMX_CONF%.vmx}`     
> VMX_DIR=`echo ${VMX_DIR%/*}`
> 

Could be combined to:

> VMX_DIR=`echo ${VMX_CONF%/*.vmx}` 	
> 

At least in my tests it produced the same output that way ...

'dirname' was intended for that specific purpose:

VMX_DIR=`dirname "${VMX_CONF}"`

The quotes ensure that spaces in the path are passed to the command correctly.

Then all

> grep -E "${VM_NAME}" /tmp/vms_list | ...
> 

should be changed to:

> grep -E \"${VM_NAME}\" /tmp/vms_list | ...
> 

So that the " is part of the search pattern. Else if input file contains 'test', it would also match for VMs that are called 'test1' or 'test vm' or anything else containing that word. Now that we have the " in the vms_list we should take benefit from it Smiley Happy

Preferably you'd use word boundaries in the regular expression so that you're not forced to have quotes in the VM names:

grep -E "\<${VM_NAME}\>" /tmp/vms_list | ...

That way, if $VM_NAME == 'test', it won't match 'tests', 'test1', 'newtest', etc.

0 Kudos
JoSte
Contributor
Contributor

@lamw,

I have tested with your demo script. This are my findings:

# cat > /tmp/bad_input_file << __BAD_INPUT__
 Monitoring & Maintenance

 RESNET-UCSB


 blah2


    blah1

   blah2

 t

         tvm

                         tt

 __BAD_INPUT__
~ # IFS=$'\n'
~ # for VM_NAME in `cat /tmp/bad_input_file | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`;
 do
 echo "#$VM_NAME#"
 done
#Monitoring & Maintenance#
#RESNET-UCSB#
#blah2#
#blah1#
#blah2#
#vm#
~ #

So the sed on my ESXi does not interpret \t as tab. It just removes the t...

However this corrected version from aremmes works as I would expect it.

 # for VM_NAME in `cat /tmp/bad_input_file | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`
 do
 echo "#$VM_NAME#"
 done
#Monitoring & Maintenance#
#RESNET-UCSB#
#blah2#
#blah1#
#blah2#
#t#
#tvm#
#tt#
~ #

Preferably you'd use word boundaries in the regular expression so that you're not forced to have quotes in the VM names:

grep -E "\<${VM_NAME}\>" /tmp/vms_list | ...

That way, if $VM_NAME == 'test', it won't match 'tests', 'test1', 'newtest', etc.

I am by no means an expert in this, but what if there is a VM_NAME == 'test vm' and 'test'. Wouldn't that 'test' also match the 'test vm', then?

~ # grep -E "\<test\>" /tmp/vms_list
"16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
~ # grep -E "\<test vm\>" /tmp/vms_list
"16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
~ #

How about this:

grep -E "\"${VM_NAME}\"" /tmp/vms_list | ...

It should work also with spaces in $VM_NAME.

~ # grep -E "\"test vm\"" /tmp/vms_list
"16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
~ # grep -E "\"test\"" /tmp/vms_list
~ #

About the VMX_DIR
~ # VMX_CONF=/vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB/RESNET-UCSB.vmx
~ # VMX_DIR=`echo ${VMX_CONF%.vmx}`
~ # VMX_DIR=`echo ${VMX_DIR%/*}`
~ # echo "Output: $VMX_DIR"
Output: /vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB
~ # VMX_DIR=""
~ # VMX_DIR=`echo ${VMX_CONF%/*.vmx}`
~ # echo "Output: $VMX_DIR"
Output: /vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB
~ #

So on my ESXi it is doing the same in both versions. However the suggested

VMX_DIR=`dirname "${VMX_CONF}"`

is anyway the better idea.

0 Kudos
aremmes
Enthusiast
Enthusiast

I am by no means an expert in this, but what if there is a VM_NAME == 'test vm' and 'test'. Wouldn't that 'test' also match the 'test vm', then?

> ~ # grep -E "\<test\>" /tmp/vms_list
> "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
> ~ # grep -E "\<test vm\>" /tmp/vms_list
> "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
> ~ #
> 

How about this:

> grep -E "\"${VM_NAME}\"" /tmp/vms_list | ...
> 

It should work also with spaces in $VM_NAME.

> ~ # grep -E "\"test vm\"" /tmp/vms_list
> "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx"
> ~ # grep -E "\"test\"" /tmp/vms_list
> ~ #
> 

You're quite correct. Seeing the file format gives me an idea, though...

#!/bin/bash

# Backup the current IFS
OLD_IFS="${IFS}"
IFS=";"

# Name of VM we're looking for
MY_VMNAME="vm"

# Walk through stdin, stop at first match
while read VM_ID VM_NAME VM_DS VM_VMX; do
        test "${MY_VMNAME}" = "${VM_NAME}" && break
done << __VMS_LIST__
1;vm01;[datastore-1];New Virtual Machine #1/New Virtual Machine #1.vmx
2;vm02;[datastore-1];New Virtual Machine #2/New Virtual Machine #2.vmx
3;vm03;[datastore-1];New Virtual Machine #3/New Virtual Machine #3.vmx
4;vm04;[datastore-1];New Virtual Machine #4/New Virtual Machine #4.vmx
5;random vm name;[datastore-1];random vm name/random vm name.vmx
6;vm;[datastore-1];vm/vm.vmx
7;vm05;[datastore-1];New Virtual Machine #5/New Virtual Machine #5.vmx
8;vm06;[datastore-1];New Virtual Machine #6/New Virtual Machine #6.vmx
__VMS_LIST__

# Print the values of the last record read
echo ${VM_ID} ${VM_NAME} ${VM_DS} ${VM_VMX}

# Restore IFS
IFS="${OLD_IFS}"

This assumes that the lines in the input is unique, but they don't have to be sorted. Running this gives:

 ~ # ./testvmlist.sh
6 vm [datastore-1] vm/vm.vmx
 ~ #

Message was edited by: aremmes -- some code was getting munched

0 Kudos
lamw
Community Manager
Community Manager

Hi JoSte and aremmes,

Thanks for your guys feedback and suggestions! Definitely great ideas and I guess we've learned that there's more than one way to accomplish a task. I've been pretty busy this morning but I'll be taking some of that feedback and update the script. I'll need to run further tests to ensure it does not cause any side affects to the current functionality. I'll post it up once it's ready to go

0 Kudos
JoSte
Contributor
Contributor

I have tried to change the script with aremmes solution:

	#dump out all virtual machines allowing for spaces now
	${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/   /g' | awk -F'   ' '{print $1";"$2";"$3}' |  sed 's/\] /\];/g' | sed '1,1d' > /tmp/vms_list

	IFS=$'\n'
	for MY_VMNAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`;
        do
		OLD_IFS="${IFS}"
		IFS=";"
		while read VM_ID VM_NAME VM_DS VMX_CONF; do
			test "${MY_VMNAME}" = "${VM_NAME}" && break
		done &lt;/tmp/vms_list
		# Restore IFS
		IFS="${OLD_IFS}"

		VMFS_VOLUME=`echo "${VM_DS}" | sed 's/\[//;s/\]//g'`
		
#		VM_ID=`grep "${VM_NAME}" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'`

		#ensure default value if one is not selected or variable is null
		if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then
			VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F)"
		fi

		#esx
		if ; then
			#VMFS_VOLUME=`grep \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'`
			#VMX_CONF=`grep \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/"//g'`
			VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}"
			VMX_DIR=`dirname "${VMX_PATH}"`
			#VMX_DIR=`echo ${VMX_DIR%/*}`
		fi
		#esxi
		if ; then
			#VMFS_VOLUME=`grep -E \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'`
			#VMX_CONF=`grep -E \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $4}'  | sed 's/\[//;s/\]//;s/"//g'`
			VMX_DIR=`dirname "${VMX_CONF}"` 	
			#VMX_DIR=`echo ${VMX_DIR%/*}`
			VMX_DIR=/vmfs/volumes/${VMFS_VOLUME}/${VMX_DIR}
			VMX_PATH=/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}
		fi

		#checks to see if we can pull out the VM_ID
		if [ -z ${VM_ID} ]; then
			echo "Error: failed to extract VM_ID for ${MY_VMNAME}!"

It looks promising so far.

Error: failed to extract VM_ID for test!
##########################################
Type: light
Virtual Machine: test vm
VM_ID: 16
VMX_PATH: /vmfs/volumes/datastore-1/New Virtual Machine/New Virtual Machine.vmx
VMX_DIR: /vmfs/volumes/datastore-1/New Virtual Machine
VMX_CONF: New Virtual Machine/New Virtual Machine.vmx
VMFS_VOLUME: datastore-1
##########################################

Error: failed to extract VM_ID for test1!
Error: failed to extract VM_ID for tesat2!
Error: failed to extract VM_ID for test3 und4 5!

Start time: Wed Nov 26 22:08:19 UTC 2008
End   time: Wed Nov 26 22:08:19 UTC 2008
Duration  : 0 Seconds

Completed backing up specified Virtual Machines!

This is the input file:

test

test vm

test1

tesat2

test3 und4 5

It has some garbage in it by purpose...

Once the now unneeded code would be removed, the script should look nicer. Overall I think aremmes had a very nice idea here. It may be worth going that direction. It is less complicated.

0 Kudos
lamw
Community Manager
Community Manager

Taking a look at the code and with the suggestions, we can actually remove some unnecessary code, since we rely on the "VMware vimsh" to pull out information. We don't have to break up the code on parsing the Virtual Machine information whether the script is running on an ESX w/SC or ESXi because the information can be pulled the same way.

Here is the code that'll get the modification:

${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/   /g' | awk -F'   ' '{print "\""$1"\";\""$2"\";\""$3"\""}' |  sed 's/\] /\]\";\"/g' | sed '1,1d' > /tmp/vms_list

        IFS=$'\n'
        for VM_NAME in `cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`;
        do
                VM_ID=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'`

                #ensure default value if one is not selected or variable is null
                if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then
                        VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F)"
                fi

                VMFS_VOLUME=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'`
                VMX_CONF=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/\[//;s/\]//;s/"//g'`
                VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}"
                VMX_DIR=`dirname "${VMX_PATH}"`

I will not have time to finalize testing but I'll do so after coming back from the holidays.

I'll be out of town and I hope everyone has a safe and happy holiday!

0 Kudos
kpc
Contributor
Contributor

Sorry, double post...

0 Kudos