Cyclone Client Side Deployment Sytem

From richud.com
Jump to navigation Jump to search


cyclone.sh

Pre Imaging Process Overview

The imaging process begins after the client image boots.

After the client boots, /init spawns /etc/inittab which in turn runs /etc/init.d/rcS. The last part of this spawns the main imaging system script.

Now choices made in the original menu come to fruition. Arguments passed on the command line (kernel append) are now processed.

These point to the location of the next script to run, in this case cyclone.sh, the main bash script that runs everything.

Imaging Processes

(Looking at the bottom of the script), the main procedure calls various functions to perform the various tasks. It is split into non-destructive and destructive parts for clarity.

Non descructive parts

The first functions are non destcructive, building up information about the client, taking under a second to run. During testing this enables you to inset a stop at a point before the disk is altered yet have all the info about what it is going to do at hand.

framebuffer

Draws the graphics to make it look more friendly.

keymap "uk"

Sets UK keymap

gettime

Gets and sets time from time server, for logging and to correct hardware clock. Windows doesn't like having wrong date and time.

gethal

Determines the HAL for Win XP imaging by interpreting /proc/cpuinfo. No trouble on real hardware - Virtualbox seems to report APIC when APIC is disabled so wrong HAL picked. Sets variables for XP's sysprep.inf

gethw

  • PCI devices
  • USB devices
  • ACPI type devices, specifically anything other than dev* LNX*, these are usually found in laptops, like hotkey buttons and such. (I do not fully understand this, but seems to encompass things that are not USB or PCI. Note most of the PNP things will be duplicates of PCI/USB devices picked up above.)
  • Audio special case for IntelHDA audio.

Builds up a list of the machines hardware for driverpack matching. Important kernel built with ALSA audio support and Intel HDA audio to enable discovery of hardware which wont show up otherwise (should be by default).

getprodinfo

Uses dmidecode to extract info about the machine such as serial number, model, etc.

This info can and usually is in one, none or several places and uses a few simple algorythms to get pick the most likely.

getdisk

Picks primary drive to image too (sda or hda), which in automated deployment situations would almost always be what you wanted.

  • Assumptions: You are imgaging the primary disk
  • Mods: Could extend DB schema/script quite easily to pick others.

getschema

Queries server DB (via php so secure/works over internet) to pull exactly what it is doing, although most things are passed on kernel append, certain things can't be.

getmac

Gets MAC address of eth0.

  • Assumptions: you are using primary network card, eth0
  • Mods: could extend DB and script to accommodate additional cards etc.

gethost

Returns the host name of the machine from DNS TXT record (we are using a fixed IP scheme based on MAC) This is pretty custom to my environment. There are many ways you could automate naming, using the Asset tag on Dell's, serial numbers, UUID, etc. but they will rely on probably using the MAC to query something or get something from the BIOS.

label print

Modifies a PostScript template with machine info including QR bar code encoding the machine info

Destructive Part (Writing to hdd)

writepart

writepartdata

Cyclone Imaging System Main Script

Main script that runs on the client controlling the high speed deployment.

#!/bin/bash
#Last modified 
#Mon 24 Jan 2012 15:29:13 BST 

#############
## colours ##
#############
txtred='\e[0;31m' # Red
txtgrn='\e[0;32m' # Green
txtblu='\e[1;34m' # Blue
txtrst='\e[0m'    # Text Reset


##############################
##############################
######### functions #########
##############################
##############################


#ok
ok() {
	echo -e "${txtblu} * $1 ${txtrst}"
}

#failure
fail() {
	echo -e "${txtred} * $1 ${txtrst}"
	exit
}

#pause
ec() {
	echo -e "${txtblu} *Pausing $1 seconds ${txtrst}"
	echo -n "#Debug Mode - Press x to exit to terminal, any other key to continue and reboot: "
	read ext
	[[ "$ext" == "x" ]] && exit
}

