This document is part of Unattended, a Windows
deployment system.
After you use Unattended a few times, you will probably get tired of answering the same questions over and over. This document describes how to create site-specific configuration files to set (or compute) the answers automatically.
Unattended looks for all site-specific configuration in
Z:\site
. In particular, the master installation script
(install.pl
) reads Z:\site\unattend.txt
and Z:\site\config.pl
.
To perform simple customizations, just create and populate
z:\site\unattend.txt
. The values you put there will
override the defaults and cause the installation script to skip any
corresponding questions. Do not worry about creating a "complete"
answer file; if you fail to provide some required value, the
installation script will prompt you for it.
Read on for a description of what to put in your
unattend.txt
file.
(The Z:\site\config.pl
mechanism is more complex and
is discussed way below.)
unattend.txt
The general syntax of an answer file is the same as any
.ini
(or .inf
) file. It looks something
like this:
[SECTION 1] ; COMMENT KEY1=VALUE1 KEY2 KEY3="funny;quoted=value" [SECTION 2] . . .
That is, it consists of sections, each headed by a section name in square brackets. Each section has zero or more entries (also called settings). Each entry assigns a value to a key, or else consists of a key by itself. Any line beginning with a semicolon, or whitespace followed by a semicolon, is a comment.
If the value contains any special characters, or if it is the empty
string, it must be placed in quotation marks. Which characters
qualify as "special" is undocumented; the .ini
file
parser/generator in Unattended tries to be fairly conservative, and
will exit with an error message if it encounters an unrecognized line.
Quotes are allowed even when they are not required, so when in doubt,
use them.
For a complete sample unattend.txt
file, use
Unattended to install Windows and then examine
C:\NETINST\UNATTEND.TXT
. For a partial file containing
some of Unattended's defaults, see Z:\lib\unattend.txt from the distribution.
The most thorough documentation for the answer file is Microsoft's own, but unfortunately, it is a bit annoying to obtain. There are different versions for Windows 2000 and Windows XP, although the answer files are very similar.
unattend.doc
file from the
deploy.cab
archive.
You can also find an earlier version of deploy.cab
on
the Windows 2000 CD in the \support\tools
folder.
deploy.cab
. Then use Internet Explorer to extract
ref.chm
, a Windows "Compiled Help" file, which you can
double-click to browse.
Here again, deploy.cab
may also be found on the
Windows XP CD in the \support\tools
folder.
Here are examples for how to configure some common settings. All of these are used by Windows Setup, which means they are fully explained in Microsoft's documentation. These examples are just to help you get started quickly.
Example:
[UserData] FullName="Jane Doe" OrgName="FooBar Widgets, Incorporated" ComputerName=magneto
Setting the ComputerName to *
tells
Windows Setup to pick a random name.
[UserData] ProductKey=XXXXX-YYYYY-ZZZZZ-00000-11111
NOTE: Prior to Windows XP, this key was named "ProductID".
To set the local Administrator account password:
[GuiUnattended] AdminPassword=sekrit
To join the domain FOOBAR
using the account
FOOBAR\wsadmin
and password verysekrit
:
[Identification] JoinDomain=FOOBAR DomainAdmin=FOOBAR\wsadmin DomainAdminPassword=verysekrit
If you do not want to store the password in cleartext, you can omit the DomainAdminPassword entry; remember that the installation script will prompt you for any required values which you do not already provide.
To join the workgroup FOOBAR:
[Identification] JoinWorkgroup=FOOBAR
Note that you are required to join either a domain or a workgroup.
To specify the Organizational Unit to join within an Active Directory domain:
[Identification] MachineObjectOU="OU=Foo,OU=FooParent,DC=department,DC=example,DC=com"
See also KB article 226315.
To set the workstation's time zone:
[GuiUnattended] ; U.S. Pacific TimeZone=004
The time zone setting is numeric; see the table of index numbers for a complete list. The default value for Unattended is 035 (U.S. Eastern).
You may specify additions to the search path for Plug&Play
drivers. Elements of this path are separated by semicolons. Windows
uses this path when searching for a driver for a piece of hardware.
These elements are relative to the C:
drive, and are
usually used in conjunction with the $oem$/$1
mechanism.
For example:
[Unattended] OemPnPDriversPath="drivers\net\eepro;drivers\video\nVidia"
The [Shell]
section controls the general look and feel
of Windows XP. To use the classic Windows Start menu (with
My Computer and My Documents on the
desktop), and to use the "classic Windows visual style":
[Shell] ; Use classic start menu DefaultStartPanelOff=Yes ; Use classic visual style DefaultThemesOff=Yes
Unattended adds some functionality on top of
Windows Setup. This functionality is controlled by a
new section of the answer file, the [_meta]
section.
This section is ignored by Windows Setup; it exists
solely to let you provide answers to some of the new questions install.pl
asks.
The installation script begins by partitioning and formatting the disk.
To automatically answer the "Use large disk support" question, set
the fdisk_lba
key to 1 for "yes" and 0 for "no".
To automatically partition the drive, use the
fdisk_cmds
key. This is a semicolon-separated list of commands invoking FreeDOS FDISK. Keep in
mind that the result of these commands must be a partition table with
an active FAT partition. To suppress the confirmation for disk
partitioning, set fdisk_confirm
to 0.
To automatically format the drive, set the format_cmd
key. This is normally an invocation of the FreeDOS format utility.
Finally, to automatically replace the Master Boot Record (MBR) or
not, use the replace_mbr
key. Set it to 1 for "yes" and
0 for "no".
For example, to use large disk support, partition the drive as a single large partition (without confirmation), format it, and replace the MBR:
[_meta] fdisk_lba=1 fdisk_cmds="fdisk /clear 1;fdisk /pri:4000;fdisk /activate:1" fdisk_confirm=0 format_cmd="format /y /z:seriously /q /u /a /v: c:" replace_mbr=1
To configure which top-level post-installation script to run, set
the top
key.
To configure which "optional" scripts to run, set the
middle
key.
To configure a script to run last, just before the final cleanup
and reboot, set the bottom
key (normally unset).
To configure which domain accounts are added to the local
Administrators group, set the local_admins
key to a
semicolon-separated list of user names. An empty list is allowed, but
it must be quoted. User names may be fully qualified (DOMAIN\user),
or they may be bare (user); in the latter case, the
[Identification]/JoinDomain value will be used as the domain.
To configure the NTP servers, set the ntp_servers key. This is a space-separated list.
To control the final question, where you are asked if you want to make any final edits, use the edit_files key. Set it to 0 to avoid being asked the question.
For example, to perform a base install, add Spybot Search&Destroy and the Sun JRE, configure NTP servers named "ntp-0" and "ntp-1", not add any accounts to the local Administrators group, and skip the final question:
[_meta] top=base.bat middle="spybot.bat;sun-jre.bat" local_admins="" ntp_servers="ntp-0 ntp-1" edit_files=0
There are several other keys which appear in the
[_meta]
section, like macaddr
and
ipaddr
. Most of these are generated automatically from
sane defaults, so unless you are sure about what you are doing, you
should probably omit them from your unattend.txt
file.
If the static configuration options provided by
unattend.txt
are not sufficient, you can create
arbitrarily complex rules using Z:\site\config.pl
. This
is a Perl file which install.pl
reads.
To write your own config.pl, you need to know a little Perl and you need to understand how the installation script works.
The installation script generates the answer file in memory,
placing it in an Unattend::IniFile object named $u
.
Programmatically, this object behaves like a Perl hash (associative
array). It maps section names to sections, where each section is
another hash which maps keys to values. So, for example, the value of
the FullName key in the [UserData] section is just
$u->{'UserData'}->{'FullName'}
, and you may read or
assign this value in your config.pl.
But these hashes are special in two ways.
First, they are case-insensitive, so that
$u->{'UserData'}->{'FullName'}
and
$u->{'userdata'}->{'fullname'}
refer to the same
thing.
Second, if you assign a Perl subroutine to a key, something magic happens when you read the key: The subroutine will be called with no arguments, and the subroutine will be replaced by its own return value. These stored subroutines are called "promises", and the act of evaluating the subroutine and replacing the value is called "forcing" the promise. (I knew that CS degree would be useful someday.)
For example, suppose you wanted the local Administrator password to be the same as the user's FullName. This is not a very realistic example, perhaps, but it will serve for illustration. You would put this in config.pl:
$u->{'GuiUnattended'}->{'AdminPassword'} = sub { return $u->{'UserData'}->{'FullName'}; }; 1;
This promise will not be forced until the AdminPassword key is read (possibly not until the unattend.txt file is actually being generated). When that happens, the subroutine will read the value of the FullName key in order to return it. That, in turn, may cause another promise to be forced, and so on... But in the end, the FullName will be returned by this subroutine, and it will be stored and used as the value for AdminPassword.
In fact, install.pl simply assigns a "default value" for most keys which is a subroutine to ask the user an appropriate question. Then it reads unattend.txt and config.pl, each of which may override the defaults with static values or with different subroutines.
This design requires that you think in a "declarative" style rather than an "imperative" one. That is, you should think about how each key is to be computed from other data (including other keys). Except for the top-level assignments of subroutines, you should avoid assigning to keys themselves.
One more thing. The config.pl script is executed by Perl's "do" operator, which returns the value of the last expression in the file. So the last line of config.pl should always be a constant true expression, like this:
1;
This is to ensure that the "do" operator returns success, and to prevent any promises from being forced prematurely.
Some examples should help.
To automatically add all drivers to OemPnPDriversPath, you just crib the code from install.pl but skip the part where it asks the question:
use warnings; use strict; $u->{'Unattended'}->{'OemPnPDriversPath'} = sub { my $media_obj = Unattend::WinMedia->new ($u->{'_meta'}->{'OS_media'}); my @pnp_driver_dirs = $media_obj->oem_pnp_dirs (1); # No driver directories means no drivers path scalar @pnp_driver_dirs > 0 or return undef; print "...found some driver directories.\n"; my $ret = join ';', @pnp_driver_dirs; # Setup does not like empty OemPnPDriversPath $ret =~ /\S/ or undef $ret; return $ret; }; 1;
This code illustrates a few points.
First, all Perl code you ever write should "use warnings" and "use strict". Do not even think twice about it.
Second, the last line of the file is 1;
.
Third, if a key has a value of "undef", it will not appear in unattend.txt at all. If you want to delete a key completely, make it undef.
Finally, this code demonstrates the use of the Unattend::WinMedia helper object. You create an instance of this object by giving it the path to your Windows installation media ([_meta]/OS_media value). It knows lots of things about such media, including how to grovel it for OEM Plug&Play drivers (oem_pnp_dirs() method).
To pick the product key based on OS type, you would use code like this:
use warnings; use strict; $u->{'UserData'}->{'ProductKey'} = sub { my $media_obj = Unattend::WinMedia->new ($u->{'_meta'}->{'OS_media'}); my $os_name = $media_obj->name (); if ($os_name =~ /Windows XP/) { return 'MY-WINDOWS-XP-KEY'; } elsif ($os_name =~ /Windows Server 2003/) { return 'MY-SERVER-2003-KEY'; } return undef; }; $u->{'UserData'}->{'ProductID'} = sub { my $media_obj = Unattend::WinMedia->new ($u->{'_meta'}->{'OS_media'}); my $os_name = $media_obj->name (); if ($os_name =~ /Windows 2000/) { return 'MY-WINDOWS-2000-KEY'; } elsif (defined $u->{'UserData'}->{'ProductKey'}) { # It is OK for us to return undef as long as there is a # ProductKey. return undef; } die "No ProductKey nor ProductID!"; }; 1;
This code sets ProductID for Windows 2000 and ProductKey for Windows XP and Windows Server 2003. (Although the later OSes accept ProductID for backwards compatibility, ProductKey is now canonical and we like to be pedantic.) The code dispatches on the name of the chosen operating system, as returned by the name() method of the Unattend::WinMedia object.
If you want to use different unattend.txt
files
depending on the type of OS being installed:
my $media_obj = Unattend::WinMedia->new ($u->{'_meta'}->{'OS_media'}); my $os_name = $media_obj->name (); if ($os_name =~ /Windows 2000/) { $u->read (dos_to_host ('z:\\site\\win2k-un.txt')); } elsif ($os_name =~ /Windows XP/) { $u->read (dos_to_host ('z:\\site\\winxp-un.txt')); } else { die "Unrecognized OS name: $os_name"; } 1;
Then put the answer files for Windows 2000 and Windows XP in
z:\site\win2k-un.txt
and
z:\site\winxp-un.txt
, respectively.
Note the call to dos_to_host
. This function does
nothing on the DOS-based boot disk, but on the Linux-based boot disk
it converts DOS-style file names (e.g., z:\site\foo.txt
)
to Linux-style (/z/site/foo.txt
). It lets you write most
config.pl
files to work unaltered with either boot
disk.
With this code, you will probably prefer to use the Linux-based boot disk. Since this code depends on the OS, it will cause the OS selection question to be asked immediately, even before you select how to partition the drive. (This is actually correct behavior, since you might have partitioning commands in your OS-dependent answer files.) If you must reboot after partitioning, as is usually the case with DOS, you will end up having making the OS selection again.
To automatically set the machine's ComputerName based on the DNS hostname associated with the IP address assigned to the machine:
use warnings; use strict; use Socket; use Net::hostent; $u->{'UserData'}->{'ComputerName'} = sub { my $addr = $u->{'_meta'}->{'ipaddr'}; defined $addr or return undef; my $host = gethostbyaddr (inet_aton ($addr)); if (!defined $host) { warn "Unable to gethostbyaddr ($addr): $? $^E\n"; return undef; } my $name = $host->name (); # Strip off domain portion $name =~ s/\.(.*)//; return $name; }; 1;
There are two things to note about this code. First, it will only work with the Linux-based boot disk. And second, I have not actually tested it yet. If you try it, please let me know how it goes :-).
More examples to come, someday.