Goals
Our objective is to create a set of scripts which automatically install all of the applications we want. Important design goals for these scripts include:
- Independence from the OS install
It should be possible (though not necessary) to install the applications independently of the automated OS installation, because sometimes automating the OS installation is more trouble than it is worth. For example, in many organizations, there are a large standard suite of applications, but sometimes sysadmins are asked to configure a single unusual PC. In such cases, they want to install the OS by hand but still have an automated procedure for loading the applications.
- Composability
We should be able to define collections of software in terms of other collections. For instance, we should be able to express:
A base installation consists of applications A, B, and C. A developer workstation consists of a base installation plus a development environment. A sales laptop consists of a base installation plus the salesforce automation tools.
And so on.
In short, these installation scripts need to be able to invoke other installation scripts. You might expect this to be trivial, until you remember that installing some software requires rebooting the machine. So if (say) sales.bat begins by invoking base.bat, which needs to reboot the machine halfway through, how exactly do you resume where you left off? See below for the answer.
- Simplicity
However this system works, it must be as simple to understand as possible. Sysadmins are busy people. In fact, I am amazed you have read this far :-) . Anyway, if you can think of a simpler approach for anything here, please let me know.
Overview
The process goes like this:
- Map the
install
share as theZ:
drive. - Install Perl.
- Install everything else.
Perl is required because the handy utility scripts are written in Perl. You do not need to know Perl to use the scripts, but you should learn it anyway because it is a good tool.
Note that this process is normally invoked immediately after the unattended OS installation by the GuiRunOnce
section in the unattend.txt
file. In particular, this
process assumes that the machine is already configured to automatically log on as
the local Administrator after every reboot. To invoke this procedure standalone,
you can use the autolog.pl script to enable or disable
automatic logon.
Structure of the install
share
The install
share is the one you created when you set up the automated OS installation, by simply copying
the install
directory from the Unattended distribution.
Application installation relies on these subdirectories of the install
share:
- bin
- Contains various utility binaries and scripts. You should not need to modify these; if you do, please consider submitting a feature request or a patch.
- packages
- Contains the installers for the applications themselves. You will need to populate this directory with the installers for your site's applications.
- scripts
- Contains the scripts for installing individual applications and sets of applications. The contents of this directory provides a fairly rich set of examples. You will probably need to edit these or write new ones; feel free to contribute changes and additions.
- site
- Contains site-specific configuration data like license keys. You will need to populate this directory with the data for your site before some of the sample scripts will work.
The todo.pl driver script
One script to rule them all
One master Perl script, todo.pl
, oversees the entire installation
process. All of the other scripts are designed to be invoked by todo.pl
.
todo.pl
maintains a "to-do list" on disk in the
plain-text file C:\netinst\todo.txt
. You can edit this file with
an ordinary text editor, but normally you will not, because todo.pl
itself takes any number of commands as arguments and inserts them at the front of the to-do list. (As you will see, this is what you want.) When
invoked as todo.pl --go, the script removes the first command
from the to-do list and executes it, then removes the next command from the list
and executes it, and so on until the list is empty.
There are multiple advantages to this design:
- It provides a consistent environment (e.g.,
PATH
) for running the other scripts. - It notices any errors returned by the other scripts, halting execution so that you can debug the problem.
- It provides controlled reboots.
- It lets us satisfy the "composability" goal in a very natural way. Programmers will recognize the to-do list as a simple stack (or "continuation"). Since it is stored on disk, it naturally survives reboots.
In addition to simple commands, the to-do list may contain directives for todo.pl
. Directives always start with a dot
and include:
The .ignore-err directive
Like its author, todo.pl
is nigh-pathologically cautious about
error checking. Any command which exits with a non-zero status will cause the
script to halt and print a diagnostic.
Some installers always exit with zero status, even when they fail. There is not much we can do about this, and it keeps us awake at night.
Contrariwise, some installers always exit with a non-zero status even when they
succeed. For such installers, you can use the .ignore-err
directive
to ignore only the expected exit status, while still halting if any unexpected
errors occur.
For example, suppose you have an installer foo.exe
which always
exits with status 37. You would schedule it for invocation like this:
todo.pl ".ignore-err 37 foo.exe"
This will add ".ignore-err 37 foo.exe
" to the
to-do list. When todo.pl
processes this line, it will invoke foo.exe
, silently ignoring exit status zero or 37. It will still treat
other status codes as errors.
The .reboot directive
The .reboot
directive instructs todo.pl
to reboot the
machine after first patching the registry to cause itself to run
the next time the current user logs in.
In other words, .reboot
provides a controlled, fully synchronous
mechanism for rebooting the machine and resuming where you left off. This is
important, because uncontrolled reboots create race conditions as the OS kills
all processes with indeterminate ordering and timing. When you write a script to
install an application, you must suppress any reboots performed by the installer
and use the .reboot
directive instead.
The .reboot-on directive
The .reboot-on
directive tells todo.pl
to run a
command and compare the exit status to a specified value. If it matches, todo.pl
behaves as if it had seen a .reboot
directive.
Otherwise, it does nothing special; that is, it behaves as if .reboot-on
were not present.
This directive is useful because most Microsoft installers exit with status 194
when they want to reboot the machine, but you suppressed the automatic reboot
with a command-line switch. (This is by observation; as far as we know, it is
documented nowhere.) For such installers, you would use .reboot-on
like so:
todo.pl ".reboot-on 194 q999999.exe /q /r:n"
The .sleep directive
If you ever have to use this directive, then you are doing something wrong, because there is no such thing as a guaranteed time bound for any Windows operation.
Unfortunately, some installers make it impossible to do things right. For example, they might fork a subprocess and exit. In this case, the most convenient thing might be to delay a while.
This directive simply takes an integer number of seconds as argument. For example, to sleep 37 seconds, you would do:
todo.pl ".sleep 37"
The environment
todo.pl
arranges to run all commands in a consistent environment,
with the following variables set.
PATH
The PATH
environment variable will have Z:\bin;Z:\scripts
prepended to it before any commands are run. So the
scripts may refer to each other and to the utility scripts without supplying a
full pathname.
WINLANG
The WINLANG
environment variable contains the three-letter language code for the currently running version
of Windows. This is useful for writing language-independent scripts.
WINVER
The WINVER
environment variable will contain a short string
representing the version of Windows:
- win2k
- For Windows 2000
- win2ksp4
- For Windows 2000 Service Pack 4
- winxp
- For Windows XP
- winxpsp1
- For Windows XP Service Pack 1
- ws2k3
- For Windows Server 2003
...and so on. This variable is useful in scripts whose behavior needs to vary based on OS; e.g., when installing hotfixes.
Z
The Z
environment variable contains the drive letter for the
installation share (default Z:
). We added this variable after a user
complained that his site was already using the Z:
drive for another
purpose. Unless you are another such user, you do not need to worry about this.
Z_PATH
The Z_PATH
environment variable contains the full UNC path of the
share which is mapped to the Z:
drive. Some applications (e.g., the
MSDN Library) remember which pathname was used to install them, and they
will occasionally search for things there. Since Z:
is only used
during the installation process, users may not have it mapped, so installing from
Z:
can cause the application to fail later. For such applications,
Z_PATH
provides a workaround; see msdn.bat for an example.
Examples
Some examples should help. All of these are from the install/scripts directory in the distribution.
Adobe Reader
The adobe-reader.bat script installs Adobe Reader. This is about as simple as an installation script can get.
To invoke this script manually, you would type:
Z:\bin\todo.pl adobe-reader.bat Z:\bin\todo.pl --go
Obviously, you could just invoke the Adobe installer directly. But that would
lose the consistent environment and error checking performed by todo.pl
.
Office XP
The officexp.bat script installs Microsoft Office XP and reboots the machine.
First, it pushes the .reboot
directive onto the to-do list. Then it
pushes directives to install each update for Office XP. Finally, it pushes the
directive to install Office itself.
To invoke this script manually, you would type:
Z:\bin\todo.pl officexp.bat Z:\bin\todo.pl --go
Combining scripts
To perform both the Office XP and Adobe Reader installations at once, you would type:
Z:\bin\todo.pl officexp.bat adobe-reader.bat Z:\bin\todo.pl --go
The first line adds officexp.bat and adobe-reader.bat to the to-do list. The
second command processes the list. The todo.pl
script begins by
removing officexp.bat
from the front of the to-do list and executing
it. The officexp.bat
script itself starts by pushing .reboot
onto the front of the to-do list.
At this point, the to-do list contains .reboot
followed by adobe-reader.bat
, and we are still in the middle of executing the officexp.bat
script itself.
Next, officexp.bat
pushes the updates onto the to-do list, and
finally it pushes the instruction to install Office itself and exits. Then todo.pl
regains control and continues processing the to-do list; that is,
it installs office followed by its updates. Next, it processes the .reboot
directive, by arranging to run itself after the next logon and
rebooting the machine. After the reboot, todo.pl
starts up again,
removes adobe-reader.bat
from the to-do list and executes it. And so
on.
The final result is that Office XP and Adobe Reader are both installed, even though the machine had to reboot in the middle.
A more complex example
The winxpsp2-updates.bat script installs all of Microsoft's "critical"
and "recommended" updates for Windows XP Service Pack 2. All this
script does is push a bunch of items onto the to-do list, including the
occasional .reboot
directive.
This example illustrates how to add a command with arguments to the to-do list by putting it in quotes. Without the quotes, spaces would separate multiple commands.
This example also illustrates the use of the .ignore-err
directive.
Finally, this example illustrates an important consequence of the "last in,
first out" semantics of todo.pl
. Since it always adds items to
the front of the to-do list, commands will execute in the
opposite order from which they are added. On the other hand,
todo.pl
will preserve the order of the commands if you pass several
of them on a single command line. Put another way, "todo.pl X
" followed by "todo.pl Y
"
has same effect as "todo.pl Y X
".
A high-level example
The base.bat script performs a "base workstation" installation for a organization. This includes a bunch of free software.
This example illustrates the use of the WINVER environment variable. The %WINVER%-updates.bat name, for example, will expand to win2ksp4-updates.bat on Windows 2000 Service Pack 4 and winxpsp2-updates.bat on Windows XP Service Pack 2.
A higher-level example
The sales.bat script performs a "salesperson laptop" installation for a organization. As you can see, this just performs a base installation, then adds Microsoft Office, Lotus Notes, the AT&T global network dialer, and the Shiva VPN client (now technically the Intel Netstructure VPN client, but I fear change).
These last two examples also illustrate how easily you can compose low-level scripts into high-level ones, no matter how many reboots the low-level scripts perform. Observe that if you make a change to the configuration in base.bat, the sales.bat script will automatically inherit it.
Unlimited composability is nice.
Database of "unattended" switches for various applications
To create an installation script for an application, you need to know how to install that application in "unattended" mode. To help you, we are collecting a list of unattended/silent mode installer switches for common installers and applications. Contributions to this list are most welcome.
Other utility scripts
Although todo.pl
is the most important script, there are others in
Z:\bin
which you might find useful.
Many of these scripts are good examples of how to use WMI, which can do quite a few things. WMI is a standard part of Windows 2000 and XP, and it is available as a free download for NT.
- auconfig.pl
- Configures the Automatic Updates feature introduced with Windows 2000 Service
Pack 3. The registry settings it tweaks are not really documented; this
third-party article is all we could find. Run
auconfig.pl --help
for full usage instructions. Reboot to make the changes take effect. - autolog.pl
- Patches the registry to enable or disable the "automatic logon" facility. Can also set the default user name and domain. Run autolog.pl --help for complete documentation.
- bootini.pl
- For some reason, all of my unattended installations end up displaying a boot
menu with an unbootable "Previous Operating System on C:\" option.
This even happens if I wipe the disk with zeroes
first. This script edits the hidden system file
boot.ini
to get rid of the useless menu option. - cert.pl
- This script adds a certificate to the
ROOT
certificate store. It depends on the CryptoAPI COM interface (CAPICOM), which you must install first. This means just copying the DLL to the right place and registering it; see capicom.bat for a sample installation script. - defrag.aut
- This is an AutoIt script to defragment the primary hard drive from a command prompt. Since we use a FAT partition which is converted to NTFS, the initial installation tends to be somewhat fragmented. I like to run a disk defragmentation before installing any software or hotfixes (to collect the free space), then again just before delivering the machine to the user.
- hidepw.pl
- According to Microsoft's Guide to
Unattended Setup, passwords in the
unattend.txt
file are erased when the installation finishes. I have not found this to be true. So I wrote this script to replace all passwords inunattend.txt
with X marks. - instances.pl
- WMI has many useful classes. This is a generic script to enumerate the instances
of any WMI class.
Running instances.pl --help will give you brief usage instructions. Try running it with arguments like Win32_Process, Win32_OperatingSystem, Win32_BIOS, or Win32_BaseBoard.
- instsrv.pl
- The Windows 2000 Resource Kit includes a tool named
instsrv.exe
which lets you install a service from the command line. It is the only such tool we could find, but invoking it requires including the password on the command line. Theinstsrv.pl
script uses WMI to perform the same task, but it prompts you for the password instead of using a command-line argument.Yes, strictly speaking, using this script means your installation will no longer be "fully unattended". But I do not like embedding passwords in world-readable scripts, and I hate using the GUI.
- rdconfig.pl
- This script enables or disables the Remote Desktop service (formerly known as "Terminal Services"). It simply invokes the SetAllowTSConnections method of the Win32_TerminalServiceSetting WMI class. Run rdconfig.pl --allow=1 to enable the Remote Desktop and rdconfig.pl --allow=0 to disable it. As usual, the --help switch will yield full documentation.
- setenv.pl
- This script takes two arguments, a variable name and a value. It sets the corresponding "System" environment variable to have that value, just as if you had set it from the GUI. This script can also set variables for specific users and variables on remote machines; run it with --help for more information.
- shortcut.pl
- This script creates a Windows shortcut. What makes it interesting is that it
uses Windows
Script Host so that it can locate the various special folders for you. So, for example, you could use
shortcut.pl "C:\Foo\foo.exe" special:AllUsersDesktop
to create a desktop shortcut for all users.Run
shortcut.pl --help
for documentation. - shutdown.pl
- Once upon a time, you could get
shutdown.exe
from the NT or 2000 Resource Kit. Now with XP, there is no Resource Kit, butshutdown.exe
is standard. Of course, the newshutdown.exe
uses completely different command-line switches from the old Resource Kit tool, making it annoying and confusing to use in a script.shutdown.pl
is a full-featured shutdown utility in 65 lines of Perl. Most of those lines are documentation and, of course, error checking. Runshutdown.pl --help
for details.(Note that the installation scripts do not use this program; they use the
.reboot
directive to todo.pl instead. But I am including it anyway for the heck of it.) - startup-type.pl
- This script lets you set the "startup type" (automatic, manual, disabled, etc.) for a service from the command-line. There are probably other tools out there to do this, but I got tired of looking for them and wrote my own.
- win2ksp4-notips.pl
- As you are no doubt aware, Windows displays lots of annoying first-time logon
junk ("tips"). This script gets rid of them for Windows 2000 Service
Pack 4. Note: This script represents my taste in things to disable; you may
want to modify it for your site.
Incidentally, this script includes examples of editing the registry settings for the default user; that is, the settings inherited by every new user who logs into the machine. Mostapproaches I have seen to this involve copying
NTUSER.DAT
from some other profile to the default user profile, but with Perl, you can edit this registry hive directly. - winxpsp2-notips.pl
- Similarly, but for Windows XP Service Pack 2.
- with-env.pl
- This script allows the output of one command to specify the environment for a
second command. The script takes two arguments, which are the commands to run.
The first command should output one or more lines of lines of the form:
VAR=VALUE
The script will parse this output, set the corresponding variables in the local environment, and execute the second command.
You might be wondering why anybody would want this. Well, the Windows command prompt is a pretty weak scripting language, but I cannot bring myself to depend on something else when I have Perl around. So, for example, I have a Perl script at my site (
z:\site\officexp-key.pl
) which looks up the Office XP product key for the current machine in a software license spreadsheet, and prints a single line of the formPIDKEY=xxx
. Then I invoke officexp.bat from sales.bat like this:with-env.pl z:\site\officexp-key.pl officexp.bat
The result is that the correct product key for Office is provided at installation time so that the user is not prompted for it later. Isn't software licensing fun?