Секреты командной Строки Windows часть 2.

В продолжении темы Cmd – хаков, опишу еще несколько эффективных приемов работы с командной строкой.

>> Что такое enabledelayedexpansion?

Вообще я не хотел писать об этом, но т.к. в следующем примере это используется и в справке расписано туманно, напишу.

Обычно при использовании переменных в bat/cmd сценариях используется конструкция %переменная%, однако если значение переменной меняется в теле цикла и считывается там же, это может не работать. Например:

for %%i in (1,2,3) do (
    set "var=%%i"
    echo %var%
Rem Не выведет ничего
)

pause

Если запустить этот пример то интерпретатор просто 3 раза выполнит echo без каких то либо параметров. Для того чтобы все работало верно нужно использовать расширенный режим обработки команд. Задается он командой setlocal  с параметром enabledelayedexpansion, а выключается командой endlocal. Приведу пример:

setlocal enabledelayedexpansion

for %%i in (1,2,3) do (
    set "var=%%i"
    echo !var!
Rem Выведет 1,2,3
)

Pause

Теперь все выводится как и задумано. Замечу что переменная в данном случае обрамлена восклицательными знаками (!).

>> Создание динамических переменных в bat-файлах? Легко!

При написании сценариев командной строки, у администраторов, которые, хотя бы немного программировали на других языках программирования (а cmd можно отнести к ним с натяжкой) возникает определенный дискомфорт при обработке  больших массивов данных. Ведь в cmd отсутствуют массивы! Мы можем обойти это ограничение с использованием динамических переменных. Для этого воспользуемся расширенным режимом обработки команд.

Например есть текстовый файл, нам нужно его прочитать и вывести определенные строки на экран, покажу как это сделать при помощи динамических переменных.

Для начала создадим файл greeka.txt  с таким содержимым:

1.Ехал Грека через реку,Видит Грека - в реке рак.

2.Ехал Грека через реку,Видит Грека - в реке рак.

3.Ехал Грека через реку,Видит Грека - в реке рак.

4.Ехал Грека через реку,Видит Грека - в реке рак.

5.Ехал Грека через реку,Видит Грека - в реке рак.

6.Ехал Грека через реку,Видит Грека - в реке рак.

7.Ехал Грека через реку,Видит Грека - в реке рак.

8.Ехал Грека через реку,Видит Грека - в реке рак.

9.Ехал Грека через реку,Видит Грека - в реке рак.

10.Ехал Грека через реку,Видит Грека - в реке рак.

 

Далее, приступим к написанию нашего сценария:

    @echo off

    setlocal enabledelayedexpansion

Включаем режим расширенной обработки команд.

    set i=0

Объявляем вспомогательную переменную.

    for /F "tokens=*" %%a in (greka.txt) do (

Начало цикла  построчного перебора файла.

      set /A i=!i! + 1

Увеличиваем вспомогательный итератор на единицу. 

      set str!i!=%%a

Собственно сама незамысловатая конструкция создания динамических переменных, с именами str1, str2, str3 и так далее.

    )

Проверим как отработал наш сценарий.

Начало цикла 

Echo Это 10 строка... -------^>^> %str10%
Echo +++++++++++++++++++++++++++++++++++++++++++++
Echo +++++++++++++++++++++++++++++++++++++++++++++
Echo ...а  это вторая -------^>^> %str2%
pause

Как видим динамические переменные использовать довольно удобно и легко, они могут помочь вам заменить массивы и добавят гибкости коду.

>> Поиск и замена текста в файлах средствами cmd/bat файла.

Многие администраторы и Power-пользователи линукс, при знакомстве с возможностями cmd, замечают такую досадную деталь, как отсутствие множества утилит для поиска и замены подстрок в  файлах.

Вообще под windows существует множество утилит для обработки строк в файлах, в том числе и портированных с #nix систем (тот же sed и supersed), но мы не ищем легких путей это будет не совсем чисто, т.к. в нужный момент этого всего может не оказаться под рукой.

