Using FOG Postinstall scripts for Windows Driver injection 2017 ed.
-
This thread is going to be a no nonsense tutorial rehashing other posts if yesteryear.
In this tutorial we will create 2 new FOG postinstall scripts and update the master fog postinstall script to call our new scripts.
fog.custominstall
- Create the following file named
fog.custominstallin the following path on the FOG server/images/postdownloadscripts. - Copy the content into that newly created file
#!/bin/bash . /usr/share/fog/lib/funcs.sh [[ -z $postdownpath ]] && postdownpath="/images/postdownloadscripts/" case $osid in 5|6|7|9) clear [[ ! -d /ntfs ]] && mkdir -p /ntfs getHardDisk if [[ -z $hd ]]; then handleError "Could not find hdd to use" fi getPartitions $hd for part in $parts; do umount /ntfs >/dev/null 2>&1 fsTypeSetting "$part" case $fstype in ntfs) dots "Testing partition $part" ntfs-3g -o force,rw $part /ntfs ntfsstatus="$?" if [[ ! $ntfsstatus -eq 0 ]]; then echo "Skipped" continue fi if [[ ! -d /ntfs/windows && ! -d /ntfs/Windows && ! -d /ntfs/WINDOWS ]]; then echo "Not found" umount /ntfs >/dev/null 2>&1 continue fi echo "Success" break ;; *) echo " * Partition $part not NTFS filesystem" ;; esac done if [[ ! $ntfsstatus -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to mount $part ($0)\n Args: $*" fi echo "Done" debugPause . ${postdownpath}fog.copydrivers # . ${postdownpath}fog.updateunattend umount /ntfs ;; *) echo "Non-Windows Deployment" debugPause return ;; esac- Save and exit your text editor.
- Make the script executable with
chmod 755 /images/postdownloadscripts/fog.custominstall
fog.copydrivers
- Create the following file named
fog.copydriversin the following path on the FOG server/images/postdownloadscripts. - Copy the content into that newly created file
#!/bin/bash ceol=`tput el`; manu=`dmidecode -s system-manufacturer`; dots "Identifying hardware" case $manu in [Ll][Ee][Nn][Oo][Vv][Oo]) machine=$(dmidecode -s system-version) ;; *[Dd][Ee][Ll][Ll]*) machine=$(dmidecode -s system-product-name) ;; *I[Nn][Tt][Ee][Ll]*) # For the Intel NUC and intel mobo pick up the system type from the # baseboard product name machine=$(dmidecode -s baseboard-product-name) ;; *) # Technically, we can remove the Dell entry above as it is the same as this [default] machine=$(dmidecode -s system-product-name) ;; esac # if the machine isn't identified then no need to continue with this script, just return to caller if [[ -z $machine ]]; then echo "Unable to identify the hardware for manufacturer ${manu}"; debugPause; return; fi echo "${machine} Identified"; # Removes Spaces in machine name, works better with path definitions # machine="${machine%"${machine##*[![:space:]]}"}"; # 14-Sep-23 Jeffrey Boulais posted that the above code did not work for his install. He # supplied this code as an alternative. If you run in to a problem using my code # comment out my code and see if his code works better for your installation. The # only right way is the one that works. Thank you Jeff for your input. # machine="$(echo -e “${machine}” | tr -d ‘[:space:]’)" # 03-Jan-24 marsface posted that he could not get either of the two above machine clean up commands to work correctly so he provided this one below which worked for him. machine="${machine//[[:space:]]/}" dots "Verifying we've found the OS disk" if [[ ! -d /ntfs/windows && ! -d /ntfs/Windows && ! -d /ntfs/WINDOWS ]]; then echo "! OS root Not found !"; debugPause return; fi echo "Found"; dots "Verifying target Arch" system64="/ntfs/Windows/SysWOW64/" [[ ! -d $system64 ]] && arch="x86" || arch="x64" echo "${arch} found"; debugPause # set osn path names based on the osid set in the FOG WebGui case $osid in 5) osn="win7" ;; 6) osn="win8" ;; 7) osn="win8.1" ;; 9) osn="win10" ;; esac dots "Preparing Drivers" clientdriverpath="/ntfs/Drivers" remotedriverpath="/images/drivers/$machine/$osn/$arch" debugPause if [[ ! -d "${remotedriverpath}" ]]; then echo "failed"; echo " ! Driver package not found for ${machine}/$osn/$arch ! "; debugPause; return; fi echo "Ready"; debugPause [[ ! -d $clientdriverpath ]] && mkdir -p "$clientdriverpath" >/dev/null 2>&1 echo -n "In Progress" rsync -aqz "$remotedriverpath" "$clientdriverpath" >/dev/null 2>&1 [[ ! $? -eq 0 ]] && handleError "Failed to download driver information for [$machine/$osn/$arch]" debugPause # the following code is only valid for Windows 7 operating systems since Windows 8 and beyond # relies on the unattend.xml section to locate OEM drivers. If you are no longer deploying Win7 # you may exclude this section. regfile="/ntfs/Windows/System32/config/SOFTWARE" key="\Microsoft\Windows\CurrentVersion\DevicePath" devpath="%SystemRoot%\DRV;%SystemRoot%\inf;"; reged -e "$regfile" &>/dev/null <<EOFREG ed $key $devpath q y EOFREG- Save and exit your text editor.
- Make the script executable with
chmod 755 /images/postdownloadscripts/fog.copydrivers
fog.updateunattend
This section is optional. You do not need to use this script if you do not need to patch or amend your unattend.xml file. If you want dynamic updates to your unattend file then you can use this section of code. Remember to uncomment the call to this script in the
fog.custominstallscript.- Create the following file named
fog.updateunattendin the following path on the FOG server/images/postdownloadscripts. - Copy the content into that newly created file:
#!/bin/bash hostadpwd="ADPASSWDHERRE"; #only downside to this method- this is the plain ad password unattends=$(find /ntfs/ -iname "unattend.xml") for unattend in $unattends; do [[ ! -f $unattend ]] && return dots "Preparing Sysprep File" #rm -f /ntfs/Windows/System32/sysprep/unattend.xml >/dev/null 2>&1 #if [[ ! $? -eq 0 ]]; then #echo "Failed" #debugPause #handleError "Failed to remove original unattend file" #fi echo "Done" debugPause dots "Writing Computer Name to $unattend" sed -i "/ComputerName/s/*/$hostname/g" $unattend >/dev/null 2>&1 if [[ ! $? -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to update originating unattend file" fi echo "Done" echo "ComputerName set to $hostname in $unattend" debugPause [[ -z $addomain ]] && continue dots "Set PC to join the domain" sed -i "/<JoinWorkgroup>/d" $unattend >/dev/null 2>&1 if [[ ! $? -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to remove the Workgroup setter" fi sed -i \ -e "s|<Password></Password>|<Password>${hostadpwd}</Password>|g" \ -e "s|<Username></Username>|<Username>${addomain}\\\\${aduser}</Username>|g" \ -e "s|<MachineObjectOU></MachineObjectOU>|<MachineObjectOU>${adou}</MachineObjectOU>|g" \ -e "s|<JoinDomain></JoinDomain>|<JoinDomain>${addomain}</JoinDomain>|g" $unattend >/dev/null 2>&1 if [[ ! $? -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to update user, pass, ou, and domain setter" fi echo "Done" debugPause done- Save and exit your text editor.
- Make the script executable with
chmod 755 /images/postdownloadscripts/fog.updateunattend
fog.postdownload
The last bit of magic we need to do is update the FOG supplied script called
fog.postdownloadto call our custom scriptfog.custominstall- Insert at the bottom of the
fog.postdownloadscript this line.
. ${postdownpath}/fog.custominstall- Save and exit your text editor.
Driver repository structure
For this process to work you must setup your driver library on the fog server in a certain fashion.
On your fog server create the drivers directory under the images directory with:
md /images/drivers
Below the /images/drivers directory you will create a directory per machine name (that exactly matches what comes from smbios using this command for Dell computersdmidecode -s system-product-name
Hint: You can also find the system product name by reviewing the inventoried system in the FOG WebGui.The structure should be built to match the variables used in the fog.drivers script.
/images ├─/drivers ├─$machine ├─$osn └─$archor translated into real values (note that the space has been removed for “Optiplex 7040” to this Optiplex7040. There is a clean up line above that removes spaces in the product name. While linux supports spaces in path names (like Windows) its bad practice to use them because some utilities will see the space as a parameter delimiter.
/images ├─/drivers ├─Optiplex7040 ├─win7 └─x86 └─x64 ├─win10 └─x64 ├─Optiplex7050 ...Unattend.xml
For WIN10 you must update the unattend.xml file to include this section. This is an example for the amd64 arch.
<settings pass="offlineServicing"> <component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DriverPaths> <PathAndCredentials wcm:action="add" wcm:keyValue="1"> <Path>C:\Drivers</Path> </PathAndCredentials> </DriverPaths> </component> </settings>If you want to read more into the how and why of this tutorial you can review this thread and its links: https://forums.fogproject.org/topic/8889/fog-post-install-script-for-win-driver-injection
EDIT: Changed arch test to the syswow64 path instead of the regedit.exe path within as the latest Windows 11 changed things so that the file is a symlink that isn’t recognized as a symlink or file in linux. Thanks to @lukebarone
- Create the following file named
-
(place holder)
-
(place holder)
-
Update: With the later releases of Win10 post 1703, the offline servicing section doesn’t seem to load the drivers. As a fix, add the following lines to your setupcomplete.cmd batch file to load the drivers at the end of the OSD process.
pnputil.exe /add-driver "C:\Drivers\*.inf" /subdirs /install pnputil.exe /add-driver "C:\Drivers\*.inf" /subdirs /install pnputil.exe /add-driver "C:\Drivers\*.inf" /subdirs /installRunning pnputil 2 times seems to be the sweet spot to capture all of the hardware devices, because some hardware is hidden behind other hardware drivers (such as USB3 and PCI hardware)
Update:if you want to use the pnputil.exe command with versions of windows before Win10 you will need to use this command instead, since the older versions of pnputil do not support the /subdir switch.forfiles /p "C:\Drivers" /s /m *.inf /c “cmd /c pnputil -a @Path” forfiles /p "C:\Drivers" /s /m *.inf /c “cmd /c pnputil -a @Path” forfiles /p "C:\Drivers" /s /m *.inf /c “cmd /c pnputil -a @Path” -
G george1421 referenced this topic on
-
G george1421 referenced this topic on
-
G george1421 referenced this topic on
-
Y youzersef referenced this topic on
-
I IT-MAN referenced this topic on
-
H HorizonG referenced this topic on
-
D diogo.seabra referenced this topic on
-
D diogo.seabra referenced this topic on
-
I just wanted to add some updates as 1.6 being released and with advent of Win 11 now.
Wanted to have more capable set of scripts here. I know the thread is very old, but it’s still worth the update in my book.
/images/postdownloadscripts/fog.postdownload
#!/bin/bash . /usr/share/fog/lib/funcs.sh [[ -z $postdownpath ]] && postdownpath="/images/postdownloadscripts/" # FOS sets osClass from the image's OS (determineOS, FOS 20261110 or later). # On an older FOS kernel osClass is empty, so derive it from the osid. if [[ -z $osClass ]]; then case $osid in 5|6|7) osClass="win7" ;; 9|10|11) osClass="winmodern" ;; esac fi case $osClass in win7|winmodern) clear [[ ! -d /ntfs ]] && mkdir -p /ntfs getHardDisk if [[ -z $hd ]]; then handleError "Could not find hdd to use" fi getPartitions $hd for part in $parts; do umount /ntfs >/dev/null 2>&1 fsTypeSetting "$part" case $fstype in ntfs) dots "Testing partition $part" ntfs-3g -o force,rw $part /ntfs ntfsstatus="$?" if [[ ! $ntfsstatus -eq 0 ]]; then echo "Skipped" continue fi if [[ ! -d /ntfs/windows && ! -d /ntfs/Windows && ! -d /ntfs/WINDOWS ]]; then echo "Not found" umount /ntfs >/dev/null 2>&1 continue fi echo "Success" break ;; *) echo " * Partition $part not NTFS filesystem" ;; esac done if [[ ! $ntfsstatus -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to mount $part ($0)\n Args: $*" fi echo "Done" debugPause . ${postdownpath}fog.copydrivers # . ${postdownpath}fog.updateunattend umount /ntfs ;; *) echo "Non-Windows Deployment" debugPause return ;; esac/images/postdownloadscripts/fog.copydrivers
#!/bin/bash ceol=`tput el`; manu=`dmidecode -s system-manufacturer`; dots "Identifying hardware" case $manu in [Ll][Ee][Nn][Oo][Vv][Oo]) machine=$(dmidecode -s system-version) ;; *I[Nn][Tt][Ee][Ll]*) # For the Intel NUC and intel mobo pick up the system type from the # baseboard product name machine=$(dmidecode -s baseboard-product-name) ;; *) # Dell and everything else machine=$(dmidecode -s system-product-name) ;; esac # if the machine isn't identified then no need to continue with this script, just return to caller if [[ -z $machine ]]; then echo "Unable to identify the hardware for manufacturer ${manu}"; debugPause; return; fi echo "${machine} Identified"; # Remove spaces from the machine name, works better with path definitions machine="${machine//[[:space:]]/}" dots "Verifying we've found the OS disk" if [[ ! -d /ntfs/windows && ! -d /ntfs/Windows && ! -d /ntfs/WINDOWS ]]; then echo "! OS root Not found !"; debugPause return; fi echo "Found"; dots "Verifying target Arch" system64="/ntfs/Windows/SysWOW64/" [[ ! -d $system64 ]] && arch="x86" || arch="x64" echo "${arch} found"; debugPause # Build the OS folder name from osname, which FOS sets from the image's OS. # "Windows 10" becomes win10, "Windows 8.1" becomes win8.1, "Windows 11" # becomes win11. So the folder names from the old osid table still match. osn="${osname,,}" osn="${osn//[[:space:]()]/}" osn="${osn/windows/win}" dots "Preparing Drivers" clientdriverpath="/ntfs/Drivers" remotedriverpath="/images/drivers/$machine/$osn/$arch" debugPause if [[ ! -d "${remotedriverpath}" ]]; then echo "failed"; echo " ! Driver package not found for ${machine}/$osn/$arch ! "; debugPause; return; fi echo "Ready"; debugPause [[ ! -d $clientdriverpath ]] && mkdir -p "$clientdriverpath" >/dev/null 2>&1 echo -n "In Progress" rsync -aqz "$remotedriverpath" "$clientdriverpath" >/dev/null 2>&1 [[ ! $? -eq 0 ]] && handleError "Failed to download driver information for [$machine/$osn/$arch]" debugPause # Windows 7, 8 and 8.1 only. Windows 10 and later read the OEM driver path # from the unattend.xml, so they skip the registry edit. [[ $osClass != win7 ]] && return regfile="/ntfs/Windows/System32/config/SOFTWARE" key="\Microsoft\Windows\CurrentVersion\DevicePath" devpath="%SystemRoot%\DRV;%SystemRoot%\inf;"; reged -e "$regfile" &>/dev/null <<EOFREG ed $key $devpath q y EOFREG/images/postdownloadscripts/fog.updateunattend
#!/bin/bash # Sets the computer name and, for a host with "Join Domain" on, the domain # join in the deployed image's unattend.xml. fog.postdownload sources this # file with the Windows partition mounted on /ntfs. # # FOG sends these from the host record (service/hostinfo.php): # hostname the host name # hostusead 1 when "Join Domain after deploy" is on # hostaddomain, hostadou the domain and the OU # hostaduser, hostadpass the join account and its password # hostadpass is empty unless FOG Configuration > FOG Settings > # FOG_HOSTKEY_ALLOWED_SOURCES lists the imaging network. The FOG Agent, # and the older FOG Client, join the domain without writing the password # to disk. Use this only where neither is installed. # Escape a value for XML text, then for the right side of a sed s|||. # The quoted replacements stop bash 5.2 from reading & as "the match". xmlEscape() { local s="$1" s="${s//&/'&'}" s="${s//</'<'}" s="${s//>/'>'}" s="${s//\"/'"'}" s="${s//\'/'''}" printf '%s' "$s" } sedEscape() { local s="$1" s="${s//\\/'\\'}" s="${s//|/'\|'}" s="${s//&/'\&'}" printf '%s' "$s" } xmlValue() { sedEscape "$(xmlEscape "$1")" } # The places Windows Setup reads an unattend.xml from after sysprep. The # [x] in each name makes it a glob, so nocaseglob matches any case of it. shopt -s nocaseglob nullglob unattends=( /ntfs/[w]indows/[p]anther/[u]nattend.xml /ntfs/[w]indows/[p]anther/[u]nattend/[u]nattend.xml /ntfs/[w]indows/[s]ystem32/[s]ysprep/[u]nattend.xml /ntfs/[u]nattend.xml ) shopt -u nocaseglob nullglob if [[ ${#unattends[@]} -eq 0 ]]; then echo " * No unattend.xml found, skipping" debugPause return fi # A PXE boot does not carry the AD values on the kernel command line. Ask the # server for them, the same way FOS does after a USB boot. Keep only the # host* lines of the reply, and delete the reply because it holds the password. if [[ -z $hostusead ]]; then curlcmd=(curl -Lks) command -v fog.curl >/dev/null 2>&1 && curlcmd=(fog.curl -Ls) sysuuid=$(dmidecode -s system-uuid) sysuuid=${sysuuid,,} [[ -z $mac ]] && mac=$(getMACAddresses) token=$("${curlcmd[@]}" --data "mac=$(echo $mac | base64)" "${web}status/hostgetkey.php") "${curlcmd[@]}" -o /tmp/hinfo.txt --data "sysuuid=${sysuuid}&mac=${mac}&hosttoken=${token}" "${web}service/hostinfo.php" -A '' if [[ -f /tmp/hinfo.txt ]]; then . <(grep -E '^\[\[ -z \$host[a-z]+ \]\] && export host[a-z]+=' /tmp/hinfo.txt) rm -f /tmp/hinfo.txt fi fi joinDomain="" if [[ $hostusead == 1 && -n $hostaddomain ]]; then if [[ -z $hostadpass ]]; then echo " * No domain join password received, so the domain join is skipped." echo " Set FOG_HOSTKEY_ALLOWED_SOURCES to the imaging network to send it." debugPause else joinDomain=1 # Qualify a bare user name with the domain: DOMAIN\user. joinUser="$hostaduser" [[ $joinUser != *\\* && $joinUser != *@* ]] && joinUser="${hostaddomain}\\${joinUser}" fi fi name=$(xmlValue "$hostname") for unattend in "${unattends[@]}"; do dots "Writing Computer Name to $unattend" sed -i "s|<ComputerName>[^<]*</ComputerName>|<ComputerName>${name}</ComputerName>|g" "$unattend" if [[ ! $? -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to update $unattend" fi echo "Done" debugPause [[ -z $joinDomain ]] && continue dots "Setting the domain join in $unattend" # Username and Password change only inside <Credentials>. AutoLogon and # the local accounts use the same element names. sed -i \ -e "/<JoinWorkgroup>/d" \ -e "/<Credentials>/,/<\/Credentials>/s|<Password>[^<]*</Password>|<Password>$(xmlValue "$hostadpass")</Password>|" \ -e "/<Credentials>/,/<\/Credentials>/s|<Username>[^<]*</Username>|<Username>$(xmlValue "$joinUser")</Username>|" \ -e "s|<MachineObjectOU>[^<]*</MachineObjectOU>|<MachineObjectOU>$(xmlValue "$hostadou")</MachineObjectOU>|g" \ -e "s|<JoinDomain>[^<]*</JoinDomain>|<JoinDomain>$(xmlValue "$hostaddomain")</JoinDomain>|g" \ "$unattend" if [[ ! $? -eq 0 ]]; then echo "Failed" debugPause handleError "Failed to set the domain join in $unattend" fi echo "Done" debugPause done