Cobbler — инструмент в мире Linux, который можно использовать как Install Server, создания многих сценариев инсталляции по сети на основе одного или нескольких дистрибутивов Linux. Есть также поддержка инсталляций FreeBSD, VMware, Xen и Nexenta.
Хотелось бы при помощи него также гибко и универсально создавать свои сценарии сетевой инсталляции с разных дистрибутивов Windows (XP, 2003, 7, 8, 2008, 2012).
Про то как настроить и использовать cobbler для инсталляции Linux исчерпывающе написано на его официальном сайте — https://cobbler.github.io. Я же здесь сосредоточусь на своем варианте решения задачи относительно Windows.
Основной проблемой для создания своего сценария сетевой инсталляции была подготовка необходимых загрузочных файлов Windows в среде Linux. Генерация необходимых бинарных файлов у меня происходит при запуске post-триггера cobbler на команду
В моем случае файлы ответов генерируются в основном на основе версии Windows и довольно редко, дополнительно на основе имени профиля. Т.е. я выбрал для себя наиболее подходящие и в большинстве случаев используемые опции инсталляции, которые можно указать в файле ответов для каждой версии Windows.
Основная же работа по настройке варианта инсталляции, установке дополнительного ПО и т.д. лежит на скрипте, который я указываю как kickstart файл для профиля cobbler (win.ks) в случае инсталляции Windows и скачиваемого скриптом post_install.cmd уже в момент инсталляции. Cobbler в момент скачивания динамически генерирует код на основе шаблона win.ks. А в этом шаблоне большинство кода генерируется уже на основе имени профиля.
В результате, в большинстве случаев для изменения сценария инсталляции при отладке мне достаточно просто подредактировать текстовый файл win.ks на сервере и заново запустить инсталляцию.
Упрощая до логической взаимосвязи файлов, которые нужно изменить для каждого сценария, сетевую инсталляцию для Xp и 2003 можно схематично изобразить так:
Для Windos 7 и новее:
Не самый, конечно короткий путь от пункта в PXE меню до запуска скрипта post_install.cmd с именем профиля в качестве параметра. Кроме того этот скрипт должен еще взять у cobbler сервера kickstart соответствующий этому профилю и запустить его.
Считаем что cobbler 2.6.9 и все необходимое для его работы у вас уже установлено, настроено (например iptables, SElinux) и прекрасно справляется с инсталляцией Linux.
Устанавливаем все необходимое:
Использовать hivex напрямую не удобно — здесь (неплохой вариант инсталляции, но мне не понравился из-за того, что использует анализ логов tftp, зато бинарники корежить не нужно) я нашел готовый скрипт (bcdedit.pl), который делает необходимые изменения в стандартном BCD. По сути он является заменой Windows утилиты bcdedit. Написан он на Perl, я его извлек из архива и поместил в /usr/local/bin, предварительно видоизменив:
Добавленная строка аналогична выполнению команды:
В каталоге tftp сервера создаем папку, где у нас будут лежать дистрибутивы Windows:
Теперь там создаем каталоги для каждого дистрибутива и копируем туда его содержимое.
У меня это выглядит так:
Создаем пользователя с минимальными правами, которого будем использовать при инсталляции. У меня: install/install
Ситуация с Win 7, 8, 2008, 2012 значительно хуже чем в Xp. Здесь так просто в бинарниках не поковыряешься — нужно потом контрольную сумму в нем пересчитывать.
Файл ответов для автоматической инсталляции тут тоже есть и более современного вида — в xml формате. В нем много улучшений, например можно указать как нужно диски на разделы порезать и т. д. Но есть и большая ложка дегтя — имя файла ответов зашито в стартовый скрипт, которой собственно и начинает инсталляцию. В свою очередь сам скрипт зашит в wim образ.
Т.е. остались еще большие неудобства с исправлением бинарников, а кроме того для каждого сценария инсталляции на основе одного дистрибутива я теперь должен иметь отдельный wim образ, который отличается от другого такого же образа только именем файла ответов в стартовом скрипте.
В каталоги с дистрибутивами нужно положить следующие файлы:
Создаем правила преобразования имен файлов для tftp:
Указываем правила преобразования в шаблоне cobbler для tftp:
Добавляем в файле /var/lib/cobbler/distro_signatures.json в раздел windows информацию о версиях, чтобы через метаданные cobbler их можно было использовать в шаблонах.
При настройке инсталляции Windows удалось сохранить основные преимущества инсталляции Linux через cobbler формированием файла ответов и пост-инсталляционного скрипта из шаблонов.
В состав шаблонов для Windows у меня входят следующие файлы:
Создаем шаблон скрипта, запускаемого после установки windows (неважно какой версии).
Скрипту при запуске передается имя профиля (варианта инсталляции) cobbler в качестве параметра.
Немного пояснений по шаблону:
В каждом каталоге с дистрибутивом есть папка $OEM$/$1/TMP в которой лежит wget.exe, todos.exe и dll'ки необходимые им для запуска. Точнее лежат в одной папке, а другие просто symlink на нее.
Файл, который мы скачивали wget'ом и который играет роль пост-инсталляционной части kickstart Linux выглядит в упрощенном виде так:
Снипеты здесь используются для того чтобы не загромождать код. В них проводится проверки на гипервизор, ставятся нужные драйвера, в зависимости от дистрибутива и профиля ставится или не ставится то или иное ПО.
Это единый шаблон из которого формируются файлы ответов всех версий windows. В нем, как и в других шаблонах можно использовать все мета-переменные cobbler.
Такие, например как $arch, $distro_name, $profile_name.
Ключевой момент здесь в строках, при помощи которых стартует скрипт с именем профиля в качестве параметра:
Шаблон на основе которого формируются стартовые скрипты для wim образов.
Стандартные образы для сетевой инсталляции Win 7 и Win 8 с интегрированными драйверами.
По команде
Каждую такую копию монтируют, в нее копируют стартовый скрипт созданный на основе шаблона startnet.template и демонтируют в триггере на post-sync.
Для работы с wim образами в Linux я использую wimlib
Для ее работы нужно установить пакет ntfs-3g, который в свою очередь работает через fuse.
Поэтому если как я, будете ставить cobbler в linux контейнере lxc под управлением libvirt, то нужно прописать в xml определения домена строки:
Создаем определение дистрибутивов в cobbler:
Здесь kernel фиктивный — PXE будет загружать файл созданный на основе этого pxeboot.n12 по команде
/var/lib/tftpboot/winos/add_ram.dat тоже фиктивный initrd, в смысле можно подсунуть любой— иначе distro не создать.
Создаем определение профилей в cobbler:
Создадим профили для двух тестовых вариантов инсталляции на основе дистрибутива Win8_RU-x64.
Теперь в win.ks можно вставлять конструкции типа:
Имена файлов в профилях реальные и должны соответствовать шаблонам, определенным в /etc/tftpd.rules для данного дистрибутива. Только вот самих файлов пока еще нет, их создаст триггер в момент выполнения команды
Cobbler по умолчанию создает меню в виде списка и чтобы сделать его более удобным нужно еще немного потрудиться.
Почему-то разработчики cobbler при генерации pxe меню из шаблона в качестве значения переменной http_server устанавливают фиксированное значение
Теперь из файла /etc/cobbler/pxe/pxedefault.template строку
можно убрать, а вместо нее нарисовать что-нибудь свое:
Теперь из шаблонов будем создавать загрузочные файлы, скрипты, образы wim для загрузки и поместим все это в нужные места.
Для этого я решил воспользоваться триггером cobbler на post sync:
Это обычный скрипт на python использующий Cobbler API. Для своей работы использует pefile для пересчета контрольной суммы в bootmgr.exe, bcdedit.pl для внесения изменений в BCD и библиотеку wimlib для монтирования образа и копирования созданного из шаблона файла startnet.cmd в папку Windows/System32.
Выполняем команды:
Теперь создаем виртуалку с инсталляцией по сети, либо на обычном компьютере жмем кнопки Reset, F12 (ну или что у вас в BIOS для этих целей прописано), выбираем нужный пункт в меню и наслаждаемся автоматической сетевой инсталляцией как Linux так и Windows.
При необходимости внести изменения в сценарий пользуемся условной генерацией кода в win.ks. У меня в шапке этого файла сначала идет общая часть нужная при любом варианте инсталляции, в потом длинный if/else:
Дублирующейся, либо логически замкнутый код вынесен в снипеты.
Хотелось бы при помощи него также гибко и универсально создавать свои сценарии сетевой инсталляции с разных дистрибутивов Windows (XP, 2003, 7, 8, 2008, 2012).
Про то как настроить и использовать cobbler для инсталляции Linux исчерпывающе написано на его официальном сайте — https://cobbler.github.io. Я же здесь сосредоточусь на своем варианте решения задачи относительно Windows.
Основной проблемой для создания своего сценария сетевой инсталляции была подготовка необходимых загрузочных файлов Windows в среде Linux. Генерация необходимых бинарных файлов у меня происходит при запуске post-триггера cobbler на команду
cobbler sync
:- часть файлов создается из стандартных прямой заменой одной строки на другую прямо в бинарнике.
- в процессе изменения файла bootmgr.exe изменится контрольная сумма PE файла и ее нужно пересчитать. Триггер это делает при помощи python-pefile.
- для создания реестров загрузочной информации BCD использовался hivex.
- для работы с wim образоми — wimlib
- файлы ответов Windows генерируются при помощи шаблонов cobbler с использованием всех его возможностей по условной генерации кода в зависимости от версии Windows, архитектуры (32 или 64 бита), профиля инсталляции и т. д.
- стартовые скрипты для образов wim (startnet.cmd) и скрипта, запускаемого по окончании инсталляции OS (post_install.cmd) также генерируются из шаблонов.
В моем случае файлы ответов генерируются в основном на основе версии Windows и довольно редко, дополнительно на основе имени профиля. Т.е. я выбрал для себя наиболее подходящие и в большинстве случаев используемые опции инсталляции, которые можно указать в файле ответов для каждой версии Windows.
Основная же работа по настройке варианта инсталляции, установке дополнительного ПО и т.д. лежит на скрипте, который я указываю как kickstart файл для профиля cobbler (win.ks) в случае инсталляции Windows и скачиваемого скриптом post_install.cmd уже в момент инсталляции. Cobbler в момент скачивания динамически генерирует код на основе шаблона win.ks. А в этом шаблоне большинство кода генерируется уже на основе имени профиля.
В результате, в большинстве случаев для изменения сценария инсталляции при отладке мне достаточно просто подредактировать текстовый файл win.ks на сервере и заново запустить инсталляцию.
Упрощая до логической взаимосвязи файлов, которые нужно изменить для каждого сценария, сетевую инсталляцию для Xp и 2003 можно схематично изобразить так:
pxeboot.n12 → setupldr.exe → winnt.sif → post_install.cmd profile_name
Для Windos 7 и новее:
pxeboot.n12 → bootmgr.exe → BCD → winpe.wim → startnet.cmd → autounattended.xml → post_install.cmd profile_name
Не самый, конечно короткий путь от пункта в PXE меню до запуска скрипта post_install.cmd с именем профиля в качестве параметра. Кроме того этот скрипт должен еще взять у cobbler сервера kickstart соответствующий этому профилю и запустить его.
Теперь обо всем по порядку
Считаем что cobbler 2.6.9 и все необходимое для его работы у вас уже установлено, настроено (например iptables, SElinux) и прекрасно справляется с инсталляцией Linux.
Устанавливаем все необходимое:
# dnf install python-pefile hivex ntfs-3g fuse
- wimlib я собирал из исходников
Использовать hivex напрямую не удобно — здесь (неплохой вариант инсталляции, но мне не понравился из-за того, что использует анализ логов tftp, зато бинарники корежить не нужно) я нашел готовый скрипт (bcdedit.pl), который делает необходимые изменения в стандартном BCD. По сути он является заменой Windows утилиты bcdedit. Написан он на Perl, я его извлек из архива и поместил в /usr/local/bin, предварительно видоизменив:
# diff -c bcdedit.pl.orig bcdedit.pl
*** bcdedit.pl.orig
--- bcdedit.pl
***************
*** 232,237 ****
--- 232,238 ----
&AddElement($BCDFILE,$guids{bootmgr},"25000004","hex:3:1e,00,00,00,00,00,00,00");
&AddElement($BCDFILE,$guids{bootmgr},"12000004","string:Windows Boot Manager");
&AddElement($BCDFILE,$guids{bootmgr},"24000001",&Guids2MultiSZ($newguid));
+ &AddElement($BCDFILE,$guids{bootmgr},"16000048","hex:3:01");
print "Creating New Object\n";
&CreateGuid($BCDFILE,$newguid,"0x10200003");
Добавленная строка аналогична выполнению команды:
bcdedit -set {bootmgr} nointegritychecks Yes
В каталоге tftp сервера создаем папку, где у нас будут лежать дистрибутивы Windows:
# mkdir /var/lib/tftpboot/winos
Теперь там создаем каталоги для каждого дистрибутива и копируем туда его содержимое.
У меня это выглядит так:
# ls -l /var/lib/tftpboot/winos
dr-xr-xr-x. 6 root root 4096 Nov 29 2014 Win2012-Server_EN-x64
dr-xr-xr-x. 6 root root 4096 Jun 1 2014 Win2012-Server_RU-x64
dr-xr-xr-x. 10 root root 4096 May 6 19:41 Win2K3-Server_EN-x64
dr-xr-xr-x. 7 root root 4096 Nov 13 2013 Win2k8-Server_EN-x64
dr-xr-xr-x. 4 root root 4096 Oct 28 2013 Win7_EN-x64
dr-xr-xr-x. 5 root root 4096 Sep 25 2014 Win7_RU-x64
dr-xr-xr-x. 6 root root 4096 Jun 25 10:29 Win8_RU-x64
dr-xr-xr-x. 7 root root 4096 Dec 8 2011 WinXp_EN-i386
dr-xr-xr-x. 8 root root 4096 Jul 31 17:12 WinXp_RU-i386
Если вы планируете инсталлировать такие раритеты как Xp или 2003
В каталогах с дистрибутивами WinXp_EN-i386, WinXp_RU-i386 и Win2K3-Server_EN-x64 выполняем следующие команды:
В каталогах дистрибутивов с Xp:
В каталогах дистрибутивов с 2003:
Нам понадобиться ris-linux. В моем дистрибутиве он ставиться так:
Чтобы RIS не мешал инсталляции Win 7, нужно в файле /usr/share/ris-linux/binlsrv.py закомментировать одну строчу:
Найти простой способ, без анализа логов tftpd, возможность указать RIS какой же BCD нужен, мне не удалось. Поэтому просто отключил.
Создаем папку /var/lib/tftpboot/winos/inf и копируем туда все .inf файлы 32-битных драйверов сетевых карт, которые вам нужны и запускаем команду:
Мне нужно чтобы RIS одновременно обслуживал 32-битную Xp и 64-битный Server 2003. Для этого создадим еще один экземпляр сервиса на другом порту:
Никто не удосужился переписать сервис ris-linux под systemd, впрочем никому это уже не надо.
Создаем папку /var/lib/tftpboot/winos/inf64 и копируем туда все .inf файлы 64-битных драйверов сетевых карт, которые вам нужны и запускаем команду:
Включаем и запускаем сервисы:
Дополнительно нужно поменять порт в загрузчике Win2K3-Server_EN-x64 на тот что мы указали 64-битному RIS.
Сделать это можно помощью утилиты modldr.py из пакета ris-linux:
Если работа утилиты оканчивается ошибкой, то в тексте скрипта modldr.py замените строку:
на строку:
Подготовка загрузочных файлов
В каталогах с дистрибутивами WinXp_EN-i386, WinXp_RU-i386 и Win2K3-Server_EN-x64 выполняем следующие команды:
[root@is WinXp_EN-i386]# cabextract i386/startrom.n1_
[root@is WinXp_EN-i386]# mv startrom.n12 pxeboot.n12
[root@is WinXp_EN-i386]# cabextract i386/setupldr.ex_
В каталогах дистрибутивов с Xp:
[root@is WinXp_EN-i386]# sed -i 's/ntdetect\.com/ntdetect.wxp/gi setupldr.exe
В каталогах дистрибутивов с 2003:
[root@is Win2K3-Server_EN-x64]# sed -i 's/ntdetect\.com/ntdetect.2k3/gi setupldr.exe
# cp /var/lib/tftpboot/winos/WinXp_EN-i386/i386/ntdetect.com /var/lib/tftpboot/winos/ntdetect.wxp
# cp /var/lib/tftpboot/winos/Win2K3-Server_EN-x64/i386/ntdetect.com /var/lib/tftpboot/winos/ntdetect.wxp
Настройка RIS
Нам понадобиться ris-linux. В моем дистрибутиве он ставиться так:
# dnf install ris-linux
Чтобы RIS не мешал инсталляции Win 7, нужно в файле /usr/share/ris-linux/binlsrv.py закомментировать одну строчу:
# cd /usr/share/ris-linux
# cp binlsrv.py binlsrv.py.orig
# sed -i "s/p = p + chr(252) + chr(len(/#&/gi" binlsrv.py
# diff binlsrv.py.orig binlsrv.py
571c571
< p = p + chr(252) + chr(len('boot\\bcd')) + 'boot\\bcd'
---
> #p = p + chr(252) + chr(len('boot\\bcd')) + 'boot\\bcd'
Найти простой способ, без анализа логов tftpd, возможность указать RIS какой же BCD нужен, мне не удалось. Поэтому просто отключил.
Создаем папку /var/lib/tftpboot/winos/inf и копируем туда все .inf файлы 32-битных драйверов сетевых карт, которые вам нужны и запускаем команду:
# /usr/share/ris-linux/infparser.py /var/lib/tftpboot/winos/inf
Мне нужно чтобы RIS одновременно обслуживал 32-битную Xp и 64-битный Server 2003. Для этого создадим еще один экземпляр сервиса на другом порту:
# cd /etc/sysconfig
# cp ris-linuxd ris-linuxd64
# sed -i 's/\/inf/&64/gi' ris-linuxd64
# sed -i 's/linuxd/&64/gi' ris-linuxd64
# sed -i 's/BINLSRV_OPTS=/&--port=4012/gi' ris-linuxd64
# diff ris-linuxd ris-linuxd64
2c2
< # ris-linuxd service.
---
> # ris-linuxd64 service.
6c6
< BINLSRV_INFPATH=/var/lib/tftpboot/winos/inf
---
> BINLSRV_INFPATH=/var/lib/tftpboot/winos/inf64
9c9
< BINLSRV_OPTS=
---
> BINLSRV_OPTS=--port=4012
12c12
< BINLSRV_LOGFILE=/var/log/ris-linuxd.log
---
> BINLSRV_LOGFILE=/var/log/ris-linuxd64.log
15c15
< BINLSRV_PIDFILE=/var/run/ris-linuxd.pid
---
> BINLSRV_PIDFILE=/var/run/ris-linuxd64.pid
# cd /usr/sbin
# ln -s ris-linuxd ris-linuxd64
Никто не удосужился переписать сервис ris-linux под systemd, впрочем никому это уже не надо.
# cd /etc/rc.d/init.d
# cp ris-linuxd ris-linuxd64
# sed -i 's/ris-linuxd/&64/gi' ris-linuxd64
Создаем папку /var/lib/tftpboot/winos/inf64 и копируем туда все .inf файлы 64-битных драйверов сетевых карт, которые вам нужны и запускаем команду:
/usr/share/ris-linux/infparser.py /var/lib/tftpboot/winos/inf64
Включаем и запускаем сервисы:
# systemctl enable ris-linuxd
# systemctl start ris-linuxd
# systemctl enable ris-linuxd64
# systemctl start ris-linuxd64
Дополнительно нужно поменять порт в загрузчике Win2K3-Server_EN-x64 на тот что мы указали 64-битному RIS.
Сделать это можно помощью утилиты modldr.py из пакета ris-linux:
# /usr/share/ris-linux/modldr.py -p 4012 /var/lib/tftpboot/winos/Win2K3-Server_EN-x64/setupldr.exe
Если работа утилиты оканчивается ошибкой, то в тексте скрипта modldr.py замените строку:
ppattern = re.compile(r'\x6a\x04\x68(..)\x00\x00\xff\x35', re.DOTALL)
на строку:
ppattern = re.compile(r'\x6a\x64\x68(..)\x00\x00\xff\x35', re.DOTALL)
Подготовка SAMBA
Samba нам понадобится для того чтобы сделать общедоступными две папки
# vi /etc/samba/smb.conf
# Дистрибутивы для инсталляции
[WINOS]
path = /var/lib/tftpboot/winos
guest ok = yes
browseable = yes
writeable = no
public = yes
blocking locks = no
oplocks = no
level2 oplocks = no
# Разный публично доступный софт
[public]
comment = Public Stuff
path = /var/www/html/Distr
public = yes
writable = no
printable = no
guest ok = yes
blocking locks = no
oplocks = no
level2 oplocks = no
Создаем пользователя с минимальными правами, которого будем использовать при инсталляции. У меня: install/install
Подготовка файлов для сетевой инсталляции Win 7, 8, 2008, 2012
Ситуация с Win 7, 8, 2008, 2012 значительно хуже чем в Xp. Здесь так просто в бинарниках не поковыряешься — нужно потом контрольную сумму в нем пересчитывать.
Файл ответов для автоматической инсталляции тут тоже есть и более современного вида — в xml формате. В нем много улучшений, например можно указать как нужно диски на разделы порезать и т. д. Но есть и большая ложка дегтя — имя файла ответов зашито в стартовый скрипт, которой собственно и начинает инсталляцию. В свою очередь сам скрипт зашит в wim образ.
Т.е. остались еще большие неудобства с исправлением бинарников, а кроме того для каждого сценария инсталляции на основе одного дистрибутива я теперь должен иметь отдельный wim образ, который отличается от другого такого же образа только именем файла ответов в стартовом скрипте.
В каталоги с дистрибутивами нужно положить следующие файлы:
- pxeboot.n12
- bootmgr.exe
- boot/BCD
- boot/boot.sdi
- boot/Fonts и набор шрифтов в нем
Создание шаблонов Cobbler
Создаем правила преобразования имен файлов для tftp:
Содержимое файла /etc/tftpd.rules
#vi /etc/tftpd.rules
rg \\ / # Convert backslashes to slashes
r (BOOTFONT\.BIN) /winos/\1
r (/Boot/Fonts/)(.*) /winos/Win8_RU-x64/boot/Fonts/\2
r (ntdetect\.wxp) /winos/\1
r (ntdetect\.2k3) /winos/\1
r (wine.\.sif) /WinXp_EN-i386/\1
r (xple.) /WinXp_EN-i386/\1
r (winr.\.sif) /WinXp_RU-i386/\1
r (xplr.) /WinXp_RU-i386/\1
r (wi2k.\.sif) /Win2K3-Server_EN-x64/\1
r (w2k3.) /Win2K3-Server_EN-x64/\1
r (/Win2K3-Server_EN-x64/)(.*) /winos\1\L\2
r (boot7r..exe) /winos/Win7_RU-x64/\1
r (/Boot/)(7R.) /winos/Win7_RU-x64/boot/\2
r (boot7e.\.exe) /winos/Win7_EN-x64/\1
r (/Boot/)(7E.) /winos/Win7_EN-x64/boot/\2
r (boot28.\.exe) /winos/Win2k8-Server_EN-x64/\1
r (/Boot/)(28.) /winos/Win2k8-Server_EN-x64/boot/\2
r (boot2e.\.exe) /winos/Win2012-Server_EN-x64/\1
r (/Boot/)(2e.) /winos/Win2012-Server_EN-x64/boot/\2
r (boot2r.\.exe) /winos/Win2012-Server_RU-x64/\1
r (/Boot/)(2r.) /winos/Win2012-Server_RU-x64/boot/\2
r (boot81.\.exe) /winos/Win8_RU-x64/\1
r (/Boot/)(B8.) /winos/Win8_RU-x64/boot/\2
r (/WinXp...-i386/)(.*) /winos\1\L\2
Указываем правила преобразования в шаблоне cobbler для tftp:
# vi /etc/cobbler/tftpd.template
service tftp
{
disable = no
socket_type = dgram
protocol = udp
wait = yes
user = $user
server = $binary
server_args = -m /etc/tftpd.rules --port-range 25000:25030 -v -v -v -s $args
per_source = 11
cps = 100 2
flags = IPv4
}
Добавляем в файле /var/lib/cobbler/distro_signatures.json в раздел windows информацию о версиях, чтобы через метаданные cobbler их можно было использовать в шаблонах.
# vi /var/lib/cobbler/distro_signatures.json
"windows": {
"2003": {
},
"2008": {
},
"2012": {
},
"XP": {
},
"7": {
},
"8": {
}
},
При настройке инсталляции Windows удалось сохранить основные преимущества инсталляции Linux через cobbler формированием файла ответов и пост-инсталляционного скрипта из шаблонов.
В состав шаблонов для Windows у меня входят следующие файлы:
- post_inst_cmd.template — шаблон скрипта запускаемого после инсталляции OS
- win.ks — играет роль раздела post в kiskstart linux для windows
- win_sif.template — шаблон файлов ответов
- startnet.template — шаблон стартового скрипта в wim образе
- winpe7.template — wim файл для Win 7 и Win 2008 Server
- winpe8.template — wim файл для Win 8 и Win 2012 Server
Шаблоны post_inst_cmd.template и win.ks
Создаем шаблон скрипта, запускаемого после установки windows (неважно какой версии).
Скрипту при запуске передается имя профиля (варианта инсталляции) cobbler в качестве параметра.
# cat /var/lib/tftpboot/winos/post_inst_cmd.template
%systemdrive%
CD %systemdrive%\TMP >nul 2>&1
$SNIPPET('my/win_wait_network_online')
wget.exe http://@@http_server@@/cblr/svc/op/ks/profile/%1
MOVE %1 install.cmd
todos.exe install.cmd
start /wait install.cmd
DEL /F /Q libeay32.dll >nul 2>&1
DEL /F /Q libiconv2.dll >nul 2>&1
DEL /F /Q libintl3.dll >nul 2>&1
DEL /F /Q libssl32.dll >nul 2>&1
DEL /F /Q wget.exe >nul 2>&1
DEL /F /Q %0 >nul 2>&1
Немного пояснений по шаблону:
В каждом каталоге с дистрибутивом есть папка $OEM$/$1/TMP в которой лежит wget.exe, todos.exe и dll'ки необходимые им для запуска. Точнее лежат в одной папке, а другие просто symlink на нее.
# ls -l '/var/lib/tftpboot/winos/Win2K3-Server_EN-x64/$OEM$/$1/TMP'
-rwxr-xr-x. 1 root root 1177600 Sep 4 2008 libeay32.dll
-rwxr-xr-x. 1 root root 1008128 Mar 15 2008 libiconv2.dll
-rwxr-xr-x. 1 root root 103424 May 7 2005 libintl3.dll
-rwxr-xr-x. 1 root root 232960 Sep 4 2008 libssl32.dll
-rwxr-xr-x. 1 root root 4880 Oct 26 1999 sleep.exe
-rwxr-xr-x. 1 root root 52736 Oct 27 2013 todos.exe
-rwxr-xr-x. 1 root root 449024 Dec 31 2008 wget.exe
# ls -l '/var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/TMP'
lrwxrwxrwx. 1 root root 45 Oct 28 2013 /var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/TMP -> ../../../../Win2K3-Server_EN-x64/$OEM$/$1/TMP
# ls -l '/var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/TMP'
lrwxrwxrwx. 1 root root 42 May 31 2014 /var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/TMP -> ../../../Win2K3-Server_EN-x64/$OEM$/$1/TMP
- При инсталляции OS эти файлы копируются в C:\TMP.
- Снипет win_wait_network_online дожидается готовности сети пингуя IP адрес сервера где установлен cobbler.
Содержимое файла снипета /var/lib/cobbler/snippets/my/win_wait_network_online:wno10 set n=0 :wno20 ping @@http_server@@ -n 3 set exit_code=%ERRORLEVEL% IF %exit_code% EQU 0 GOTO wno_exit set /a n=n+1 IF %n% lss 30 goto wno20 pause goto wno10 :wno_exit
При генерации скрипта имя переменной @@http_server@@ из метаданных cobbler заменяется на реальный IP адрес сервера на котором установлен cobbler.
- Далее wget.exe скачивает kickstart файл. В отличие от Linux kickstart этот содержит .cmd файл, но тоже формируемый на лету сервером cobbler из шаблона win.ks.
- Этот файл переименовывается, затем перекодируется в формат текстового файла windows и запускается как обычный cmd скрипт.
- После отработки скрипта в C:\TMP удаляются файлы использованные для его получения и сам скрипт тоже.
Файл, который мы скачивали wget'ом и который играет роль пост-инсталляционной части kickstart Linux выглядит в упрощенном виде так:
Содержимое файла /var/lib/cobbler/kickstarts/win.ks
# cat /var/lib/cobbler/kickstarts/win.ks
$SNIPPET('my/win_wait_network_online')
set n=0
:mount_y
net use y: \\@@http_server@@\Public /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO mount_z
set /a n=n+1
IF %n% lss 20 goto mount_y
PAUSE
goto mount_y
set n=0
:mount_z
net use z: \\@@http_server@@\winos /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO mount_exit
set /a n=n+1
IF %n% lss 20 goto mount_z
PAUSE
goto mount_z
:mount_exit
if exist %systemdrive%\TMP\stage.dat goto flag005
echo 0 > %systemdrive%\TMP\stage.dat
$SNIPPET('my/win_check_virt')
#if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' )
z:\Drivers\wsname.exe /N:$DNS /NOREBOOT
#else
REM pause
#end if
echo Windows Registry Editor Version 5.00 > %systemdrive%\TMP\install.reg
echo [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce] >> %systemdrive%\TMP\install.reg
echo "DD"="C:\\TMP\\install.cmd" >> %systemdrive%\TMP\install.reg
$SNIPPET('my/win_install_drivers')
#if $distro_name == 'Win2K3-Server_EN-x64'
start /wait z:\Win2K3-Server_EN-x64\cmpnents\r2\setup2.exe /q /a /sr
start /wait y:\Windows\Win2003\IE8-WindowsServer2003-x64-ENU.exe /passive /update-no /norestart
if %virt% equ NO REG IMPORT y:\Windows\Win2003\vm.reg
#end if
REG IMPORT %systemdrive%\TMP\install.reg
net use Y: /delete
net use Z: /delete
%systemdrive%\TMP\sleep.exe 10
exit
:flag005
for /f "tokens=*" %%i in (%systemdrive%\TMP\stage.dat) do set stage=%%i
echo 1 > %systemdrive%\TMP\stage.dat
REG IMPORT %systemdrive%\TMP\install.reg
if %stage% neq 0 goto flag010
net use Y: /delete
net use Z: /delete
shutdown -r -f -t 5
exit
:flag010
if %stage% gtr 1 goto flag020
echo 2 > %systemdrive%\TMP\stage.dat
$SNIPPET('my/winzip')
$SNIPPET('my/winrar')
$SNIPPET('my/win_install_chrome')
$SNIPPET('my/win_install_ffox')
$SNIPPET('my/win_install_adacr')
$SNIPPET('my/win_install_jdk7-x86')
$SNIPPET('my/win_install_jdk7-x86_64')
$SNIPPET('my/win_install_UltraVNC')
#if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' )
$SNIPPET('my/win_install_office_2007_ru')
#else if $distro_name in ( 'Win7_RU-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
$SNIPPET('my/win_install_office_2010_ru')
#else
$SNIPPET('my/win_install_office_2010_en')
#end if
Title Cleaning Temp files
DEL "%systemroot%\*.bmp" >nul 2>&1
DEL "%systemroot%\Web\Wallpaper\*.jpg" >nul 2>&1
DEL "%systemroot%\system32\dllcache\*.scr" >nul 2>&1
DEL "%systemroot%\system32\*.scr" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Windows Update.lnk" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Set Program Access and Defaults.lnk" >nul 2>&1
DEL "%AllUsersProfile%\Start Menu\Windows Catalog.lnk" >nul 2>&1
DEL "%systemdrive%\Microsoft Office*.txt" >nul 2>&1
net user aspnet /delete >nul 2>&1
REM %systemdrive%\TMP\sleep.exe 60
net use Y: /delete
net use Z: /delete
shutdown -r -f -t 30
RD /S /Q %systemdrive%\DRIVERS\ >nul 2>&1
if not defined stage DEL /F /Q %systemdrive%\post_install.cmd
DEL /F /S /Q %systemdrive%\TMP\*.*
exit
Снипеты здесь используются для того чтобы не загромождать код. В них проводится проверки на гипервизор, ставятся нужные драйвера, в зависимости от дистрибутива и профиля ставится или не ставится то или иное ПО.
Шаблон win_sif.template
Это единый шаблон из которого формируются файлы ответов всех версий windows. В нем, как и в других шаблонах можно использовать все мета-переменные cobbler.
Такие, например как $arch, $distro_name, $profile_name.
Содержимое файла /var/lib/tftpboot/winos/win_sif.template
#if $distro_name in ( 'WinXp_EN-i386', 'WinXp_RU-i386', 'Win2K3-Server_EN-x64' )
#if $arch == 'x86_64'
#set $win_arch = 'amd64'
#else if $arch == 'i386'
#set $win_arch = 'i386'
#end if
#set $OriSrc = '\\\\' + $http_server + '\\WINOS\\' + $distro_name + '\\' + $win_arch
#set $DevSrc = '\\Device\\LanmanRedirector\\' + $http_server + '\\WINOS\\' + $distro_name
[Data]
floppyless = "1"
msdosinitiated = "1"
; Needed for second stage
OriSrc="$OriSrc"
OriTyp="4"
LocalSourceOnCD=1
DisableAdminAccountOnDomainJoin=0
AutomaticUpdates="No"
Autopartition="0"
UnattendedInstall="Yes"
[SetupData]
OsLoadOptions = "/noguiboot /fastdetect"
; Needed for first stage
SetupSourceDevice = "$DevSrc"
[Unattended]
CrashDumpSetting=0
FactoryMode=No
UnattendMode=FullUnattended
UnattendSwitch="Yes"
OemPreinstall="Yes"
OemSkipEula="Yes"
Repartition=No
FileSystem=*
WaitForReboot="No"
NoWaitAfterTextMode=1
NoWaitAfterGUIMode=1
DriverSigningPolicy=Ignore
NonDriverSigningPolicy=Ignore
UpdateInstalledDrivers=Yes
TargetPath=\WINDOWS
OemPnPDriversPath=DRIVERS\NIC;DRIVERS\ACPI;DRIVERS\CHIPSET\5520\All;DRIVERS\CHIPSET\C200\All;DRIVERS\Storage;DRIVERS\Virt
#if $os_version == '2003'
[LicenseFilePrintData]
AutoMode = PerSeat
#end if
[Display]
BitsPerPel=32
XResolution=1440
YResolution=900
Vrefresh=60
[WindowsFirewall]
Profiles = WindowsFirewall.TurnOffFirewall
[WindowsFirewall.TurnOffFirewall]
Mode = 0
[PCHealth]
RA_AllowToGetHelp=0
[GuiRunOnce]
"%Systemdrive%\post_install.cmd @@profile_name@@"
[GuiUnattended]
AdminPassword=*
TimeZone=195
OEMSkipRegional=1
OemSkipWelcome=1
#if $os_version != '2003'
AutoLogon = Yes
AutoLogonCount=1
#end if
[RemoteInstall]
Repartition=Yes
UseWholeDisk=Yes
[Components]
msmsgs=Off
msnexplr=Off
zonegames=Off
Paint=Off
#if $os_version == '2003'
; Iis_common=On
; Iis_inetmgr=On
ComPlusNetwork=On
; Iis_www=On
; Iis_asp=On
IEHardenAdmin=Off
IEHardenUser=Off
#end if
[TerminalServices]
AllowConnections=1
[UserData]
#if $os_version == '2003'
ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
#else if $distro_name == 'WinXp_EN-i386'
ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
#else if $distro_name == 'WinXp_RU-i386'
ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
#end if
ComputerName=*
FullName="Admin"
OrgName="Microsoft"
[RegionalSettings]
LanguageGroup=1,2,3,4,5
#if $distro_name == 'WinXp_RU-i386'
SystemLocale=00000419
UserLocale=00000419
#else
SystemLocale=00000409
UserLocale=00000409
#end if
InputLocale=0409:00000409,0419:00000419
[Shell]
CustomDefaultThemeFile="%WinDir%\Resources\Themes\Windows Classic.Theme"
[Networking]
InstallDefaultComponents="Yes"
#else if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64', 'Win2k8-Server_EN-x64', 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<servicing>
<package action="configure">
<assemblyIdentity name="Microsoft-Windows-ServerCore-Package" version="6.3.9600.16384" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" />
<selection name="IIS-WebServerRole" state="true" />
<selection name="Microsoft-Hyper-V" state="true" />
<selection name="Microsoft-Hyper-V-Offline" state="true" />
<selection name="Microsoft-Hyper-V-Online" state="true" />
<selection name="Microsoft-Hyper-V-Management-Clients" state="true" />
<selection name="Microsoft-Hyper-V-Management-PowerShell" state="true" />
<selection name="VmHostAgent" state="true" />
<selection name="FailoverCluster-FullServer" state="true" />
<selection name="FailoverCluster-PowerShell" state="true" />
<selection name="FailoverCluster-CmdInterface" state="true" />
<selection name="MultipathIo" state="true" />
<selection name="ServerManager-Core-RSAT-Role-Tools" state="true" />
<selection name="RSAT-Hyper-V-Tools-Feature" state="true" />
<selection name="ServerManager-Core-RSAT" state="true" />
<selection name="ServerMediaFoundation" state="true" />
<selection name="Remote-Desktop-Services" state="true" />
<selection name="IIS-WebServer" state="true" />
<selection name="IIS-ApplicationDevelopment" state="true" />
<selection name="IIS-CommonHttpFeatures" state="true" />
<selection name="IIS-HealthAndDiagnostics" state="true" />
<selection name="IIS-Performance" state="true" />
<selection name="IIS-Security" state="true" />
<selection name="IIS-WebServerManagementTools" state="true" />
</package>
</servicing>
#end if
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
#if $distro_name in ( 'Win7_RU-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
<InputLocale>0409:00000409;0419:00000419</InputLocale>
<SystemLocale>ru-RU</SystemLocale>
<UILanguage>ru-RU</UILanguage>
<UILanguageFallback>ru-RU</UILanguageFallback>
<UserLocale>ru-RU</UserLocale>
#else
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>en-US</UserLocale>
#end if
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DiskConfiguration>
<WillShowUI>OnError</WillShowUI>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Extend>true</Extend>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallFrom>
<Credentials>
<Domain></Domain>
</Credentials>
<MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key>
#if $profile_name == 'IOS'
<Value>Windows8_VmVare_MacOS_xCode</Value>
#else if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )
<Value>Windows 7 PROFESSIONAL</Value>
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
<Value>Windows Server 2008 R2 SERVERENTERPRISE</Value>
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<Value>Windows Server 2012 R2 SERVERDATACENTER</Value>
#else if $distro_name in ( 'Win8_RU-x64' )
<Value>Windows 8.1 Pro</Value>
#else if $distro_name in ( 'Win8_EN-x64' )
<Value>Windows 8.1 Enterprise</Value>
#end if
</MetaData>
</InstallFrom>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>1</PartitionID>
</InstallTo>
</OSImage>
</ImageInstall>
<UserData>
<ProductKey>
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<Key>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</Key>
#end if
<WillShowUI>Never</WillShowUI>
</ProductKey>
<AcceptEula>true</AcceptEula>
<FullName>User</FullName>
<Organization>My Organization</Organization>
</UserData>
<EnableFirewall>false</EnableFirewall>
</component>
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DriverPaths>
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\Win8</Path>
</PathAndCredentials>
#else
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\5520\Vista</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>\\@@http_server@@\WINOS\Drivers\CHIPSET\C200\WIN7</Path>
</PathAndCredentials>
#end if
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
#if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64', 'Win8_RU-x64' )
<Path>\\@@http_server@@\WINOS\Drivers\NIC\Win8</Path>
#else
<Path>\\@@http_server@@\WINOS\Drivers\NIC</Path>
#end if
</PathAndCredentials>
#if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64', 'Win2k8-Server_EN-x64' )
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
<Path>\\@@http_server@@\WINOS\Drivers\ACPI\64\WIN7</Path>
</PathAndCredentials>
#end if
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
<Path>\\@@http_server@@\WINOS\Drivers\Storage\64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="6">
#if $distro_name in ( 'Win8_RU-x64' )
<Path>\\@@http_server@@\WINOS\Drivers\Virt\Win8</Path>
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<Path>\\@@http_server@@\WINOS\Drivers\Virt\2012</Path>
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
<Path>\\@@http_server@@\WINOS\Drivers\Virt\2008</Path>
#else
<Path>\\@@http_server@@\WINOS\Drivers\Virt\Win7</Path>
#end if
</PathAndCredentials>
</DriverPaths>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComputerName>*</ComputerName>
<RegisteredOrganization>My Organization</RegisteredOrganization>
<RegisteredOwner>Instructor</RegisteredOwner>
#if $distro_name == 'Win7_RU-x64'
<ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey>
#else if $distro_name == 'Win7_EN-x64'
<ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey>
#else if $distro_name == 'Win2k8-Server_EN-x64'
<ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey>
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey>
#end if
</component>
<component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Identification>
<JoinWorkgroup>WORKGROUP</JoinWorkgroup>
</Identification>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<FirewallGroups>
<FirewallGroup wcm:action="add" wcm:keyValue="EnableRemoteDesktop">
<Active>true</Active>
<Group>Remote Desktop</Group>
<Profile>all</Profile>
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserAuthentication>0</UserAuthentication>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OOBE>
<ProtectYourPC>3</ProtectYourPC>
<NetworkLocation>Work</NetworkLocation>
</OOBE>
<UserAccounts>
<AdministratorPassword>
<Value>XXXX</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>XXXXX</Value>
<PlainText>false</PlainText>
</Password>
<Name>User</Name>
<Group>Administrators</Group>
</LocalAccount>
</LocalAccounts>
<DomainAccounts>
<DomainAccountList wcm:action="add">
<Domain>WORKGOUP</Domain>
<DomainAccount wcm:action="add">
<Name>Domain Admins</Name>
<Group>Administrators</Group>
</DomainAccount>
<DomainAccount wcm:action="add">
<Name>User</Name>
<Group>Administrators</Group>
</DomainAccount>
</DomainAccountList>
</DomainAccounts>
</UserAccounts>
<TimeZone>Central Asia Standard Time</TimeZone>
<RegisteredOrganization>My Organization</RegisteredOrganization>
<RegisteredOwner>User</RegisteredOwner>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<RequiresUserInput>false</RequiresUserInput>
<Order>1</Order>
<CommandLine>cmd /C wmic useraccount where "name='user'" set PasswordExpires=FALSE</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<RequiresUserInput>false</RequiresUserInput>
<Order>2</Order>
<CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine>
</SynchronousCommand>
</FirstLogonCommands>
<AutoLogon>
<Password>
<Value>XXXX</Value>
<PlainText>false</PlainText>
</Password>
<Enabled>true</Enabled>
<Domain>WORKGOUP</Domain>
<Username>User</Username>
<LogonCount>10000</LogonCount>
</AutoLogon>
</component>
</settings>
#if $distro_name in ( 'Win7_RU-x64', 'Win7_EN-x64' )
<cpi:offlineImage cpi:source="catalog:d:/sources/install_windows 7 professional.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
#else if $distro_name in ( 'Win2k8-Server_EN-x64' )
<cpi:offlineImage cpi:source="catalog:d:/sources/install_windows server 2008 r2 serverenterprise.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
#else if $distro_name in ( 'Win2012-Server_EN-x64', 'Win2012-Server_RU-x64' )
<cpi:offlineImage cpi:source="wim:c:/netboot/install.wim#Windows Server 2012 R2 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
#else if $distro_name in ( 'Win8_RU-x64' )
<cpi:offlineImage cpi:source="catalog:d:/sources/install_windows 8.1 Pro.clg" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
#end if
</unattend>
#end if
Ключевой момент здесь в строках, при помощи которых стартует скрипт с именем профиля в качестве параметра:
- для Xp, 2003
[GuiRunOnce] "%Systemdrive%\post_install.cmd @@profile_name@@"
- для 7, 8, 2008, 2012
<SynchronousCommand wcm:action="add"> <RequiresUserInput>false</RequiresUserInput> <Order>2</Order> <CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine> </SynchronousCommand>
Шаблон startnet.template
Шаблон на основе которого формируются стартовые скрипты для wim образов.
- После инициализации сетевого стека скрипт находит IP адрес DHCP сервера (там же у нас находится и cobbler с samba).
- Монтируются необходимые сетевые ресурсы samba.
- Из метаданных cobbler извлекается имя файла ответов ($kernel_options[«sif»]) для данного профиля.
- Запросом к DNS серверу находим имя инсталлируемого компьютера в DNS и заносим его в файл ответов.
- Запускаем инсталляцию с этим файлом ответов.
Содержимое файла /var/lib/cobbler/kickstarts/startnet.template
wpeinit
ping 127.0.0.1 -n 10 >nul
md \tmp
cd \tmp
ipconfig /all | find "DHCP Server" > dhcp
ipconfig /all | find "IPv4 Address" > ipaddr
FOR /F "eol=- tokens=2 delims=:" %%i in (dhcp) do set dhcp=%%i
FOR %%i in (%dhcp%) do set dhcp=%%i
FOR /F "eol=- tokens=2 delims=:(" %%i in (ipaddr) do set ipaddr=%%i
net use y: \\%dhcp%\Public /user:install install
net use z: \\%dhcp%\WINOS\@@distro_name@@ /user:install install
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO GETNAME
echo "Can't mount network drive
goto EXIT
:GETNAME
y:\windows\bind\nslookup.exe %ipaddr% | find "name =" > wsname
for /f "eol=- tokens=2 delims==" %%i in (wsname) do echo %%i > ws
for /f "eol=- tokens=1 delims=." %%i in (ws) do set wsname=%%i
FOR %%i in (%wsname%) do set wsname=%%i
#set $unattended = "set UNATTENDED_ORIG=Z:\\sources\\" + $kernel_options["sif"]
$unattended
set UNATTENDED=X:\tmp\autounattended.xml
echo off
FOR /F "tokens=1 delims=!" %%l in (%UNATTENDED_ORIG%) do (
IF "%%l"==" <ComputerName>*</ComputerName>" (
echo ^<ComputerName^>%wsname%^<^/ComputerName^>>> %UNATTENDED%
) else (
echo %%l>> %UNATTENDED%
)
)
echo on
:INSTALL
set n=0
z:\sources\setup.exe /unattend:%UNATTENDED%
set /a n=n+1
ping 127.0.0.1 -n 5 >nul
IF %n% lss 20 goto INSTALL
:EXIT
Шаблоны winpe7.template и winpe8.template
Стандартные образы для сетевой инсталляции Win 7 и Win 8 с интегрированными драйверами.
По команде
cobbler sync
их копируют в место указанное в каждом профиле.Каждую такую копию монтируют, в нее копируют стартовый скрипт созданный на основе шаблона startnet.template и демонтируют в триггере на post-sync.
Для работы с wim образами в Linux я использую wimlib
Для ее работы нужно установить пакет ntfs-3g, который в свою очередь работает через fuse.
Поэтому если как я, будете ставить cobbler в linux контейнере lxc под управлением libvirt, то нужно прописать в xml определения домена строки:
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/fuse</char>
</source>
</hostdev>
Создаем определения дистрибутивов и профилей
Создаем определение дистрибутивов в cobbler:
# systemctl restart cobblerd
# cobbler distro add --name=WinXp_RU-i386 --kernel=/var/lib/tftpboot/winos/WinXp_RU-i386/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=i386 --breed=windows --os-version=XP --kopts='post_install=/var/lib/tftpboot/winos/WinXp_RU-i386/$OEM$/$1/post_install.cmd'
# cobbler distro add --name=Win7_RU-x64 --kernel=/var/lib/tftpboot/winos/Win7_RU-x64/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=x86_64 --breed=windows --os-version=7 --kopts='post_install=/var/lib/tftpboot/winos/Win7_RU-x64/sources/$OEM$/$1/post_install.cmd'
# cobbler distro add --name=Win8_RU-x64 --kernel=/var/lib/tftpboot/winos/Win8_RU-x64/pxeboot.n12 --initrd=/var/lib/tftpboot/winos/add_ram.dat --arch=x86_64 --breed=windows --os-version=8 --kopts='post_install=/var/lib/tftpboot/winos/Win8_RU-x64/sources/$OEM$/$1/post_install.cmd'
и т.д.
Здесь kernel фиктивный — PXE будет загружать файл созданный на основе этого pxeboot.n12 по команде
cobbler sync
./var/lib/tftpboot/winos/add_ram.dat тоже фиктивный initrd, в смысле можно подсунуть любой— иначе distro не создать.
Создаем определение профилей в cobbler:
# cobbler profile add --name=WinXp_RU-i386 --distro=WinXp_RU-i386 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=winr0.0,bootmgr=xplr0,sif=winr0.sif'
# cobbler profile add --name=Win7_RU-x64 --distro=Win7_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win7ra.0,bootmgr=boot7ra.exe,bcd=7Ra,winpe=winpe.wim,sif=autounattended.xml'
# cobbler profile add --name=Win8_RU-x64 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81a.0,bootmgr=boot81a.exe,bcd=B8a,winpe=winpe.wim,sif=autounattended.xml'
Создадим профили для двух тестовых вариантов инсталляции на основе дистрибутива Win8_RU-x64.
# cobbler profile add --name=Win8-test1 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81b.0,bootmgr=boot81b.exe,bcd=B8b,winpe=winpb.wim,sif=autounattended01.xml'
# cobbler profile add --name=Win8-test2 --distro=Win8_RU-x64 --kickstart=/var/lib/cobbler/kickstarts/win.ks --kopts='pxeboot=win81c.0,bootmgr=boot81c.exe,bcd=B8c,winpe=winpc.wim,sif=autounattended02.xml'
Теперь в win.ks можно вставлять конструкции типа:
#if $profile_name == ' Win8-test1'
<code>
#end if
#if $profile_name == ' Win8-test2'
<code>
#end if
Имена файлов в профилях реальные и должны соответствовать шаблонам, определенным в /etc/tftpd.rules для данного дистрибутива. Только вот самих файлов пока еще нет, их создаст триггер в момент выполнения команды
cobbler sync
.Шаблон меню PXE загрузки
Cobbler по умолчанию создает меню в виде списка и чтобы сделать его более удобным нужно еще немного потрудиться.
Почему-то разработчики cobbler при генерации pxe меню из шаблона в качестве значения переменной http_server устанавливают фиксированное значение
server.example.org
, а не берут его из конфигурационного файла. Windows пунктам меню это не мешает, а вот для Linux при помощи этого значения можно указать местоположение kickstart файла. Исправляем это:
# cd /usr/lib/python2.7/site-packages/cobbler
# cp templar.py templar.py.orig
# sed -i 's/"server.example.org"/self.settings.server/gi' templar.py
Теперь из файла /etc/cobbler/pxe/pxedefault.template строку
$pxe_menu_items
можно убрать, а вместо нее нарисовать что-нибудь свое:
menu begin Linux
MENU TITLE Linux
label Fedora-latest-x86_64
MENU INDENT 5
MENU LABEL Fedora-latest-x86_64
kernel /images/Fedora-22-x86_64/vmlinuz
append initrd=/images/Fedora-22-x86_64/initrd.img ks.device=bootif ks.sendmac lang=en text ks=http://@@http_server@@/cblr/svc/op/ks/profile/Fedora-latest-x86_64
ipappend 2
label returntomain
menu label Return to ^main menu.
menu exit
menu end
menu begin Windows
MENU TITLE Windows
label Win8-test1
MENU INDENT 5
MENU LABEL Win8-test1
kernel /winos/Win8_RU-x64/win81b.0
label Win8-test2
MENU INDENT 5
MENU LABEL Win8-test2
kernel /winos/Win8_RU-x64/win81c.0
label returntomain
menu label Return to ^main menu.
menu exit
menu end
Собираем все вместе
Теперь из шаблонов будем создавать загрузочные файлы, скрипты, образы wim для загрузки и поместим все это в нужные места.
Для этого я решил воспользоваться триггером cobbler на post sync:
Содержимое файла /usr/lib/python2.7/site-packages/cobbler/modules/sync_post_wingen.py
import distutils.sysconfig
import sys
import os
import traceback
import cexceptions
import os
import re
import xmlrpclib
import pefile
import cobbler.module_loader as module_loader
import cobbler.utils as utils
import cobbler.config as config
import cobbler.templar as templar
template_dir = "/var/lib/tftpboot/winos/"
sif_template_name = template_dir + "win_sif.template"
post_inst_cmd_template_name = template_dir + "post_inst_cmd.template"
startnet_template_name = template_dir + "startnet.template"
wim7_template_name = template_dir + "winpe7.template"
wim8_template_name = template_dir + "winpe8.template"
wimlib = "/usr/bin/wimlib-imagex"
wimlib_mount = wimlib + " mountrw"
wimlib_umount = wimlib + " unmount"
mount_point = "/mnt/wim"
bcdedit = "/usr/local/bin/bcdedit.pl"
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
def register():
# this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
# the return of this method indicates the trigger type
return "/var/lib/cobbler/triggers/sync/post/*"
def run( api, args, logger ):
settings = api.settings()
images = api.images()
distros = api.distros()
profiles = api.profiles()
conf = config.Config( api )
templ = templar.Templar( conf )
rc = 0
template_win = open( post_inst_cmd_template_name )
tmpl_data = template_win.read()
template_win.close()
for distro in distros:
if distro.breed == "windows":
meta = utils.blender( api, False, distro )
if distro.kernel_options.has_key( "post_install" ):
data = templ.render( tmpl_data, meta, None, distro )
pi_file = open( distro.kernel_options["post_install"], "w+" )
pi_file.write( data )
pi_file.close()
template_win = open( sif_template_name )
tmpl_data = template_win.read()
template_win.close()
template_start = open( startnet_template_name )
tmplstart_data = template_start.read()
template_start.close()
logger.info( "\nWindows profiles:" )
for profile in profiles:
distro = profile.get_conceptual_parent()
if distro.breed == "windows":
logger.info( 'Profile: ' + profile.name )
meta = utils.blender( api, False, profile )
(distro_path, pxeboot_name) = os.path.split( distro.kernel )
if profile.kernel_options.has_key( "sif" ):
data = templ.render( tmpl_data, meta, None, profile )
if distro.os_version in ( "7", "2008", "8", "2012" ):
sif_file_name = os.path.join( distro_path, 'sources', profile.kernel_options["sif"] )
else:
sif_file_name = os.path.join( distro_path, profile.kernel_options["sif"] )
sif_file = open(sif_file_name, "w+" )
sif_file.write( data )
sif_file.close()
logger.info( 'Build answer file: ' + sif_file_name )
if profile.kernel_options.has_key( "pxeboot" ) and profile.kernel_options.has_key( "bootmgr" ):
wk_file_name = os.path.join( distro_path, profile.kernel_options["pxeboot"] )
wl_file_name = os.path.join( distro_path, profile.kernel_options["bootmgr"] )
logger.info( "Build PXEBoot: " + wk_file_name )
if distro.os_version in ( "7", "2008", "8", "2012" ):
if len(profile.kernel_options["bootmgr"]) != 11:
logger.error( "The loader name should be EXACTLY 11 character" )
return 1
if profile.kernel_options.has_key( "bcd" ):
if len(profile.kernel_options["bcd"]) != 3:
logger.error( "The BCD name should be EXACTLY 5 character" )
return 1
tl_file_name = os.path.join( distro_path, 'bootmgr.exe' )
pat1 = re.compile( r'bootmgr\.exe', re.IGNORECASE )
pat2 = re.compile( r'(\\.B.o.o.t.\\.)(B)(.)(C)(.)(D)', re.IGNORECASE )
bcd_name = 'BCD'
if profile.kernel_options.has_key( "bcd" ):
bcd_name = profile.kernel_options["bcd"]
bcd_name = "\\g<1>" + bcd_name[0] + "\\g<3>" + bcd_name[1] + "\\g<5>" + bcd_name[2]
data = open( tl_file_name, 'rb').read()
out = pat2.sub( bcd_name, data )
else:
if len(profile.kernel_options["bootmgr"]) != 5:
logger.error( "The loader name should be EXACTLY 5 character" )
return 1
if len(profile.kernel_options["sif"]) != 9:
logger.error( "The response should be EXACTLY 9 character" )
return 1
tl_file_name = os.path.join( distro_path, 'setupldr.exe' )
pat1 = re.compile( r'NTLDR', re.IGNORECASE )
pat2 = re.compile( r'winnt\.sif', re.IGNORECASE)
data = open( tl_file_name, 'rb').read()
out = pat2.sub( profile.kernel_options["sif"], data )
logger.info( 'Build Loader: ' + wl_file_name )
if out != data:
open(wl_file_name, 'wb+').write(out)
if distro.os_version in ( "7", "2008", "8", "2012" ):
pe = pefile.PE( wl_file_name, fast_load=True )
pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()
pe.write( filename=wl_file_name )
data = open(distro.kernel, 'rb').read()
out = pat1.sub( profile.kernel_options["bootmgr"], data )
if out != data:
open(wk_file_name, 'wb+').write(out)
if profile.kernel_options.has_key( "bcd" ):
obcd_file_name = os.path.join( distro_path, 'boot', 'BCD' )
bcd_file_name = os.path.join( distro_path, 'boot', profile.kernel_options["bcd"] )
wim_file_name = 'winpe.wim'
if profile.kernel_options.has_key( "winpe" ):
wim_file_name = profile.kernel_options["winpe"]
wim_file_name = os.path.join( '/winos', distro.name, 'boot', wim_file_name )
sdi_file_name = os.path.join( '/winos', distro.name, 'boot', 'boot.sdi' )
logger.info( 'Build BCD: ' + bcd_file_name + ' for ' + wim_file_name )
cmd = "/usr/bin/cp " + obcd_file_name + " " + bcd_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
cmd = bcdedit + " " + bcd_file_name + " " + wim_file_name + " " + sdi_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
ps_file_name = os.path.join( distro_path, "boot", profile.kernel_options["winpe"] )
if distro.os_version in ( "7", "2008" ):
wim_pl_name = wim7_template_name
elif distro.os_version in ( "8", "2012" ):
wim_pl_name = wim8_template_name
cmd = "/usr/bin/cp " + wim_pl_name + " " + ps_file_name
rc = utils.subprocess_call( logger, cmd, shell=True )
if os.path.exists( wimlib ):
cmd = wimlib_mount + " " + ps_file_name + " " + mount_point
rc = utils.subprocess_call( logger, cmd, shell=True )
data = templ.render( tmplstart_data, meta, None, profile )
pi_file = open( mount_point + "/Windows/System32/startnet.cmd", "w+" )
pi_file.write( data )
pi_file.close()
cmd = wimlib_umount + " " + mount_point + " --commit --rebuild"
rc = utils.subprocess_get( logger, cmd, shell=True )
return 0
Это обычный скрипт на python использующий Cobbler API. Для своей работы использует pefile для пересчета контрольной суммы в bootmgr.exe, bcdedit.pl для внесения изменений в BCD и библиотеку wimlib для монтирования образа и копирования созданного из шаблона файла startnet.cmd в папку Windows/System32.
Выполняем команды:
# mkdir /mnt/wim
# systemctl restart cobblerd
# cobbler sync
# systemctl restart xinetd
Теперь создаем виртуалку с инсталляцией по сети, либо на обычном компьютере жмем кнопки Reset, F12 (ну или что у вас в BIOS для этих целей прописано), выбираем нужный пункт в меню и наслаждаемся автоматической сетевой инсталляцией как Linux так и Windows.
При необходимости внести изменения в сценарий пользуемся условной генерацией кода в win.ks. У меня в шапке этого файла сначала идет общая часть нужная при любом варианте инсталляции, в потом длинный if/else:
#if $profile_name == '<profile_name1>'
<code>
#elseif $profile_name == '<profile_name2>'
<code>
. . .
#elseif $profile_name == '<profile_nameN>'
<code>
#end if
Дублирующейся, либо логически замкнутый код вынесен в снипеты.
Чего не хватает
- Не будет работать импорт Windows дистрибутива. Новые версии Microsoft выпускает не так уж и часто, так что это можно пережить.
- Не будет работать (даже не пытался проверять) создание ISO образа Windows дистрибутива.
- Автоматическая генерация PXE меню в cobbler в случае Windows профилей будет работать неправильно.
- Если не соблюдать правила формирования имен загрузочных файлов, придется изменять /etc/tftpd.rules. А при совпадении имен загрузочных файлов в рамках одного дистрибутива, файлы одного профиля могут перезаписаться файлами другого профиля.