#set framebuffer up
framebuffer() {
	uname -a #dump kernel info
	echo "Server:$serveraddr Runinit:$runinit"
	
	mkfifo /fbsplash.fifo #boot splash
	fbsplash -s /cyclone.ppm -i /fbsplash.conf -f /fbsplash.fifo &

	#5% done
	echo 5 > /fbsplash.fifo
	
	pc=5 #define starting percentage complete
}

#set keymap
keymap() {
	loadkmap < /etc/$1.keymap
}

#label print
printlabel() {
	$(grep -qio "label=Label" /proc/cmdline) || return
	cat "/tmp/$spath/ps/uol-label.ps" | sed -e "s/H\{10\}/$hostn/g" -e "s/D\{10\}/`date +"%d-%b-%Y"`/g" -e "s/S\{16\}/$sn/g" -e "s/M\{17\}/$macc/g" > "/tmp/$spath/ps/$mac.ps"
	[[ ${#hostn} -gt 7 ]] && sed -i "s/\[34 0 0 -34 0 0\]/\[30 0 0 -30 0 0\]/" "/tmp/$spath/ps/$mac.ps"
	lpr -P DYMO-LabelWriter-450-Turbo@$serveraddr "/tmp/$spath/ps/$mac.ps"&
}


## Determining HAL for XP images 
gethal() {
	#fixes to make sure its kind of reliable , needs SMP linux kernel for detection to work, this is crude but seems reliable after a year
	#no bothered about MPS stuff for desktop
	flags=$(cat /proc/cpuinfo | grep "flags")
	[[ -d "/proc/acpi" ]] && flags="$flags acpi "
	[[ "$(cat /proc/cpuinfo | grep "^processor" | wc -l)" -gt "1" ]] && flags="$flags multi "


	case "$flags" in
		*\ multi\ *)
 			halname="ACPI Multiprocessor PC"
 			haln=ACPIAPIC_MP
			hal='UpdateHAL="ACPIAPIC_MP,%WINDIR%\\Inf\\Hal.inf"'
			halmini=halaacpi.dll ; kernmini=ntkrnlmp.exe
		;;
		*\ apic\ *)
			halname="ACPI Uniprocessor PC" #virtualbox reports apic in /proc/cpuinfo when IO APIC is off!
 			haln=ACPIAPIC_UP
 			hal='UpdateUPHAL="ACPIAPIC_UP,%WINDIR%\\Inf\\Hal.inf"'
			halmini=halaacpi.dll
		;;
		*\ acpi\ *)
			halname="Advanced Configuration and Power Interface ACPI PC"
			haln=ACPIPIC_UP
			hal='UpdateUPHAL="ACPIPIC_UP,%WINDIR%\\Inf\\Hal.inf"'
			halmini=halacpi.dll
		;;
		*)
 			halname="Standard HAL"
		;;
	esac
	ok "Determined $halname, $haln, $halmini $kernmini"
}


# get/set time 
gettime() {
	#ntpd -d -p ntp3c.le.ac.uk #this dont seem to do anything
	#http://www.nist.gov/pml/div688/grp40/its.cfm
	for i in nist1-ny.ustiming.org nist1-pa.ustiming.org time-a.nist.gov time-b.nist.gov nist1.aol-va.symmetricom.com nist1.columbiacountyga.gov nist1-chi.ustiming.org nist.expertsmi.com nist.netservicesgroup.com nisttime.carsoncity.k12.mi.us
	do
		ok "Current time $(date), Setting from: $i"
		rdate -s "$i" 2> /dev/null && hwclock -w && break
	done
}


