hammackj

VMWare Fusion VM Clone Script

This is a fairly simple VMWare VM cloning script. I use VMWare Fusion/ESXi for most of my research and neither of these tools have a cloning functionality without paying for it. VMWare Workstation has a built-in function and Fusion is missing that function currently, hopefully they will add it in 4.0. Attached is a really simple proof of concept script for cloning virtual machines. I created a github repo for the script here. I have tested it locally on about 20 different VM's that I have but nothing beats having other people test it. Please let me know if there are any issues.

#!/usr/bin/env ruby -wKU

#Jacob Hamack
#jacob.hammack@hammackj.com
#http://www.hammackj.com
#Usage: clone_vm.rb original/ clone/

def rename_files_by_type (type, old_vm_name, new_vm_name)
    file_of_type = Dir["*.#{type}"]

    file_of_type.each do |old_file_name|
        new_file_name = old_file_name.sub(old_vm_name, new_vm_name)

        if old_file_name == new_file_name
            next
        else
            File.rename(old_file_name, new_file_name)
        end
    end
end

def file_type(file)
    type = `file '#{file}'`.split(":")[1]
end

def process_vmdk_files(old_vm_name, new_vm_name)
    vmdk_files = Dir["*.vmdk"]

    vmdk_files.each do |file|
        type = file_type file
        new_file_name = file.sub(old_vm_name, new_vm_name)

        if type =~ /ASCII English text/                            
            File.open("#{new_file_name}", "w+") do |output|
                File.open("#{file}", "r") do |input|
                    while (line = input.gets)
                        if line =~ /\"#{old_vm_name}-s\d\d\d.vmdk\"/
                            line = line.sub(old_vm_name, new_vm_name)
                        elsif line =~ /\"#{old_vm_name}-\d*-s\d\d\d.vmdk\"/
                            line = line.sub(old_vm_name, new_vm_name)
                        elsif line =~ /parentFileNameHint/
                            line = line.sub(old_vm_name, new_vm_name)
                        else
                            line = line
                        end

                        output.write line
                    end
                end
            end

            system("rm '#{file}'")

        elsif type =~ /VMware4 disk image/
            File.rename(file, new_file_name)
        else
            puts "[!] Broken vmdk: #{file}"
        end
    end
end

def process_vmx_file(old_vm_name, new_vm_name)
    File.open("#{new_vm_name}.vmx", "w+") do |output|
        File.open("#{old_vm_name}.vmx", "r") do |input|
            while (line = input.gets)       
                if line =~ /scsi0:0.fileName/
                    line = line.gsub("#{old_vm_name}", "#{new_vm_name}")
                elsif line =~ /displayName/
                    line = line.gsub("#{old_vm_name}", "#{new_vm_name}")
                elsif line =~ /extendedConfigFile/
                    line = line.gsub("#{old_vm_name}.vmxf", "#{new_vm_name}.vmxf")
                elsif line =~ /nvram/
                    line = ""
                elsif line =~ /sched.swap.derivedName/
                    line = line.gsub("#{old_vm_name}", "#{new_vm_name}")
                elsif line =~ /checkpoint.vmState/
                    line = line.gsub("#{old_vm_name}", "#{new_vm_name}")
                else
                    line = line
                end

                output.write line
            end
        end
    end
    system("rm '#{old_vm_name}.vmx'")
end

def process_vmxf_file(old_vm_name, new_vm_name)
    File.open("#{new_vm_name}.vmxf", "w+") do |output|
        File.open("#{old_vm_name}.vmxf", "r") do |input|
            while (line = input.gets)
                if line =~ /vmxPathName/
                    line = line.gsub("#{old_vm_name}.vmx", "#{new_vm_name}.vmx")
                else
                    line = line
                end

                output.write line
            end
        end
    end

    system("rm '#{old_vm_name}.vmxf'")
end

def process_vmsd_file(old_vm_name, new_vm_name)
    File.open("#{new_vm_name}.vmsd", "w+") do |output|
        File.open("#{old_vm_name}.vmsd", "r") do |input|
            while (line = input.gets)       
                if line =~ /fileName/ or line =~ /filename/
                    line = line.sub("#{old_vm_name}", "#{new_vm_name}")
                else
                    line = line
                end

                output.write line
            end
        end
    end
    system("rm '#{old_vm_name}.vmsd'")
end


file = ARGV[0]
newfile = ARGV[1]

if file[-1] == "/"
    file = file[0...file.rindex('/')]
end

if newfile[-1] == "/"
    newfile = newfile[0...newfile.rindex('/')]
end

#1. Copy existing vm to new path
puts "[*] Cloning #{file} to #{newfile}"
system("cp -r '#{file}'/ '#{newfile}'/")

#clean up the new vm
puts "[*] Clean #{newfile}"
Dir.chdir("#{newfile}/")

file = file.gsub(".vmwarevm", "")
newfile = newfile.gsub(".vmwarevm", "")

#Remove all of the pointless stuff
system("rm *.nvram")
system("rm -rf *.lck")
system("rm -rf Applications/")
system("rm -rf appListCache")
system("rm *.log")
system("rm quicklook-cache.png")

puts "[*] Fixing disk references"
process_vmdk_files(file, newfile)

puts "[*] Fixing #{newfile}.vmx"
process_vmx_file(file, newfile)

puts "[*] Fixing #{newfile}.vmxf"
process_vmxf_file(file, newfile)

puts "[*] Fixing Snapshots"
puts "[*] Fixing *.vmem"
rename_files_by_type("vmem", file, newfile)

puts "[*] Fixing *.vmsn"
rename_files_by_type("vmsn", file, newfile)

puts "[*] Fixing *.vmss"
rename_files_by_type("vmss", file, newfile)

puts "[*] Fixing vmsd"
process_vmsd_file(file, newfile)