Возможна ли реализация данной функции на cmd ? Конечно! А поможет нам в этом специальные строки расширения, а точнее конструкция  вида %переменная:A=B%, где переменная- переменная содержащая исходную подстроку, A – подстрока которую ищем, B – подстрока на которую меняем искомую подстроку в строке.

Приступим писать наш код, т.к. он очень похож на тот что выше, особо вдаваться в детали я не буду:

@echo off

setlocal enabledelayedexpansion

rem Собираем параметры командой строки переданной сценарию.
rem Например сценарий запущен "File_str_replace.bat greka.txt Грека Гитлер"
rem %1 = greka.txt %2=Грека %3=Гитлер
Set infile=%1
Set find=%2
Set replace=%3

    @echo off

    setlocal enabledelayedexpansion

    set COUNT=0

    for /F "tokens=* delims=," %%n in (!infile!) do (
    set LINE=%%n

    set TMPR=!LINE:%find%=%replace%!

REM Именно здесь происходит замена текущей строки!

    Echo !TMPR!>>TMP.TXT

REM Добавляем строку к временному файлу
                    )

REM Делаем резервное копирование исходного файла, на случай что что то пойдет не так…

copy %infile% %infile%.bak

REM Заменяем исходный файл временным

move TMP.TXT %infile%

Наш батник для поиска и замены подстрок файла готов. Теперь сгенерируем файл для него, да побольше! Сделаем это тоже батником:

@echo off
for /L %%i in (1,1,100000) do (

echo %%i.Ехал Грека через реку,Видит Грека - в реке рак.Сунул Грека в реку руку,Рак за руку Греку - цап!>>greka.txt
)

Получился файл, забитый поговоркой, размером ~ 10 мегабайт, неплохо для примера…  Запустим Наш сценарий введя в командной строке “File_str_replace.bat greka.txt Грека Гитлер” , для того чтобы заменить все слова Грека на Гитлер. Запустив увидим что через полторы минуты(неплохо для 20-ти летнего консольного мамонта) в нашем файле поговорка заменилась на “Ехал Гитлер через реку,Видит Гитлер - в реке рак.Сунул Гитлер в реку руку,Рак за руку Греку - цап”. Можно усовершенствовать сценарий, и даже прикрутить к ней регулярные выражения, но это уже мелкие технические детали :)

На сегодня пожалуй все, попозже продолжу.

CmdArcanoid "Графическая" игра, средствами командной строки Windows.

Больше года назад, в порядке эксперимента, я написал игру используя исключительно команды cmd.exe. Игрой это назвать сложно, ибо отсутствует интерактивная составляющая, но тем не менее “компьютер” сам с собой играет. Скачать и посмотреть на это можно здесь.

Собственно как это статично выглядит.

Секреты командной строки Windows, часть 1.

К сожалению, благодаря ряду независящих от нас причин, командная оболочка cmd.exe до сих пор не утратила своей актуальности. Многие системные администраторы используют ее для управления своими задачами, при отсутствии на OS других средств, либо в силу привычки. Несмотря свою на простоту, командная строка обладает рядом интересных и неочевидных возможностей. В этом посте я постараюсь рассказать, о некоторых недокументированных либо малоизвестных возможностях командной строки Windows.

>> Beep из bat-файла.

В консоли Windows отсутствует команда заставляющая пищать системный динамик, но существует "недокументированный" символ (•) при выводе которого на экран консоли системный динамик издает звук. Чтобы ввести его нужно в текстовом редакторе набрать "Alt+7". Например: @echo •.

Проблема заключается в том что не все редакторы сохраняют такие символы, а по закону подлости ты оказываешься за компьютером, который, как раз без такового. Но и это можно обойти: запустим cmd.exe и в командной строке наберем @Echo,  нажав комбинацию Ctrl+G или "Alt+7" получим ^G далее перенаправим это все в файл, должно получиться @Echo echo ^G >beep.bat. Файл-пищалка готов.