# collect pc hardware
gethw() {
	#pci
		lspci | grep -Eio '[0-9A-Z]{4}:[0-9A-Z]{4}' > /tmp/pci.txt
	#usb
		lsusb | grep -Eio '[0-9A-Z]{4}:[0-9A-Z]{4}' > /tmp/usb.txt
	#acpi
		#remove PNP| from first grep to reenable
		ls /sys/bus/acpi/devices | grep -Eiv 'dev|LNX' | grep -iEo '[A-Z0-9]{7,8}' > /tmp/acpi.txt
	#audio
		#find /sys/devices -iname -type f vendor_id -exec cat {} \; | sed -e 's/.*0x\([A-Za-z0-9]\{4\}\)\([A-Za-z0-9]\{4\}\)/\1:\2/' > /tmp/audio.txt
		#wont do hdmi audio  although  if grahpics driver is bundled with it it should
		find /proc/asound -iname "codec*" -type f -exec cat {} \; | grep "Vendor Id" | sed -e 's/.*0x\([A-Za-z0-9]\{4\}\)\([A-Za-z0-9]\{4\}\)/\1:\2/' > /tmp/audio.txt

	cat /tmp/pci.txt /tmp/usb.txt /tmp/acpi.txt /tmp/audio.txt | sort | uniq > /tmp/hwids.txt
	hwids=$(cat /tmp/hwids.txt)
}


# Get Product Information
getprodinfo() {
	#Dumping dmidecode output to tmp
	dmidecode > /tmp/dmidecode.txt

	##SERIAL NUMBER
	sn=$(cat /tmp/dmidecode.txt | sed -n "/System Information/,/^$/p" | sed -e '/Serial Number:/!d' -e "s/.Serial Number: *\([^ ]*\) */\1/" | tr -cd 'A-Za-z0-9-_')
	[[ -z "$sn" ]] && sn=$(cat /tmp/dmidecode.txt | sed -n "/System Information/,/^$/p" | sed -e '/UUID:/!d' -e "s/.UUID: *\([A-Za-z0-9-]*\) */\1/" | tr -cd 'A-Za-z0-9-_')
	[[ -z "$sn" ]] && sn=$(cat /tmp/dmidecode.txt | sed -n "/Base Board Information/,/^$/p" | sed -e '/Serial Number:/!d' -e "s/.Serial Number: *\([A-Za-z0-9][ A-Za-z0-9]*[A-Za-z0-9]\) */\1/" | tr -cd 'A-Za-z0-9-_')
	[[ -z "$sn" ]] && pickserial "" || sn=${sn:0:14} #restrict to 14 characters else too large for QR code
		
	##PRODUCT NAME
	bbprod=$(cat /tmp/dmidecode.txt | sed -n "/Base Board Information/,/^$/p" | sed -e '/Product Name:/!d' -e "s/.Product Name: *\([A-Za-z0-9][ A-Za-z0-9]*[A-Za-z0-9]\) */\1/" | tr -cd 'A-Za-z0-9-_')
	siprod=$(cat /tmp/dmidecode.txt | sed -n "/System Information/,/^$/p" | sed -e '/Product Name:/!d' -e "s/.Product Name: *\([A-Za-z0-9][ A-Za-z0-9]*[A-Za-z0-9]\) */\1/" | tr -cd 'A-Za-z0-9-_')
	[[ -z "$bbprod" ]] && [[ -z "$siprod" ]] && prod="BAD BIOS NO INFO" || prod="$bbprod$siprod"

}

