Dynamic configuration with server-side perl scripts
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.
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 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;
Some examples
Some examples should help.
Computing OemPnPDriversPath automatically
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).
Assigning product key based on OS type
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.
Reading different answer files based on OS type
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.
Assigning ComputerName based on DNS hostname
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...
More examples to come, someday.
Using a database
If you wish to store and organize installation settings for a large number of computers you are best off with a database of some sort. This is where Unattended really shines in large organizations.
Unattended offers two options:
- MySQL Database
- CSV Flat File
Setting up a CSV flat file system
- Make a backup of the original
config.pl
and replace it with the cvs flat file-enabled sample:mv site/config.pl site/orig-config.pl cp site/sample-config.pl site/config.pl
- Edit
site/unattend.csv
based upon the format described at the beginning oflib/conf-csv.pl
and the Lookups and Properties described in the beginning of (the new)site/config.pl
and populate it with your data. - Done!
Setting up a MySQL system
- Set up the MySQL Database on a server using the comments at the beginning of
lib/conf-mysql.pl
. - Populate the database to suit your needs. Use the guidelines at the beginning
of
site/sample-config.pl
and look atsite/unattend.csv
for examples. - Make a backup of the original
config.pl
and replace it with the database-enabled sample:mv site/config.pl site/orig-config.pl cp site/sample-config.pl site/config.pl
- Edit site/config.pl.
- Uncomment the two lines under
# Set db for mysql interface
. - Comment out the two lines under
# Setup db for CSV interface
. - Edit the second line with the correct mysql host, username, and password.
- Make sure the MySQL server can be reached by any client you install.
- Done!
Making the most of your Database
If you prefer to have a more straightforward list of MAC addresses associated with computer names, organization names, passwords, etc., you could create this list and then generate the MySQL DB or CSV file that Unattended prefers from this list.
Further development
If you wish to modify unattend.txt
setting that Unattended does
not have properties for, you can use a database to generate the Unattended MySQL
database linking MAC addresses -> computer names and computer names ->
specific unattend.txt
file. Then you use your master database to
generate these unattend.txt files.
In Active Directory environments, remember you don't need to do everything with
Unattended. Software can be added and settings configured with Group Policy.
With the ComputerName
and MachineObjectOU
you can add
the computer directly to the OU
you want it in.
If you developed other database-modules (e.g. for LDAP or PostgreSQL or such) to meet your environment, we would love to hearing from you and appreciate your contribution!