This document is part of Unattended, a Windows
deployment system.
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:
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, where I work, we have a large standard suite of applications, but sometimes we are asked to configure a single unusual PC. In such cases, we want to install the OS by hand but still have an automated procedure for loading the applications.
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 my answer.
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.
The process goes like this:
install
share as the Z:
drive.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.
install
shareThe 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:
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:
PATH
) for
running the other scripts.In addition to simple commands, the to-do list may contain
directives for todo.pl
. Directives always start
with a dot and include:
.ignore-err
directiveLike 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.
.reboot
directiveThe .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.
.reboot-on
directiveThe .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 I 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"
.sleep
directiveIf 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"
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:
...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.
Some examples should help. All of these are from the install/scripts directory in the distribution.
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
.
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
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.
The winxpsp1-updates.bat script installs all of Microsoft's
"critical" and "recommended" updates for Windows XP Service
Pack 1. 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
".
The base.bat script performs a "base workstation" installation for my 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.
The sales.bat script performs a "salesperson laptop" installation for my 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.
To create an installation script for an application, you need to know how to install that application in "unattended" mode. To help you, I am collecting a list of unattended/silent mode installer switches for common installers and applications. Contributions to this list are most welcome.
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 --help
for full usage instructions.
Reboot to make the changes take effect.boot.ini
to get rid of the useless menu option.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.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 in unattend.txt
with X
marks.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.exe
which lets you install a service from the command line. It is the
only such tool I could find, but invoking it requires including the
password on the command line. The instsrv.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.
shortcut.pl "C:\Foo\foo.exe" special:AllUsersDesktop
to create a desktop shortcut for all users.
Run shortcut.pl --help
for documentation.
shutdown.exe
from the
NT or 2000 Resource Kit. Now with XP, there is no Resource Kit, but
shutdown.exe
is standard. Of course, the new
shutdown.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. Run shutdown.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.)
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. Most
approaches 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.
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 form
PIDKEY=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?