#user input if no serial number found
pickserial() {
	read -p "No serial found, please enter it:" sn
	# Sanitize input
	sn=${sn//[^a-zA-Z0-9-]/}
	sn=${sn:0:14}
	[[ -z "$sn" ]] && pickserial
}

#get sectors of drive
getdisk() {
	#assume primary drive, can be hda or sda
	hdd=$(fdisk -l | grep "Disk /dev/..a" | head -1 | grep "[0-9]" | sed "s/.*dev\/\([a-z]*\):.*/\1/")

	#total sectors of drive
	sectors=$(fdisk -lu -S63 -H255 /dev/${hdd} | grep -E "total [0-9]* sectors" | sed "s/.*total \([0-9]*\) sectors.*/\1/")
}

#get MAC address
getmac() {
	ip=$(ip a s eth0 | sed -n 's/.*inet \([0-9.]\+\)\/.*/\1/p')
	ok "IP -=$ip=-"
	macc=$(ip a s eth0 | sed -n 's/.*ether \([a-z0-9:]\+\) .*/\1/p')
	mac=$(echo $macc | tr -d :)
}

# lookup hostname in DNS record
gethost() {
	hostn=$(dig @143.210.12.152 +short $mac.mac2host.le.ac.uk TXT | tr -d \")
	#hostn=$(nslookup -type=TXT $mac.mac2host.le.ac.uk 143.210.12.152 | sed -e '/text =/!d' -e 's/.*\"\(.*\)\".*/\1/g')
	if [[ -z "$hostn" ]]  ; then  [[ "$scheme" = *Domain* ]]  && fail "Scheme with domain client picked, but machine $mac not found on NDOR" || pickname "" ; fi
	ok "Serial No. -=$sn=- Product Name -=$prod=-"
	ok "Hostname -=$hostn=- MAC -=$mac=-"
}

#get hostname if not on NDOR, not for domain use
pickname() {
	read -p "No hostname found, please enter one:" hostn
	# Sanitize input
	hostn=${hostn//[^a-zA-Z0-9-]/}
	[[ -z "$hostn" ]] && pickname
	#add a check here to stop it being al numerical too 
}

#get schema from SQL
getschema() {
	#should return $snum $scheme $os $p1 $p2 $p3 $p4 $fdisk
	eval $(wget -O- -q --post-data "q=SELECT * from schema where snum='$snum'" http://$serveraddr/cyclone/php/cyclone-post.php)
	ok "Scheme -=$scheme=- OS(s) -=$os=-"
	ok "Partitions -=1:$p1 2:$p2 3:$p3 4:$p4=-"
}

#get case correct path to Windows versions
getwinpaths() {
	winpath=$(find "/mnt/$i" -maxdepth 1 -type d -iname "windows")
	sys32path=$(find "$winpath" -maxdepth 1 -type d -iname "system32")
}

#write partition and MBR
writepart() {
	#Wipe first MB of drive, to remove existing partitions and MBR and MBR backups
	dd if=/dev/zero of=/dev/${hdd} bs=1M count=1 2>/dev/null

	#partition drive ,fdisk value from SQL will call function pssec() to work out split points of partition
	echo -e "$fdisk" | fdisk -S63 -H255 -u /dev/${hdd} >/dev/null

	#some option to use grub needs sorting out
	# grub-install --no-floppy --boot-directory=/mnt/$os/boot /dev/${hdd}
	#Add MBR, without 64 byte partition table
	#FIXME $os isnt much help on mixes, if more than one use grub/syslinux?
	dd if=/tmp/$spath/mbr/$os-mbr-446 of=/dev/${hdd} 2>/dev/null
}

#partition sector split function, called from inside the argument to fdisk that is returned from SQL in function writepart()
#args: $1 is % of total,  $2=-1 end sector before split, $2=null start sector after split
psec() {
	ss=$((sectors*$1/100)) #work out split point in sectors from a percentage
	#1Mb with 512 bytes/ sector = aligment at 2048 sectors - more info http://www.ibm.com/developerworks/linux/library/l-4kb-sector-disks/index.html?ca=dgr-lnxw074KB-Disksdth-LX
	ss=$((ss/2048)) #divide and multiply to integer round
	echo $((ss*2048 $2))
}


#Wiping and partitioning drive
writepartdata() {
	for i in {1..4}				#run through partitions that have an image/format set, $i is active partition
		do
			j=p$i			#j=p1 p2 etc
			k=${!j}			#k=value of $p1
			[[ -z "$k" ]] && break	#assume end and exit as no assigned image
			for l in $k		#match a set image if one exists, if no match, will use last image which should be universal
				do
					imgname=$l	#if match occurs stop, you will be using a specific set image
					[[ "$prod" = *$imgname* ]]  && break
				done
			processpart

	done
}

#Process each partition
processpart() {

	#gets details about partition, SQL returns $osname $process $imgname $type $imgfilename $os $dpspath
	eval $(wget -O- -q --post-data "q=SELECT * from image where imgname='$imgname'" http://$serveraddr/cyclone/php/cyclone-post.php)

	ok "Processing Partition $i: Process: $process  OS Name: $osname Imgname: $imgname\n * Type: $type Imgfilename: $imgfilename OS:$os DP path: $dpspath"
	imgfilenameLOG+="[p$i] $imgfilename<br>"  #build log entry

	#determin what to do with returned partition info
	case "$process" in 
		format) 
			$imgfilename /dev/${hdd}${i} #format command from SQL, easy to set label or other customisations to format
		;; 
		ntfs) 
			writewindows
			if [[ -n "$type" ]]
				then
					mountntfs
					getwinpaths
					runonce$os
					postbatch$os
					typeLOG+="[p$i] $type<br>" #build log entry
				fi
			if [[ -n "$dpspath" ]]
				then
					copydp
					dp$os
					sysprep$os
					fixes$os
					massstorage$os
					dpmatchLOG+="[p$i] $dpmatch<br>" #build log entry
			fi
		;; 
		ext4) 
			writelinux $i; 
		;; 
		esac

	pc=$[pc+25] && echo $pc > /fbsplash.fifo
}


#write windows image to partition
writewindows() {
	#work out imagetype, without use of file
	case ${imgfilename##*.} in
		gz)
			decomp="pigz -d -" ;;
		lzma)
			decomp="lzma -d -c" ;;
		xz)
			#decomp="pixz -d -" ;;
			decomp="xz -c -d" ;;
	esac

	#get, decompress, write image in one
	wget -qO- http://$serveraddr/cyclone/img/$imgfilename | $decomp | ntfsclone -r --overwrite /dev/${hdd}${i} -

	#resize NTFS volume to fit partition
	ntfsresize -f /dev/${hdd}${i}

	#specify head sec/trk for compat with fdisks forced head sec/trk, else will fail on CHS drives on old stuff (Tosh portege PP201E) - this breaks start sec so need to specify this to match with partition as in SQL 2048, but if you are fixing not the first ntfs bs it wil break as not 2048
	ntfsfixboot -w  -h 255 -t 63 /dev/${hdd}${i}
}

