Scripted Installations of Windows 7 via PXE
The end of life date for Windows XP is quickly approaching, and like many other lazy/trailing-edge sysadmins, we are just beginning to transition to something else. If I was an ideological purist that "something else" would be some flavour of FLOSS, but because I am a sellout we are moving to Windows 7.
This blog post documents the infrastructure we use to install and maintain Windows images. Here are some of the goals for this installer:
It should not require a Windows server to use, because we do not have enough server CALs for all of the computers we install (namely, those we install as part of the Working Centre's Computer Recycling project.
It should be integrated with the PXE infrastructure we already have in place.
It should work for the different classes of computer we install: staff machines, machines for our training and public access labs, and the computers we refurbish and sell.
It should be completely scripted and automated. In particular, it should not require us to maintain images, because maintaining images is for dorks.
Ideally, it should provide ways to keep images up to date with third-party updates after they have been deployed.
The installer should be reasonably straightforward to maintain even by those who did not set it up. (This documentation is an attempt to fulfill that requirement.)
The infrastructure we set up took weeks and weeks of work to develop, but it works pretty well.
There are other valiant attempts to do this, but I have not seen other cases that match our needs exactly. For example, there is a procedure here that claims not use use the WAIK at all: http://www.ultimatedeployment.org/win7pxelinux1.html . But I could not follow it well enough to get it working, and it was too ideologically pure for me. Since I am installing Windows 7 computers, getting a Windows 7 computer to use as a technician workstation did not seem that much of a stretch.
- Scripted Installations of Windows 7 via PXE
- Sidebar!
Overview
The installation process is kind of intricate, so here is a high-level overview:
- The client boots to the PXE server and selects a Windows autoinstaller.
- The PXE server then boots a remastered WinPE ISO file.
- The ISO file contains a file called
winpeshl.ini
, which launches aninit.cmd
file on the remastered ISO. - This
init.cmd
maps a network drive, and then calls arunsetup.cmd
command on the network drive. - The
runsetup.cmd
handles any interactive user interaction that needs to be set for configuring this installation. Then it launchessetup.exe
from the Windows 7 installation DVD with anunattend.xml
file. - The
unattend.xml
file configures a number of configuration options for the barebones Windows install. It partititions the hard drive, installs Windows, and sets up initial users. - As part of the OOBE "Out of the Box Experience" step of the Windows
installation, the installer launches a
postinstall.cmd
script. This script runs a number of tweaks, adds drivers where appropriate, and launches WPKG. - Based on the computer name, WPKG installs 3rd party software as required for this machine. After the machine has been deployed WPKG can be run again to update software on the computer.
Terminology/Stuff You Need
- A Windows 7 installation DVD or ISO.
- The Windows Automated Installation Toolkit (WAIK), which is installed on a "technician machine" (a regular Windows 7 machine). As of this writing, you could get the WAIK here: http://www.microsoft.com/en-us/download/details.aspx?id=5753
- A computer to use as a PXE server (I will assume a Debian/Ubuntu server)
- The ability to redirect PXE clients to your server (which probably requires modifying options in your DHCP server)
- A few Samba/Windows shares that can be mapped as network drives.
For this walkthrough, let's assume the PXE server and Samba shares are
all hosted by a machine called ntinstall
with IP address 10.168.1.5 .
The share containing the Windows 7 install files is
\\ntinstall\win7-install
The share containing WPKG configuration is \\ntinstall\wpkg
The PXE configuration files live in /var/lib/tftpboot/ of
ntinstall`
Remaster bootable WIM
Most of these steps are taken from the following site: https://sites.google.com/site/godunder/windows-build-files/how-to-create-a-custom-winpe-3-0-3-1-boot-cd-iso-or-usb-boot-key
Start by installing the WAIK on the technician computer.
Next, create the file structure for the WinPE environment into
c:\winpe
:
copype.cmd x86 c:\winpe
Next, get the contents of the Windows 7 installation DVD someplace
useful. (Eventually, this will have to be on a network share so that
PXE clients can use it.) For now say that the Windows 7 contents are
on drive D:
.
At this point, there are two possible files that could serve as the
bootable ISO: boot.wim
(in d:\sources\boot.wim) or winre.wim
which
is embedded in d:\sources\install.wim
(in
C:\Windows\System32\Recovery\
) . I cannot remember
whether it makes a difference, so I will modify boot.wim
. Refer to
the link above if you want to use winre.wim
(and please contact me
to let me know that it makes a difference).
First, copy boot.wim
someplace useful. "Someplace useful" means the
ISO\sources
folder, because the oscdimg
command below depends on this
location:
copy d:\sources\boot.wim c:\winpe\ISO\sources\
then mount boot.wim
for editing:
dism /mount-wim /wimfile:c:\winpe\ISO\sources\boot.wim /index:1 /mountdir:c:\winpe\mount
If you look in c:\winpe\mount
you should see some files. These are the
mounted image. You can make changes here and commit those changes.
Start by editing c:\winpe\mount\windows\system32\winpeshl.ini
. Mine
looks something like this:
[LaunchApps]
wpeinit
%SystemDrive%\local\init.cmd
The wpeinit
starts the installation environment.
Now, make a folder called c:\winpe\mount\local
and add an
init.cmd
command. This will mount the network share and call a
runsetup.cmd
command. (It is possible that this could all be
integrated into winpeshl.ini
, which would save a step. Oh well.)
My init.cmd
looks something like this:
@echo off
echo Mounting network drive..
net use q: \\ntinstall\win7-install
echo Launching runsetup.cmd
q:\local\runsetup.cmd
Next, we want to remove the irritating "press any key to boot from CD
or DVD" message when the ISO boots. To do this, delete the file
c:\winpe\ISO\boot\bootfix.bin
file.
We should now be ready to commit changes that we made to the ISO:
dism /unmount-wim /mountdir:c:\winpe\mount /commit
and remaster the boot CD:
oscdimg -n -bc:\winpe\etfsboot.com c:\winpe\ISO c:\winpe\winpe_x86.iso
If all went well, you should have a 200MB winpe_x86.iso
file that
can be used for PXE booting.
Configure a PXE server
I will not go through detailed steps of how to install a PXE server here. There are other good tutorials online. I used the ones here: http://www.howtoforge.com/ubuntu_pxe_install_server
Here are a few quick tips that saved me some hassle:
- I used the
tftpd-hpa
TFTP server - To get a sensible set of configuration files for the PXE boot menu,
I stole the configuration files from an Ubuntu installation CD.
It looks like you may be able to steal the
isolinux
folder from a Debian install CD and get similar template files. You might be best off using them for inspiration rather than using them directly. - In
/etc/default/tftp-hpa
, I set the following option:TFTP_DIRECTORY="/var/lib/tftpboot"
- I needed to add the following to
/etc/sysctl.conf
to prevent "PXE-EA1: No PXE server found, using static boot file" errors:net.ipv4.ip_no_pmtu_disc = 1
(but this was for an ancient Ubuntu 8.04 server, and you may not need this any more.) - I organize the files in
/var/lib/tftpboot/
as follows:introscreens/
: Text for user prompts (f1.txt, f2.txt, etc)pxelinux.cfg/default
: I throw all of my PXE configuration stanzas herewin7/
: Files related to the Windows 7 installer go here
- The
pxelinux.0
andmemdisk
files can be found in thesyslinux-common
package. You want version 4 or higher (which is the default in Debian Wheezy) because this version allows you to boot ISO files directly. I put these in/var/lib/tftpboot
- Maybe you need the
*.c32
files fromsyslinux-common
package as well. I put them in a folder/var/lib/tftpboot/libs
and then used a#PATH libs
directive in mypxelinux.cfg/default
file.
The beginning of my pxelinux.cfg/default
file looks something like
this:
#PATH libs
DISPLAY introscreens/f1-boot.txt
f1 introscreens/f1-boot.txt
f2 introscreens/f2-hwtest.txt
f9 introscreens/f9-windows.txt
and my f9-windows.txt
file looks something like this:
^Xintroscreens/cr_splash_danger.rle
These Windows installers are for AUTOMATED INSTALLATION. They will
WIPE
OUT your hard drive without prompting you!
autoinstall-win7 : Windows 7 Installer
The first character is special. I think it is a ctrl-X
character.
The cr_splash_danger.rle
is a splash screen graphic file. You make
it by creating a PPM file and converting it with the ppmtolss16
command (available in the syslinux
package). There are some
reasonable instructions here:
- - http://christian.amsuess.com/tutorials/lanbootserver/index.html :
- Look at "Create An Own Bootsplash Image"
- - http://frantisek.rysanek.sweb.cz/splash/isolinux-splash-HOWTO.html
- Fairly in-depth, but I do not think you need to go through the hassle of making a GIF first.
Next you need to configure the DHCP server to point to your PXE
machine. I use pfSense as the DHCP server for
this subnet. I needed to add a next-server
directive to the "DHCP
Server" section (labelled "Enable network booting". The IP of the
next-server is the IP address of the PXE server (10.168.1.5 in my
example) and the filename is /var/lib/tftpboot/pxelinux.0
If you do not have access to your DHCP server, you could add a second network card to your PXE server and set up a private subnet there (with its own DHCP server). Then you could use that private subnet to install Windows.
To test the PXE server, it might be easiest to test with memtest86+
. Install the memtest86+
package, grab the binary
/boot/memtest86+.bin
, and copy it to the folder
/var/lib/tftpboot/memtest/
. Then use the following stanza in your
pxelinux.cfg/default
file:
LABEL memtest
kernel memtest/memtest86+.bin
If all goes well, you should be able to network boot a machine, have it connect to your PXE server, and get a prompt. Typing "memtest" should start the memory tester.
Some computers do not support network booting, or they do not support
network booting well (in particular, they do not know what to do with
a next-server
directive from the DHCP server). If this is the case,
generate a GPXE ISO or USB key image from http://rom-o-matic.net/
and boot from that.
Configure PXE for the Windows Installer
Once your PXE server is working, set up Windows 7 installer files:
- Copy the remastered
winpe_x86.iso
file into/var/lib/tftpboot/win7
- Make a folder in a Samba share and populate it with the contents of
the Windows DVD. I made a (read-only) share in
/var/lib/tftpboot/win7/share
, but you can do whatever makes you happy. To match theinit.cmd
contents above, this should be viewable as\\ntinstall\win7-install
- Copy the contents of the Windows 7 installation DVD to the share. I
put mine into
/var/lib/tftpboot/win7/share/win7dvd/
- Make a folder for your configuration files. Mine is called
/var/lib/tftpboot/win7/share/local
Next, add a stanza that will boot your Windows 7 installer. Here is
what I used in my pxelinux.cfg/default
file:
LABEL autoinstall-win7
kernel memdisk
append iso raw initrd=win7/winpe_x86.iso
Now if you type autoinstall-win7
at the PXE boot prompt, you should
see the ISO file getting downloaded (which will produce a lot of dots
on the screen) and then you should boot into the Windows installation
environment. Pressing <shift>+<F10>
should bring up a command
window. Typing wpeutil reboot
in a command line might get you out if
you are stuck.
Because there is no runsetup.cmd
file, the installer will get stuck
and probably reboot. But if you are lucky it will mount the Q:
network drive.
Add drivers
Windows 7 does a pretty good job of finding drivers on its own, but if you have drivers that are not detected by particular machines, adding your own is usually pretty easy.
In the windvd\sources
folder, make the following folder hierarchy:
$OEM$\$$\Inf
. Inside this Inf
folder you can add the (unpacked)
drivers for your hardware. I make one folder for each model of
machine (dc7100
, ibm-6072-c1u
, etc) and then make folders in those
machine folders for each component (Sound
, Video
, etc). Then I put
the unpacked drivers in those subfolders.
This is great because Windows will only integrate drivers that it needs for the particular installation.
In addition, once you have the $OEM$\$$\Inf
path working,
the underlying folder hierarchy seems pretty forgiving. I do not put
spaces in filenames for superstitious reasons.
Note that this technique will not work for components that don't have
enough drivers to get through the install (namely, network cards). I
believe those drivers need to be slipstreamed into the winpe_x86.iso
file above. UPDATE: See the next section.
I learned about this trick from the following forum post: http://forums.mydigitallife.info/threads/22915-Fully-unattended-win7-x64-install-with-integrated-%28not-injected%29-unsigned-drivers
Add drivers to boot.wim
I ended up having to slipstream network card drivers into
winpe_x86.iso
, and found that these instructions were insufficient.
Here are some quick notes on how I did it, assuming that the driver
files are unpacked into a folder called c:\drivers
:
Get your winpe_x86_cr.iso
. Use 7-zip or something to extract boot.wim
to c:\winpe\ISO\sources
.
Next, mount the WIM file:
dism /mount-wim /wimfile:boot.wim /index:1 /mountdir:c:\winpe\mount
Now you can install the drivers. All the drivers in c:\drivers
will be added. You can also point this command to a single .inf file if you only want to add one driver.
dism /image:c:\winpe\mount /add-driver /driver:\c:\drivers /recurse
Next you have to unmount boot.wim
:
dism /unmount-wim /mountdir:c:\winpe\mount /commit
Finally you have to remaster the ISO file:
oscdimg -n -bc:\winpe\etfsboot.com c:\winpe\ISO c:\winpe\winpe_x86.iso
Then upload winpe_x86.iso
to your PXE folder, and you are done.
Set up runsetup.cmd
The purposes of this file are:
- To live on a network drive so that making changes does not require
remastering the
winpe_x86.iso
file. - Allow the user to customize the Windows install.
- To pick an appropriate
unattend.xml
and runsetup.exe
with it.
It is worth talking about user customization a little. You might want different kinds of Windows installations for different computers. Maybe some computers should get Office 2010 installed; some Office 2007 or some no Office at all. Maybe you need to install Windows 7 Professional for some computers and Windows 7 Enterprise for others.
Ideally, we would have different PXE stanzas for each case:
LABEL autoinstall-win7-office2010
kernel memdisk
append iso raw initrd=win7/winpe_x86.iso office=o2010
LABEL autoinstall-win7-office2007
kernel memdisk
append iso raw initrd=win7/winpe_x86.iso office=o2007
but I never found an effective way of getting parameters passed on the
PXE command line to the Windows installation environment. Therefore,
we have to make the decision once the installer environment has
launched, which is where the runsetup.cmd
command fits in.
To do this, I use the choice.exe
command, which I copied from the
technician machine to the network share (probably violating some EULAs
in the process). Then I had a runsetup.cmd
file that looks something
like this:
@echo off
echo Starting runsetup.cmd...
echo(
rem =========== CHOOSE OFFICE OPTIONS ==================
echo Irritating installation Menu
echo ----------------------------
echo(
echo a - Install Office 2010
echo b - Install Office 2007
rem if statements must be in reverse order of choice
q:\local\choice.exe /C ab /t 30 /m "Choose or lose (timeout: 30s):" /d b
if errorlevel 2 goto office_2007
if errorlevel 1 goto office_2010
echo "Timeout!"
:office_2010
echo You chose to install Office 2010!
copy q:\local\unattend-o2010.xml %TEMP%\unattend.xml
goto endchoice
:office_2007
echo You chose to install Office 2007!
copy q:\local\unattend-o2007.xml %TEMP%\unattend.xml
goto endchoice
:endchoice
rem ================ START SETUP (x86) ==================
echo Starting setup.exe
q:\win7dvd\setup.exe /unattend:%TEMP%\unattend.xml
In this case, Office 2007 gets installed if there is no other input for 30 seconds.
You can add other options as well, but if your selections get too multivariate you will face a combinatorial explosion quickly.
The unattend-o2010.xml
file is a Windows answer file that
specifies how the installation should proceed. In a flagrant violation
of the "don't repeat yourself" principle, both of the different XML
files are almost copies of each other. (In this case only the
preferred hostname changes.)
Note that the XML file must be named unattend.xml
for the
setup.exe
to recognise it as an answer file. (Actually,
autounattend.xml
might also work as a name, but unattend-o2007.xml
will not, and it will frustrate you if you try.)
Set up unattend.xml
The easiest way to create the set of unattend.xml files that you need is to use the "Windows Image System Manager" program. Basically, you need to create a new answer file, and then you need to populate it.
The Windows installer goes through a number of different "passes", and different configuration options apply to each pass.
Here are two good guides to getting a handle on the passes and how to create answer files:
- - http://www.symantec.com/connect/blogs/windows7-untangling-scripted-installs-sysprep-and-configuration-passes
- - http://community.spiceworks.com/how_to/show/2224-create-a-master-windows-7-image
- Steps 6 through 11 are the most helpful. This tutorial uses amd64 (ie x64) options, but there are corresponding x86 options as well.
Here are specific options I set. Note that I did not set any options
for 2 offlineServicing
, 5 auditSystem
or 6 auditUser
.
1 windowsPE
x86_Microsoft-Windows-International-Core-WinPE_neutral
- Set InputLocale, SystemLocale, UILanguage, UILanguageFallback,
UserLocale to
en-US
(or whatever)
- Set InputLocale, SystemLocale, UILanguage, UILanguageFallback,
UserLocale to
x86_Microsoft-Windows-Setup_neutral
- DiskConfiguration
- Disk[DiskID="0"]
- Action: AddListItem, DiskID: 0, WillWipeDisk: true
- CreatePartitions
- (This creates two partitions: 100MB for the system partition, and the remaining for the Windows partition)
- Action: AddListItem, Order: 1, Size: 100, Type: Primary
- Action: AddListItem, Order: 2, Extend: true, Type: Primary
- ModifyPartitions
- Action: Modify, Format: NTFS, Order: 1, Partition 1
- Action: Modify, Format: NTFS, Order: 2, Partition 2
- Disk[DiskID="0"]
- ImageInstall
- OSImage
- InstallToAvailablePartition: False, WillShowUI: OnError
- InstallFrom:
- Path Q:\win7dvd\sources\install.wim
- MetaData[Key="/IMAGE/NAME"]
- Action: AddListItem, Key: /IMAGE/NAME, Value: "Windows 7 PROFESSIONAL"
- InstallTo: DiskID 0, PartitionID: 2
- OSImage
- UserData
- AcceptEula: true
- DiskConfiguration
The ImageInstall | OSImage | InstallFrom | MetaData
key determines
what version of Windows gets installed. You find out what to type for
"Value" by typing
imagex /info \\ntinstall\win7-install\win7dvd\sources\install.wim
This will dump an XML file. Each flavour of Windows has an index and a
name. The names available in my install.wim
are:
- Windows 7 STARTER
- Windows 7 HOMEBASIC
- Windows 7 HOMEPREMIUM
- Windows 7 PROFESSIONAL
- Windows 7 ULTIMATE
Using "IMAGE/INDEX" instead of "IMAGE/NAME" is probably less error-prone, but harder for other people to understand.
3 generalize
- x86_Microsoft-Windows-Security-SPP_neutral
- SkipRearm: 1
4 specialize
x86_Microsoft-Windows-Deployment_neutral
- RunSynchronous
- RunSynchronousCommand[Order="1"]
- Action: AddListItem
- Description: Disable "The publisher could not be verified" prompts
- Order: 1
- Path:
cmd /c reg add HKCU\Software\Microsoft\Internet Explorer\Security\ /v DisableSecuritySettingsCheck /t REG_DWORD /d 0 /f
- RunSynchronousCommand[Order="2"]
- Action: AddListItem
- Order: 2
- Path:
cmd /c reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
- RunSynchronousCommand[Order="1"]
- RunSynchronous
x86_Microsoft-Windows-Security-SPP-UX_neutral
- SkipAutoActivation: true
x86_Microsoft-Windows-UnattendedJoin_neutral
- ComputerName:
computer-name-here
- ProductKey:
your product key
- ShowWindowsLive: false
- TimeZone: Eastern Standard Time
- WindowsFeatures
- ShowMediaCenter: false
- ShowWindowsMail: true
- ShowWindowsMediaPlayer: false
- ComputerName:
x86_Microsoft-Windows-UnattendedJoin_neutral
- Identification
- JoinWorkGroup:
WORKGROUPNAME
- JoinWorkGroup:
- Identification
The primary way I distinguish between different types of builds
(Office 2010 vs Office 2007, for example) is by changing
ComputerName
in the different unattend.xml files.
If you do not specify a ProductKey
then I believe that the installer
will use a default key, and I believe the installer will try to
activate itself by contacting a KMS host (for flavours that support
this). You can find a list of the default keys available for Windows 7
by looking for product.ini
in the sources
folder of the Windows
install DVD. Of course, the default keys will not activate Windows, so
you will have to activate afterwards.
The list of TimeZones is picky. You can find a list of strings that Microsoft claims works here: http://technet.microsoft.com/library/cc749073%28WS.10%29
The RunSynchronous
commands will come up again later. I wish I had
documented why I put in the second registry edit. I believe it
disables UAC (which is later restored in the postinstall.cmd script).
7 oobeSystem
x86_Microsoft-Windows-International-Core_neutral
- InputLocale, SystemLocale, UILanguage, UILanguageFallback, UserLocale: en-US
x86_Microsoft-Windows-Shell-Setup_neutral
- Autologon
- Enabled: True
- LogonCount: 1
- Username: firstuser
- Password
- Value :
type the password here; it will be scrambled later
- Value :
- Display
- ColorDepth: 32
- HorizontalResolution: 1024
- VerticalResolution: 768
- FirstLogonCommands
- SynchronousCommand[Order="1"]
Action: AddListItem
CommandLine:
cmd /c "copy \\ntinstall\win7-install\local\postinstall.cmd %TEMP% && %TEMP%\postinstall.cmd"
- SynchronousCommand[Order="1"]
Action: AddListItem
CommandLine:
- OOBE
- HideEULAPage: true
- HideWirelessSetupInOOBE: true
- NetworkLocation: Work
- ProtectYourPC: 1
- SkipMachineOOBE: true
- SkipUserOOBE: false
- UserAccounts
- LocalAccounts
- Action: AddListItem
- Group: Administrators
- Name: firstuser
- Password
- Value:
match the password in the other password field
- Value:
- Autologon
The LogonCount
means that the user firstuser
will be able to log
in once without a password. This gives the postinstall.cmd
script a
chance to run and complete the installation.
The postinstall.cmd
does most of the customization, which is why we
do not need the WAIK "Packages" functionality. Note that the command is
all one line.
The SkipMachineOOBE
and SkipUserOOBE
are deprecated, but they
still work. The NetworkLocation
does not seem to work for me --
after the install finishes the user is still prompted for network
locations.
Configure postinstall.cmd
The postinstall.cmd
command does a lot of the tweaks that are hard
to enter into the unattend.xml
file. The script runs as the first
user you defined (firstuser
in my case, which runs as administrator)
, but if UAC is enabled, then the script will give UAC
prompts.
Here are some choice elements of my postinstall.cmd
script. Sorry
that the line lengths are do ridiculous.
:: Make Admin password never expire
WMIC USERACCOUNT WHERE "Name='firstuser'" SET PasswordExpires=FALSE
You can do a lot of interesting things with WMIC.
:: ============ STARTUP SOUND ===========
cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\BootAnimation /v DisableStartupSound /t REG_DWORD /d 0 /f
This disables the startup sound.
:: ============ THEME STUFF =============
:: Copy default theme (no sound)
copy \\ntinstall\win7-install\local\includes\twc-default.theme %SYSTEMROOT%\Resources\Themes\
:: This supposedly sets the theme for all users
:: From: http://www.sevenforums.com/tutorials/80384-theme-specify-default-theme-load-new-users.html
:: First load the default user hive and edit it:
:: Edit HKLM\TempHive instead of HKCU
cmd /c reg load HKLM\TempHive "%SystemDrive%\Users\Default\NTUSER.DAT"
cmd /c reg add HKLM\TempHive\Software\Microsoft\Windows\CurrentVersion\Themes /v CurrentTheme /t REG_SZ /d "%SYSTEMROOT%\Resources\Themes\twc-default.theme" /f
cmd /c reg unload HKLM\TempHive
:: Now install the theme for firstuser
:: See http://www.sevenforums.com/themes-styles/93397-there-silent-command-line-operation-change-theme-2.html
\\ntinstall\win7-install\local\ThemeTool.exe changetheme %SYSTEMROOT%\Resources\Themes\twc-default.theme
Getting Windows 7 to shut up and not play sounds is pretty important for us. The good news is that this was scriptable -- I had a harder time in Windows XP (but I did not know about loading the default user hive then).
To make this twc-default.theme
I logged into a Windows machine,
right-clicked on the desktop, chose "Personalize" and changed the
sounds to be "No sounds". Then I saved the theme. This appears in the
current user's %userprofile%\AppData\Local\Microsoft\Windows\Themes
folder. If you do not use any custom wallpapers, this is a simple file
you can drop in.
The registry changes load the profile for the default user, make a change, and then save that profile. This means that every subsequent user will have the quiet theme by default. This does not force anybody to have a quiet theme, however -- users can still change their sounds. It is kind of confusing because simply changing the theme to a different one -- for example with different wallpaper -- will usually re-enable sounds.
Changing the theme for the current user is actually kind of hard. The
ThemeTool.exe
program is wacky. You have to generate it in a secret
way, as documented in the www.sevenforums.com link:
- Go to Control Panel / Troubleshooting
- Run "Appareance and Personalization" . Run the wizard but do not finish it.
- Look in "C:\Windows\Temp" for a folder that begins with the name "SDIAG_" (You may need to gain access to the folder as administrator.)
- There may be a
ThemeTool.exe
file already. If not, right-clickTS_ColorTheme.ps1
and run with PowerShell. This should generate the executable.
Copy the ThemeTool.exe executable to the network share (no doubt violating more EULAs).
:: ============ SOUND DRIVER STUFF =============
:: Install soundcard driver for dc7100s
:: WMIC trick from
:: http://myserverissick.com/2008/04/find-a-computers-model-using-the-command-line-or-in-a-batch-file/
FOR /F "tokens=2 delims==" %%A IN ('WMIC csproduct GET Name /VALUE ^| FIND /I "Name="') DO SET machine=%%A
ECHO Computer model: "%machine%"
:: Testing strings is complicated! The idea is to make a new string
:: by GETTING RID OF the text you want, and then compare results
set machine_testdc7100=%machine:dc7100=%
ECHO DC7100 test: %machine_testdc7100%
:: Gah. There is a "Would you like to install this device
:: software?" prompt. We need to trust some nonsense cert.
:: http://www.migee.com/2010/09/24/solution-for-unattendedsilent-installs-and-would-you-like-to-install-this-device-software/
:: The strings are different, so "dc7100" was removed
IF NOT "%machine%" == "%machine_testdc7100%" (
certutil -f -addstore "TrustedPublisher" \\ntinstall\win7-install\drivers\dc7100-audio\analog-devices-inc.cer
\\ntinstall\win7-install\drivers\dc7100-audio\sp36530\setup.exe /s
)
This is an example of installing a driver for a particular machine -- in our case, a Compaq/HP dc7100. The basic idea is to use WMIC to get the name of the computer, and then execute the code only if that machine matches.
:: Run wpkg script
\\ntinstall\wpkg\warez\wpkgsync\wpkgsync.bat
This installs third party software, as described in the next section.
:: Enable stupid "The publisher could not be verified" warnings again
:: From http://www.mockbox.net/windows-7/278-how-to-disable-windows-7-popup-the-publisher-could-not-be-verified
cmd /c reg add HKCU\Software\Microsoft\Internet Explorer\Security\ /v DisableSecuritySettingsCheck /t REG_DWORD /d 0 /f
:: Enable UAC again
cmd /c reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 1 /f
This undoes the registry changes done in the 4 specialize
section of
the unattend.xml
file.
Install third-party software with WPKG
Configuring WPKG is an entire topic unto itself, so I am just going to sketch the basics here. There is lots of documentation available at http://wpkg.org . Once WPKG is configured and working it is pretty sweet, but it took a lot of time to set up.
If you have an IT budget, then I would recommend using a service such as Ninite Pro instead: https://ninite.com/pro . It handles a lot of third party software with a lot less hassle than WPKG. There is a version of Ninite that is free for home users.
The advantage of using WPKG is that it can potentially install any software used at your organization, whether open or closed source. The critical factor is that it must be possible to script the program's installation non-interactively. This is usually possible, either because the applicaton has a "silent installer" flag, or because a program like AutoIT can be used to click the buttons required to install the application.
In addition to the http://wpkg.org site, you can find information about silent installers on the old (and loved) Unattended installation site: http://unattended.sourceforge.net/installers.php . (We used the Unattended infrastructure to install Windows XP, and its approach definitely influenced this one.)
To set up WPKG, you need yet another samba share, which I placed at
\\ntinstall\wpkg
. This needs to be visible by all clients (and it
should be read-only).
Inside this share are the following files:
wpkg.js
and the other WPKG binary filespackages.xml
and thepackages/
folder, which contain XML script files for each of the applications you wish to install. Most of the work in maintaining WPKG is creating these script files (or copying them from wpkg.org) and then keeping them up to date.profiles.xml
, which bundles software packages into groups that can be installed on particular hosts.hosts.xml
, which defines hosts and assigns profiles to them.- A
%SOFTWARE%
folder, which contains the binaries to be installed.
For example, here is a fragment from my profiles.xml
file which
specifies all the software that gets installed on three different
classes of machine:
<profile id="default">
<package package-id="WinDirStat" />
<package package-id="AdobeFlashPlayer" />
<package package-id="SumatraPDF" />
<package package-id="JavaJRE" />
<package package-id="Firefox" />
<package package-id="7zip" />
</profile>
<profile id="staff-base">
<depends profile-id="default" />
<package package-id="TrueCrypt" />
<package package-id="LibreOffice" />
<package package-id="AdobeReader" />
<package package-id="WinDirStat" />
</profile>
<profile id="staff-win7">
<depends profile-id="staff-base" />
<package package-id="Office2010ProfessionalPlus-staff" />
</profile>
The default
class is common to every machine. The staff-base
is
used to keep Windows XP machines up to date, and the staff-win7
profile is used to install Win7 clients (which, unlike Windows XP
machines, have Office 2010 installed.)
Here is a snippet from my hosts.xml
that shows some of these
profiles getting assigned:
<host groups="WPKG-Abstainers" profile-id="nothing" />
<host name="to-be-named" profile-id="staff-win7" />
<host groups="Domain Computers" os="professional.+6\.1\.\d{4}" profile-id="staff-win7" />
<host groups="WPKG-Clients-WinXP" profile-id="staff-base" />
The first line specifies an Active Directory group that is used to exclude certain machine from any WPKG updates at all (because the "nothing" profile will have no packages defined.
The second line assigns clients with the name "to-be-named" (as defined
in the WAIK answer file unattend.xml
) to be given the "staff-win7"
profile.
The third line filters on two things: the Active Directory group "Domain Computers" AND the operating system Windows 7 Professional.
The fourth line filters Windows XP clients, which must be in the Active Directory group "WPKG-Clients-WinXP" to have WPKG updates applied to them. (Note that these groups are COMPUTER groups, not USER groups.)
Again, the WPKG website has good documentation about filtering on hosts: http://wpkg.org/Extended_host_attribute_matching
Finally, here is a (very simple) package configuration file, to install and uninstall Inkscape:
<packages>
<package
id="Inkscape"
name="Inkscape"
revision="3"
reboot="false"
priority="50">
<variable name="version" value="0.48.4" />
<variable name="extra" value="-1-win32" />
<check
type="uninstall"
condition="exists"
path="Inkscape %version%" />
<install cmd='"%SOFTWARE%\Inkscape\Inkscape-%version%%extra%.exe" /S' />
<upgrade include="install" />
<remove cmd='"%PROGRAMFILES%\Inkscape\uninstall.exe" /S' />
</package>
</packages>
Many packages require some tweaking to install cleanly, but often other people have done the hard work already, and you can download XML files from the WPKG website. There is good documentation on creating a package script file here: http://wpkg.org/packages.xml
The postinstall.cmd
script above calls a small batch wpkgsync.bat
,
which looks something like this:
@echo off
rem Synchronize WPKG from server without any client
set SOFTWARE=\\ntinstall\wpkg\warez
%SOFTWARE%\tools\wpkgMessage.exe /package "Installing.."
start /b %SOFTWARE%\tools\wpkgMessage.exe
cscript \\wpkg-server\wpkg\wpkg.js /synchronize
%SOFTWARE%\tools\wpkgMessage.exe /package "Software updates completed!"
%SOFTWARE%\tools\wpkgMessage.exe /terminate
There are only two important lines in this script:
set SOFTWARE=\\ntinstall\wpkg\warez
cscript \\wpkg-server\wpkg\wpkg.js /synchronize
The rest of the file is to display messages on the screen, using the wpkgMessage.exe program available here: http://www.gig-mbh.de/edv/index.htm?/edv/software/wpkgtools/wpkg-message-english.htm
We also call the wpkgsync.bat
script to keep software up to date. In
Active Directory, we use a shutdown script. Unfortunately, there is a
bug: Unless otherwise specified, Windows 7 terminates shutdown scripts
after 10 minutes. So we have to change some parameters and make
registry edits on the clients, as documented here:
http://www.mail-archive.com/wpkg-users@lists.wpkg.org/msg05228.html
Here are the steps:
In Group Policy:
Computer Configuration | Administrative Templates | System | Scripts
setMaximum wait time for Group Policy scripts = 1800
(which gives WPKG 30 minutes to runAs a Group Policy preference, set the following key:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\gpsvc] "PreshutdownTimeout"=dword:1b7740
In Group Polcy:
Computer Configuration | Administrative Templates | System | Logon
setRun shutdown scripts visible = Enable
In Group Policy:
Computer Configuration | Administrative Templates | System
setVerbose vs normal status messages = Enable
In Group Policy:
Computer Configuration | Windows Settings | Scripts | Shutdown
run\\ntinstall\wpkg\warez\wpkgsync.bat
(This part of the installer is not fully tested yet.)
Future Work
- Thus far, I have not incorporated x64 builds into this infrastructure. I expect it is possible, but there will surely be quirks.
- Maintaining multiple
unattend.xml
files is irritating. - Batch files are awful, and Windows 7 includes Powershell support by
default. Where possible (eg with the
postinstall.cmd
file) it would be nice to replace the batch files with something saner. - I have not incorporated Windows updates into the installer, so machines need hundreds of updates once desployed. I expect that http://download.wsusoffline.net will be helpful here, but I have not integrated it into the installer.
- I am not happy with the sound schemes. I would like most people to have no sound effects in their themes regardless of what desktop theme they choose, but I do not want to disable sound entirely.