>> Внутренний вызов команды CALL.

Из справочной документации Microsoft мы знаем, что команда Call - совершает вызов одного пакетного файла из другого, без завершения выполнения первого файла, но мало кому известно что при помощи Call можно перемещаться по внутренним меткам того же cmd/bat файла, подобно команде Goto. @Зачем это нужно, ведь есть goto?" - спросите вы. А затем, что помимо просто перемещения по меткам команда Сall имеет две особенности :

  1. "Умеет" возвращаться к месту своего вызова с помощью специальной метки :EOF.
  2. Умеет передававать значения в "подпрограмму". Поясню на примере:

@echo off

Rem Вызываем подпрогамму

call :SQRT 2

call :SQRT 8

pause

goto :EOF

rem а вот тут уже выходим см. ниже.

rem ***Начало подпрограммы.***

:SQRT

set /a outputval= %1*%1

echo Корень из %1 равен %outputval%.

goto :EOF

rem не выходим, но всего лишь возвращаемся на место за вызовом

rem ***Конец подпрограммы.***

После выполнения данного командного сценария получим такой вывод:

Красиво, не правда ли? Такая незаметная возможность, может сэкономить кучу строк кода, и на время может показаться, что мы программируем на полноценном скриптовом языке.

Да, стоит учесть еще и то что в подпрограмму можно передавать несколько аргументов, тогда обращаться к ним следует так же как и к аргументам командной строки %1, %2, %3 … и далее по порядку.

Символы экранирования в bat-файле.

Что если вам надо вывести  в консоль текст содержащий операторы или спецсиволы?

Например надо вывести: “%TEMP%”. Попробуем ввести в командную строку Echo %TEMP%.

Результат:

 

На консоль вывелось значение переменной %TEMP%. Совсем не то что нам надо…  Для таких случаев и существуют символы экранирования “^” и “%”. Достаточно его подставить перед спецсимволом и интерпретатор поймет что мы хотим вывести именно символ а не строку или выражение.Для проверки создадим батник:

@Echo off
Echo съешь еще этих французских булок да выпей чаю ^
съешь еще этих французских булок да выпей чаю ^
съешь еще этих французских булок да выпей чаю ^
съешь еще этих французских булок да выпей чаю

Echo %%TEMP%%

Pause

Результат:

Как видим все работает и экономия кода на лицо.

Не смотря на то что в примере все отработало, с  экранированием в батниках надо быть более осторожным, не всегда все гладко, поэтому следует тестировать каждый скрипт.

На сегодня все, продолжение в другом посте.

WEB-сервер на Jscript, своими руками/ WSH: работа с сокетами.

В Интернете очень мало документации для работы с сокетами из WSH, а ведь это, под час очень удобное средство для обмена данными между скриптами, запущенными на разных компьютерах сети (либо через интернет), а так же через сокеты можно организовать многопоточность VBS/JS скриптов.

Работа с сокетами будет показана на создании своего собственного нативного HTTP сервера, который будет читать запрос от клиента (в данном случае обычного браузера) обрабатывать его, и на основе анализа выдавать клиенту (браузеру) HTML содержимое запрашиваемой страницы.

Что такое сокет? Сокет это прикладной интерфейс Windows разработанный для обмена данными между хостами по технологии клиент-сервер. Сокет - виртуальная коммуникационная точка которая единовременно, может работать либо на прием либо на передачу данных (иногда, ошибочно, сокетами называют айпи_узла:номер_порта, например 127.0.0.1:80, но это может быть применимо только для протокола IP). В процессе обмена как правило используются два сокета - сокет отправителя и сокет получателя. Хосты могут находиться либо в разных участках сети либо на одном компьютере.

Сразу оговорюсь, из на прямую из Jsript работать с сокетами нельзя, поэтому тут возможны 3 пути:

1.Регестрировать сторонний ActiveX контрол(ActiveX компонент) для работы с API Windows и работать с сокетами через API, что приемлемо но не очень просто и чисто.

2. Использовать MSWINSCK.OCX ActiveX контрол от Майкрософт (поставляется с VB и MS Office). Здесь вроде все нормально, но с одной оговоркой- Js, в силу своей ограниченности, не умеет работать с функцией GetData(), хотя если вы пишете сервер на VBS то все нормально, да и сам контрол требует лицензии.

3.Использовать сторонний контрол, методы которого нормально возвращают параметры в Jscript. Я для этой цели использовал OstroSoft Winsock component (oswinsck.dll) http://www.ostrosoft.com/oswinsck/oswinsck_javascript.asp, по ссылке описание компонента и инструкции по установке.

После установки компонента приступаем к написанию кода.

var bClose=true; 

Переменная-флаг, с помощью которой будем следить за состоянием соединения.

function obInit()
{
oWinsock = new ActiveXObject("OSWINSCK.Winsock");
WScript.ConnectObject(oWinsock, "oWinsock_");
oWinsock.LocalPort=8080;//порт
oWinsock.Protocol=0;// протокол
oWinsock.Listen();// начинаем слушать порт
}

Создаем экземпляр объекта OSWINSCK.Winsock, присоединяемся к нему с целью предоставления доступа к своим событиям - _OnConnectionRequest, _OnDataArrival _Error, Winsock_Close. Далее устанавливаем протокол, порт и начинаем слушать порт (oWinsock.Listen) .

Далее- пишем функции реагирующие на события.

Функция создания соединения:

function oWinsock_OnConnectionRequest(reqId)

{
WScript.Echo("accepting request");
oWinsock.CloseWinsock();

// на всякий случай закрываем все подлючения

WScript.Echo(reqId);
oWinsock.Accept(reqId);

// начинаем соединение

}

После создания соединения управление передается функции для обмена данными.

function oWinsock_OnDataArrival(bytTotal)