#write linux image to partition
writelinux() {
	#fsarchiver restfs file.fsa id=0,dest=/dev/${hdd}${i} - problem doesnt support pipes
	wget -qO- http://$serveraddr/cyclone/img/$imgfilename |$decomp | dd bs=16M of=/dev/${hdd}${i}
	resize2fs /dev/${hdd}${i}
}

#convert ascii to REG_EXPAND_SZ  null terminated, 0 padded  hex string
regexpandsz() {
	o= ; for ((z=0;z<${#1};z++)); do o+=$(printf "%x,00," "'${1:$z:1}") ; done
	echo 'hex(2):'${o/%,/,00,00}
}

#Inject registry file
#args: $1 hive, $2 data
writereg() {
	reged -I -C "$sys32path/config/$1" HKEY_LOCAL_MACHINE\\${1^^} $2 >/dev/null
}

#Delete key and any sub values from registry, regmod cant handle key removals in .reg files. rdel doesnt work piping for some reason
#args: $1 hive, $2 key
delreg () {
	echo -e "cd $2\ndel *\nq\ny\n"  | chntpw "$sys32path/config/$1" >/dev/null
}


#RunOnce, probably only needed in XP as Win7 unattend.xml seems ok
runoncewinxp(){
	writereg "software" "/tmp/$spath/reg/$os-RunOnceEx.reg"
	#remove mounted devices
	delreg "system" "MountedDevices"
	delreg "system" "ControlSet001\Services\intelppm" #will break imaging AMD machine, on Intel gets automatically recreated.
	delreg "system" "ControlSet001\Services\iaStor" # fix for image having built in iaStor entry with non REG_EXPAND_SZ imagepath entry which breaks chntpw
}

runoncewin7(){
	echo "" #dont think we need this here, func cant be empty
}

runonceserver2008() {
	writereg "SOFTWARE" "/tmp/$spath/reg/$os-RunOnceEx.reg"
}

# Mount NTFS
mountntfs() {
	ok "Mounting NTFS Filesystem /dev/${hdd}${i} to /mnt/$i"
	#lowmount -o ignore_case so any case differences in overwrite files dont duplicate files or folders like Sysprep and sysprep - this stops being able to hexedit MFT file though
	[[ -d "/mnt/$i" ]] || mkdir /mnt/$i
	mount.ntfs-3g /dev/${hdd}${i} /mnt/$i
}

#Copy matched DriverPacks to newly imaged partition
copydp() { #needs Win mounted
	ok "Copying matching Driverpacks"

	wget -P "/tmp/$i/" -q -nd http://$serveraddr/cyclone/$dpspath/DP-$os-$dppath.list

	wget -P "/mnt/$i/" -A.ini -q -r -l1 -nd http://$serveraddr/cyclone/$dpspath/

	join -i -t# -1 1 -2 1 -o1.2 /tmp/$i/DP-$os-$dppath.list /tmp/hwids.txt | sort -u > /tmp/$i/DP.match
	dpmatch=$(< /tmp/$i/DP.match)

	cat /tmp/$i/DP.match | while read pck
		do
			#test this with D/G/A1 as sub folders
			wget -P "/mnt/$i/" --reject=index.html -nH --cut-dirs=3 -q -r -np http://$serveraddr/cyclone/$dpspath/$pck/
  			pc=$[pc+3] && echo $pc > /fbsplash.fifo
 		done

}

dpwinxp() { #needs Win mounted

 	ok "Copying over newest DP Base files"
 	cp /tmp/$spath/base/* "/mnt/$i/" #quotes break the *

 	#Create DevPath string - this is REG_EXPAND_SZ which is hex in regedit file so would need converting char by char to hex
	devpath=$(cat /tmp/$i/DP.match | sed "s/^/c:\\\/g" | sed "s/$/;/g" | tr '/' '\' | tr -d '\n')
	#convert string to reg_expand_sz hex
	DevicePath='"DevicePath"='$(regexpandsz "%SystemRoot%\Inf;$devpath")

	echo -e "Windows Registry Editor Version 5.00\n[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]\n$DevicePath"  > /tmp/$i/DevPath.reg
 	ok "Injecting registry changes for devpath"
 	writereg "software" "/tmp/$i/DevPath.reg"

}

dpwin7() { #needs Win mounted
	ok "Copying over newest DP files"
	#mv "/mnt/$i/$dppath/" "/mnt/$i/\$WinPEDriver\$" #Use Win7 scanning of $WinPEDriver$ special folder to add drivers - didnt seem to work
	mv "/mnt/$i/$dppath/" "$winpath/inf/"
}

#Copy matching MassStorage .sys files to new image and modify registry with appropriate CDDB and Service data
massstoragewinxp() { #needs Win mounted
	#exit function if no mass storage drivers matched
	[[ -d  "/mnt/$i/$dppath/M/" ]] || return

		#adding all breaks normal images with ntfs.sys error as too many mass storage entries
		#ASSUMPTIONS sys file has same name as addservice,  forget System Bus Extenders, only do SCSI Miniport, only one Addservice per inf , mostly true see below for some exceptions

	#get mass storage IDS, convert to Windows syntax
	msids=$(lspci -n | grep -E " 01[0-9]{2}" | grep -ioE "[A-Z0-9]{4}:[A-Z0-9]{4}" | sed -e "s/^/VEN_/" -e "s/:/\&DEV_/g" | tr '\n' '|')
	msids=${msids/%|/}

	#write registry header
	echo "Windows Registry Editor Version 5.00" > /tmp/$i/MassStorage.reg

 	#cddb addition for pci#CC_ vital bits and service stops
	cat /tmp/$spath/reg/cddb.reg >> /tmp/$i/MassStorage.reg
	

	#grep fails on UTF-16 e.g. SS2/sisraid4.inf so have to use find/read/continue, busybox tr doesnt support hyphen for ranges like  -cd '\11\12\15\40-\176'
	#Skip any INF's not matching MassStorage or having AddService entry - could be several infs in one matching driver folder, or non relevant infs.
	#The (chronologically by dir order) last driver .inf/.sys that is matched to hardware will be what ends up in Windows ONLY for first boot, as duplicates in DPs, Windows driver installer will then install latest one properly.
	find "/mnt/$i/$dppath/M/" -type f -iname "*.inf" | xargs grep -lEi  "$msids" | xargs grep -lEir "^AddService *= *[A-Z0-9_]+," | while read m
		do
			#ClassGUID -  96A IDE/AHCI, 97B RAID
			#head removes more than one entry, incase multiple GUID in same inf, e.g D1/percsas.inf I think
			classguid=$(cat "$m" | tr -cd '[:alnum:]\r\n-=,{};' | grep -Eir "^ClassGUID *= *\{[a-zA-Z0-9-]+\}" | sed "s/.* *{\([a-zA-Z0-9-]\+\)}.*/\1/" | head -1)

			#AddService
			#head removes more than one entry, not correct but stops it bombing out on AT/atiide.inf and AM/AMDEIDE.inf - could filter somehow as inf and addservice names match... also persas and megasas in same inf D1/percsas.inf
			addservice=$(cat "$m" | tr -cd '[:alnum:]\r\n-=,_.[];' | grep -Eir "^AddService *= *[A-Z0-9_]+," | sed "s/.*= *\([a-zA-Z0-9_]\+\),.*/\1/" | head -1)

			#skip iteration if no addservice, usually applies if the servicename is a variable, ignoring this as obscure only
			[[ -z "$addservice" ]] && { ec "Unprocessable Addservice entry for $m"; continue; }

			##any matching ven/dev, create CriticalDeviceDatabase reg entry
			cat "$m" | tr -cd '[:alnum:]\r\n_&' | grep -iE "$msids" | grep -Eio '[V][E][N]_[A-Z0-9]{4}&[D][E][V]_[A-Z0-9]{4}' | sort -u | while read n 
				do
					#echo "#$m# #$n# #$classguid# #$addservice#"
					echo "
					[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#$n]
					\"Service\"=\"$addservice\"
					\"ClassGUID\"=\"{$classguid}\"
					" | tr -d '\t' >> "/tmp/$i/MassStorage.reg"
		 		done

			##add any addservice entry for Services reg entry

			# skip if pciide/intelide/atapi as these allready included/  part of offline sysprep on universal image. (IntelIde/PCIIde/AliIde are also System Bus Extenders) . chntpw is case sensative and breaks as PCIIde is pciide also ImagePath isnt reg_expand_sz and this also breaks chtnpw
			shopt -s nocasematch; [[ "$addservice" =~ pciide|intelide|atapi|aliide ]] && { shopt -u nocasematch; continue; }

			#AddService , encode ImagePath to REG_EXPAND_SZ, null terminated hex
			ImagePath='"ImagePath"='$(regexpandsz "system32\drivers\\$addservice.sys")

			echo "
				[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\\$addservice]
				\"ErrorControl\"=dword:00000001
				\"Group\"=\"SCSI miniport\"
				\"Start\"=dword:00000000
				\"Tag\"=dword:00000019
				\"Type\"=dword:00000001
				\"DisplayName\"=\"$addservice\"
				$ImagePath
				" | tr -d '\t' >> "/tmp/$i/MassStorage.reg"

			#copy the .sys files over using .inf location as base dir name
			find "${m%/*}" -type f -iname "*.sys" -exec cp "{}" "$sys32path/drivers/" \;

		done  

	ok "Injecting MassStorage Drivers"
	writereg "system" "/tmp/$i/MassStorage.reg"

}

massstoragewin7() { #needs Win mounted
	echo "" #not needed as far as know
}



#XP Sysprep file modifications
sysprepwinxp() {
	[[ -d "/mnt/$i/Sysprep" ]] && mv "/mnt/$i/Sysprep" "/mnt/$i/sysprep"
	cp -R "/tmp/$spath/sysprep/" "/mnt/$i/"

	ok "Modifying HAL in sysprep to $haln"
	sed -i "s/;Update.*/$hal/" "/mnt/$i/sysprep/sysprep.inf" 
		#sed -i "s/haln=/haln=$haln/" '/mnt/$i/sysprep/i386/$OEM$/hal.cmd' #this does it via hal.cmd in OEM sysrep folder, doesnt seem to make any difference, hangs using sysprep, this way or not changing hal in same way, IDE/HDD related

	ok "Setting Computername in sysprep to $hostn"
	sed -i "s/ComputerName=\*/ComputerName=$hostn/" "/mnt/$i/sysprep/sysprep.inf"
}

#Windows 7 Sysprep file modifications
sysprepwin7() {
	ok "Setting Computername in sysprep to $hostn"
	sed -i  "s/<ComputerName>\*/<ComputerName>$hostn/" "/tmp/$spath/Windows/System32/sysprep/unattend.xml"
	cp -R "/tmp/$spath/Windows/" "/mnt/$i/" #case matters, copies Windows/System32/sysprep 
}


#Fixes to get XP through guisetup part of sysprep on certain machines
fixeswinxp() {
	#[[ -n "$halmini" ]] && ok "Modifying HAL in minisetup to $halmini" && lzma -d -c "/tmp/$spath/$os/$halmini.lzma" > "$sys32path/hal.dll"
	#[[ -n "$kernmini" ]] && ok "Modifying Kernel in minisetup to $kernmini" && lzma -d -c "/tmp/$spath/$os/$kernmini.lzma" > "$sys32path/ntoskrnl.exe"
	[[ -n "$halmini" ]] && ok "Modifying HAL in minisetup to $halmini" && cp "/tmp/$spath/$os/$halmini"  "$sys32path/hal.dll"
	#[[ -n "$kernmini" ]] && ok "Modifying Kernel in minisetup to $kernmini" && cp "/tmp/$spath/$os/$kernmini"  "$sys32path/ntoskrnl.exe"
}

#Fixes for Windows 7
fixeswin7() {
	echo "" #fixes here, func cant be empty
}


#Modify batch file that runs in XP after rebooting into that nasty OS
postbatchwinxp() {
#domain univ, domain set image, standard univ

	#Copying post deploy files to XP Volume
	cp -R /tmp/$spath/$os/ /mnt/$i/

	if [[ -n "$dpspath" ]]
		then
			 #univ image
			sed -i "s/RUNCMD/$type/" /mnt/$i/$os/cyclone-$os.bat
		else
 			#set image, run newsid and change name
			sed -i "s/RUNCMD/fimg/" /mnt/$i/$os/cyclone-$os.bat
 			sed -i "s/NEWSID/1/" /mnt/$i/$os/cyclone-$os.bat
	fi

	sed -i "s/HOSTN/$hostn/" /mnt/$i/$os/cyclone-$os.bat

}

#Modify batch file that runs in Win7 after rebooting
postbatchwin7() {
	#Copying post deploy files to Win7 Volume
	cp -R /tmp/$spath/$os/ /mnt/$i/
}

postbatchserver2008() {
	#Copying post deploy files toServer 2008 Volume
	cp -R /tmp/$spath/$os/ /mnt/$i/ 
}

#write log
writelog() {
	imgtime=$(($endT-$startT))
	ok "Cyclone took just ${imgtime}s!, writing log"
	#this not great as some details are from partition so only last one will be in log, hence type and runcmd blank
	wget -q --post-data "q=INSERT into log (hal,imgtime,product,serialnum,mac,hostname,runcmd,image,hwids,dpmatch) VALUES ('$halname','$imgtime','$prod','$sn','$mac','$hostn','$typeLOG','$imgfilenameLOG','$hwids','$dpmatchLOG');" http://$serveraddr/cyclone/php/cyclone-post.php
}

############################
############################
############################
######### MAIN PROGRAM ########
############################
############################
############################

#quick initialisations - non destructive
framebuffer
keymap "uk"
gettime
#telnetd

#start timing execution of system
startT=$(date +%s)

#build information - non destructive
gethal
gethw
getprodinfo
getdisk
getschema
getmac
[[ "$os" == "info" ]] && fail "Info only chosen, stopping further action"
gethost


printlabel
[[ "$os" == "label" ]] && fail "Printed label, stopping further action"


#run destructive
writepart
writepartdata

endT=$(date +%s)
writelog


#END
ok "Rebooting and Unmounting"
reboot