{
WScript.Echo("Начинаем прием от клиента...");


var size=0, match1 ;

data= new String("");// буфер запроса получаемого от клиента

fcontent= new String("");// строки считанные с запрашиваемого файла

result= new String(""); // то что будем в результате отдавать клиенту (браузеру)

Объявляем переменные, создаем строковые объекты, давая интерпретатору понять что это именно строки, а не Variant как по умолчанию.

WScript.Echo(bytTotal);

data=oWinsock.GetDataBuffer();// получаем запрос клиента

Обращаю внимание на то, что переменная дата !обязательно! должна быть строкой, иначе могут возникнуть проблемы.

WScript.Echo(data);//выводим запрос клиента в консоль, для наглядности.

(!) Необходимо использовать именно csript.exe.

WScript.Echo("Прием закончен...");
//обрабатываем запрос

Запрос от клиента будет иметь похожий вид:

GET / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-silverlight, application/msword, application/vnd.ms-powerpoint, application/vnd.ms-excel, */*
Referer: http://10.97.24.200:8080/page2.html
Accept-Language: ru
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 1.1.4322; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.1)
Host: 10.97.24.200:8080


Connection: Keep-Alive


Пробуем его обработать регулярными выражениями, с целью – узнать запрашиваемую страницу на сервере.

(!) Здесь показан только пример, для полноценной работы необходим более детальный анализ заголовка, что выходит за рамки этой статьи.

match1=data.match(/GET[\s]\/(.*?)[\s]*?HTTP\/?/i);

WScript.Echo("REGEXP:"+match1[1]);

В match1 получаем имя файла вида page.html.


//обрабатываем запрос (конец)

if(match1[1]=="")

{
fcontent=getfile("index.html");
}

else

{
fcontent=getfile(match1[1]);
}

Вызываем ф-ю чтения таргет-файла (которую опишем ниже), если результат пустой – переправляем на главную страницу сайта (Index.html).

size=fcontent.lenght; 

Здесь получаем размер возращаемой страницы, с целью вставки ее в заголовок ответа.

result="HTTP/1.0 200 OK\r\nServer: JscriptServer\r\nContent-Language: ru\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length:"+size+"\r\n\r\n";

result+=fcontent;

Формируем заголовок ответа. Опять же – Это Пример, на самом деле в зависимости от медиатипа файла нужно отдавать разные заголовки (Content-Type:{здесь вбивать медиатип согласно RFC} ). Будем считать что на нашем сервере хранится только текстовая информация, дописать для остальных медиатипов не сложно.

oWinsock.SendData(result );

Отсылаем данные клиенту.

oWinsock.CloseWinsock();

Закрываем коннект.

oWinsock = null;

Убиваем объект.

obInit();

Создаем объект заново, для выполнения последующего запроса.

}

Далее опишем ф-ю чтения запрашиваемого файла.

function getfile(file)
{
FSO= new ActiveXObject("Scripting.FileSystemObject");
try
{
fl=FSO.OpenTextFile(file,1);

resfile =fl.ReadAll();

fl=null;
}

catch(e)
{
if(e!=0)
{
fl=FSO.OpenTextFile("404.html",1);
resfile =fl.ReadAll();
}
}
return (resfile);
}

Замечу что просто чтение файла 404 страницы тут НЕ корректно, ибо надо менять в самом заголовке result="HTTP/1.0 200…

Остается только контроль ошибок и проверка соединения.

function oWinsock_Error(number, desc, sCode, src, help, helpctx, cancelDisplay)

{
WScript.Echo("Error: "+desc);
bClose=false;
}

function oWinsock_Close()

{
WScript.Echo("Connection closed.");
bClose=false;
}

obInit();
while(bClose)WScript.Sleep(1);

Чем меньше значение WScript.Sleep(1); тем чаще сервер будет обрабатывать запросы. В остальном вроде все понятно.

Осталось создать страницы в корне нашего сервера и запустить скрипт.

http server

В строке браузера набираем Http://127.0.0.1:8080 видим следующую картину:

Естественно, данный пример не выдержит, большое число запросов, и не обладает многопоточностью, но вполне работоспособен. Скрипт можно доработать добавив Mime типы, кеширование, многопоточность, поддержку динамических страниц и т д..

Готовый скрипт можно скачать здесь.

Миграция на Windows 2003 Server с Novell Netware 6.5: Автоматизированный перенос пользователей.

Novell Netware одна из наиболее стабильных и надежных сетевых операционных систем, но имеет один неприятный недостаток - она разработана не в Microsoft, что влечет за собой стремительное сокращение оной на рынке серверных ОС. На сегодняшний день Novell не планирует развитие данной ОС, а стремительно (в 2008 году продано более 400 000 лицензий) выводит на рынок свои Linux дистрибутивы SLES и OES, что заставляет по крайней мере задуматься над миграцией на более перспективные современные ОС.

В этой статье я расскажу о переносе учетных записей пользователей с Novell Netware 6.5, на Windows 2003 Server путем импорта данных при помощи ODBC Driver for eDirectory и экспорта их при помощи пакетного файла Windows.


Пример не практический, но на основе его можно разработать перенос практически любой информации из базы Novel eDirectory в службу каталогов Active Directory.


Нам понадобиться:

  1. Сервер с установленным Windows 2003 Server с развернутым контроллером домена.
  2. Сервер с установленным Novell Netware 6.5 с заведенными пользователями и группами.
  3. Рабочая станция (OS Windows) с установленным клиентом Novell Netware, PHP5 и ODBC Driver for eDirectory.

Этап #1: Создание DSN Novell ODBC for NDS

ODBC Driver for eDirectory универсальный программный интерфейс, для доступа к базе Novell eDirectory, позволяющий обращаться к ней различным программным средствам (например MS Access, MS Excel, Vba и многим другим) с использованием SQL синтаксиса, незная внутреннего устройства eDirectory. ODBC позволяет значительно снизить время разработки ПО, но в то же время не расчитан на большое число подключений клиентов.

Для того чтобы установить ODBC Driver надо либо скачать его с сайта Novel, либо если у вас установлена ConsoleOne в папке *\consoleone\1.2\reporting\bin\odbc.exe. После установки необходимо настроить источник данных, для чего запустить Администратор источников данных ODBC. - odbcad32.exe. Далее перейти на вкладку Системный DSN и нажать кнопку Добавить, в следующем списке выбрать Novell ODBC Driver for NDS.



NDS - так называлось то что впоследствии переросло в eDirectory.


Далее ввести имя источников данных и обозначить дерево, имя не важно какое, но мы введем Novell.

Жмем два раза ок.

Этап #2: Извлекаем данные Novell eDirectory.

Для подключения к источникам данных ODBC будем использовать язык программирования PHP5. Итак сам код.

Объявляем переменные:

Имя домена первого и второго уровня
//Имя домена
$dc1="local";
$dc2="dc";
$context='.imns';
На следующих двух ф-ях заострять внимание пока не следует.

Вспомогательная функция для вывода на консоль в ДОС кодировке:
function echo_con($str)// вывод в консоль в дос кодировке
{
$str=convert_cyr_string($str,'w','a');
echo($str."\n");
return($str);
}
Функция для записи аварийных ситуаций
function error_con($err_dump)  // ф-я записи аварийных ситуаций
{
//Запись ошибок в лог:
$hd1=fopen("CONECT_ERROR.log", "w");
fwrite($hd1,$err_dump);
fclose($hd1);
}
Подключаемся к базе данных.

Обращаю внимание что в ф-ии odbc_connect() не обязательно вводить логин и пароль, если вы залогинены под админом, а это лучше сделать.
//***ПОДКЛЮЧАЕМСЯ К ОРАКЛУ****
echo_con("Подключаемся к базе, ждите..");

$con=odbc_connect("novell", "admin", "Очень_сложный_пароль",'SQL_CURSOR_USE_ODBC'); //УБРАТЬ ПАРОЛ�� !!!!
if ($con==0)
{
echo "Error Connect to server";
$err_dump.=odbc_error($con);

error_con($err_dump);
echo_con("/n ОШШИБКА СОЕДИНЕНИЯ С СЕРВЕРОМ!") ;
exit;
}
echo "OK... $con \n";

Тепрерь мы можем сформировать и выполнить запрос к базе eDirectory используя SQL синтаксис
Здесь мы выполняем запрос к таблице UserNDS на выборку имени пользователя в eDirectory, имени, фамилии, дерева, полного имени, группам в которых состоит пользователь, Id пользователя и емейла.

Естественно данная выборка приведена исключительно для примера, на практике можно вытянуть все что угодно, кроме пароля - пароль хеширован.

//Подготавливаем и выполняем запрос
echo_con("Ждите. Выполняем выборку из базы...");

$stmt = odbc_exec ($con,'SELECT "Given Name", "Surname", "NDS_Tree", "Full Name", "Group Membership", "GUID_Data","EMail Address_Addr", "CN"

FROM UserNDS

' );
Далее переводим полученные данные в два массива, первый - содержит все выбранные группы пользователей, второй- содержит всю информацию о пользователях.
$i=0; //для подсчета итераций в цикле
// присваеваем выходящему массиву значения из результата запроса
while(odbc_fetch_row($stmt))
{
if (odbc_result($stmt, 6)==$flagGUID_data)
{
if (trim(odbc_result($stmt, 5))!=='')
{
$groupArr[]=str_replace($context,'',trim(odbc_result($stmt, 5)));
}
$i--;
$out_arr[$i]['Group_Membership'] = $out_arr[$i]['Group_Membership']."|".str_replace($context,'',trim(odbc_result($stmt, 5)));
}
else
{
echo_con("User #: ".$i."\n");
echo_con("Given Name: ".$out_arr[$i]['Given_Name'] = trim(odbc_result($stmt, 1)));
echo_con("Surname: ".$out_arr[$i]['Surname'] = trim(odbc_result($stmt, 2)));
echo_con("Full Name: ".$out_arr[$i]['Full_Name'] = trim(odbc_result($stmt, 4)));
echo_con("Email: ".$out_arr[$i]['EMail Address_Addr'] = trim(odbc_result($stmt, 7)));
echo_con("Login: ".$out_arr[$i]['CN'] = trim(odbc_result($stmt, 8)));
echo_con("GUID_Data: ".$out_arr[$i]['GUID_Data'] = trim(odbc_result($stmt, 6)));
echo_con("Group Membership: ".$out_arr[$i]['Group_Membership'] = str_replace($context,'',trim(odbc_result($stmt, 5))));

$flagGUID_data= odbc_result($stmt, 6);
if (trim(odbc_result($stmt, 5))!=='')
{
$groupArr[]=str_replace($context,'',trim(odbc_result($stmt, 5)));
}
}
$i++;
}
В итоге имеем два массива данных, достаточных для выполнения нашей задачи.

Этап #3: Формируем пакетный файл для экспорта данных в eDirectory

Следующий код будет формировать bat файл, в котором используеться консольная утилита DSADD выполняющая добавление новых объектов в домен AD.

Почему именно бат файл? Почему не внести данные в базу AD прямо из скрипта?

Дело в том, что в Novell eDirectory есть несколько объектов (в том числе и users) являющихся системными, и переносить их в AD в подавляющем большинстве случаев не требуется. Понятно что такие объекты отследить программно сложно, поэтому и генерируем bat-ник, который потом может быть подредактирован системным администратором.
Далее код генерирующий пакетный файл.
//****Создаем cmd код для групп****
$batGroupUser.="
@ECHO OFF
rem Батник для создания пользователей и групп в домене
rem 1. Создаем группы.\n";
foreach ($groupArr as $val)
{
$batGroupUser.="dsadd group \"CN=$val"."1".",OU=test,DC=$dc2,DC=$dc1\"^
-secgrp yes^
-scope g^
-samid $val"."1"."^
-desc \"Novell_migrate\"\n Pause \n";
}
$batGroupUser.="\n\nrem 2. Создаем пользователей.\n";
foreach($out_arr as $val_u)
{
if ($val_u['CN'])
{
$batGroupUser.="\n"."dsadd user cn=\"$val_u[CN]\",ou=test,dc=$dc2,dc=$dc1^
-samID $val_u[CN]^\n";
if ($val_u['Given_Name'])
{
$batGroupUser.=" -fn \"$val_u[Given_Name]\"^\n";
}
if ($val_u['Surname'])
{
$batGroupUser.=" -ln \"$val_u[Surname]\"^\n";
}
if ($val_u['EMail Address_Addr'])
{
$batGroupUser.=" -email \"".$val_u['EMail Address_Addr']."\"^\n";
}
if ($val_u['Group_Membership'])
{ $batGroupUser.=" -memberOf";
$memb_arr=explode("|",$val_u['Group_Membership']);
foreach($memb_arr as $val_memb)
{
$batGroupUser.=" cn=\"$val_memb\",ou=test,dc=$dc2,dc=$dc1";
}
$batGroupUser.="^\n";
}
$batGroupUser.=" -pwd PassWord42^"."\n"." -desc \"Netware Migrate User\"\n Pause \n";
}
}
$batGroupUser.= "\n Pause";
$batHd=fopen("NetwareToWindows2003Server.bat",'w');
fwrite($batHd,$batGroupUser);
echo "END";

die();
В результате получаем пакетный файл, который после изучения и редактирования запускаем на Windows 2003 Server.