Эй, автор сценариев!Возвращение WinRM

Программисты корпорации Майкрософт

Когда авторы сценариев задумали свою серию статей (состоящую из двух частей) об удаленном управлении Windows® (WinRM), на передний план немедленно вышла одна важная проблема: безопасность. Безусловно, нам, авторам сценариев, хорошо известно о проблемах, возникших в связи с выпуском последней книги серии о Гарри Поттере: заказанные заранее экземпляры неожиданно были отправлены и доставлены раньше официального выхода книги в свет. (Это, действительно, вызвало большие проблемы? Ну, конечно, нет; просто издательские компании попросили клиентов не читать книгу до ее официального выхода.)

И это еще только цветочки. Текст книги и окончание всей серии стали достоянием широкой публики гораздо раньше того, как книги появились в книжных магазинах. (Если вы до сих пор не читали последнюю книгу, это произошло по следующей причине: видимо, Гарри Поттер и впрямь оказался волшебником или чем-то вроде этого!) Аналогичным образом, отсканированные копии всех страниц мгновенно были выложены в Интернете, в некоторых случаях даже раньше, чем автор, Дж. К. Роулинг, написала эти страницы. Иными словами, этот был пример полного фиаско в сфере безопасности, и авторы сценариев были полны решимости не допустить повторения подобных вещей с их трудами. Посудите сами, если некоторые лица затратили столько трудов ради финала серии о Гарри Поттере, можно себе представить, какие силы будут брошены на то, чтобы наложить руки на окончание серии о WinRM (состоящей из двух частей).

К счастью, авторы сценариев смогли собраться и сохранить свои секреты в секрете (простите за тавтологию). Это произошло в первую очередь потому, что наши авторы завершили вторую часть своей серии уже после того, как прошли все сроки для предоставления статьи в редакцию. Но простите, ведь тот автор, который отвечает за написание колонки, был в отпуске весь август. И, в отличие от на удивление большого количества сотрудников Майкрософт, он никогда даже и не смотрит на компьютер во время отпуска, не говоря уж о том, чтобы его использовать.

Хотя, сказать по правде, он редко смотрит и почти никогда не использует компьютер, даже когда он не в отпуске. Но это совсем другая история.

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

Figure 1 The denouement

strComputer = "atl-fs-01.fabrikam.com"

Set objWRM = CreateObject("WSMan.Automation") Set objSession = objWRM.CreateSession("http://" & strComputer)

strDialect = "https://schemas.microsoft.com/wbem/wsman/1/WQL" strResource = "https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*" strFilter = "Select Name, DisplayName From Win32_Service Where State = 'Running'"

Set objResponse = objSession.Enumerate(strResource, strFilter, strDialect)

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

    Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

    Set objElement = objXMLDoc.documentElement Wscript.Echo "Name: " & objElement.ChildNodes(0).ChildNodes(0).NodeValue Wscript.Echo "Display Name: " &  objElement.ChildNodes(1).ChildNodes(0).NodeValue Wscript.Echo Loop

Да, мы понимаем. Такого поворота событий никто не мог ожидать. objElement.ChildNodes(0).ChildNodes(0).NodeValue! Кто это когда-либо видел? Безусловно, это часть нашей системы безопасности: ведь даже авторы сценариев понятия не имели, чем закончится наша серия. Но теперь все тайное стало явным.

Прежде чем мы пойдем дальше, необходимо сделать краткий обзор всей темы в целом. Вдруг кто-то из вас, по какой-то невероятной причине, не сумел прочесть первую часть статьи (которую можно найти по адресу technetmagazine.com/issues/2007/11/HeyScriptingGuy). Тогда, в первой части, мы рассмотрели общие понятия удаленного управления — новой технологии, реализованной в операционных системах Windows Server® 2003 R2, Windows Vista® и Windows Server 2008. Эта технология значительно упрощает процесс управления компьютерами через Интернет даже при наличии брандмауэра. Безусловно, и раньше существовала возможность удаленного управления компьютерами с помощью инструментария управления Windows (WMI); однако в основе WMI лежит технология удаленного управления Distributed COM (DCOM). Мы ничего не имеем против этой технологии за исключением одного факта: многие брандмауэры по умолчанию блокируют трафик DCOM. Конечно, можно открыть соответствующие порты и разрешить трафик DCOM, однако многие сетевые администраторы никогда бы не согласились с этим по одной очень существенной причине: открывая доступ трафику DCOM, мы одновременно распахиваем дверь перед многочисленными злоумышленными программами всех сортов и мастей.

Итак, WinRM «является реализованным Майкрософт протоколом WS-Management — стандартным протоколом на основе SOAP, который позволяет взаимодействовать оборудованию и операционным системам различных производителей». Звучит довольно сложно, но, в сущности, здесь говорится о том, что теперь можно осуществлять удаленное управления, используя стандартные Интернет-протоколы, например HTTP или HTTPS.

Как мы указывали в статье прошлого месяца, с помощью WinRM стало гораздо проще устанавливать соединение и извлекать с удаленных компьютеров данные WMI. Но означает ли это, что WinRM — совершенно идеальная технология? Увы, не совсем. Как мы уже поясняли, когда WinRM отправляет данные вызывающему сценарию, эти данные возвращаются в формате XML. Вряд ли стоит говорить, что синтаксический анализ и использование XML-файлов вызывает большие трудности, особенно у тех системных администраторов, которые не имеют большого опыта в этой области. По этой причине в комплект поставки WinRM входит XSLT-файл, который преобразует возвращаемые данные в более удобный формат.

Это было бы просто прекрасно, если бы не тот факт, что ваши выходные данные всегда будут выглядеть приблизительно так:

Win32_Service AcceptPause = false AcceptStop = true Caption = User Profile Service CheckPoint = 0 CreationClassName = Win32_Service

Это не так уж и плохо, однако нельзя забывать, что выходные данные всегда будут выводиться в окне командной строки. По умолчанию вы не сможете сохранить данные в текстовом файле, записать их в базу данных или таблицу Microsoft® Excel® или выполнить с ними какие-либо другие операции. Эти данные можно только отобразить на экране. В этом нет ничего хорошего.

Более того, если вы захотите возвратить лишь выбранное число свойств класса WMI (что обычно делается для снижения сетевого трафика), ваши проблемы только усугубятся. При работе лишь с отдельными свойствами класса, а не со всеми его свойствами, выходные данные будут выглядеть примерно следующим образом:

XmlFragment DisplayName = Windows Event Log Name = EventLog

XmlFragment DisplayName = COM+ Event System Name = EventSystem

Интересно, но не слишком привлекательно с эстетической точки зрения (особенно это касается заголовка XmlFragment, который появляется во всех строках отчета).

Но что можно с этим сделать? Написать собственный код для синтаксического анализа и форматирования данных XML? Вряд ли системному администратору, даже и владеющему навыками написания сценариев, захочется сделать что-то подобное. Или все-таки захочется?

Если вы хотите, чтобы какая-то вещь была сделана правильно, сделайте ее сами

Получается, что использование необработанных данных XML, возвращаемых WinRM, далеко не такая страшная задача, как это может показаться на первый взгляд. В этом месяце мы покажем один простой метод синтаксического анализа и форматирования данных XML. Подход, который здесь будет использоваться, является далеко не единственным способом работы с XML, но он дает превосходные результаты. В этой статье мы, в первую очередь, хотим показать, что вам не стоит полностью полагаться на преобразования XSLT. Если вы поняли основную идею, значит, ваши возможности работы с данными WinRM становятся практически безграничными.

Здесь есть небольшая загвоздка: чтобы уместить эту статью в отведенное для нее пространство, нам пришлось опустить большую часть сценария, приведенного на рис. 1. Однако это не должно вызвать больших проблем, поскольку первые две трети сценария были подробно описаны в статье прошлого месяца. Теперь мы сосредоточимся на последней трети сценария. Именно в этой части выполняется фактическая работа с возвращенными данными.

Как видите, этот эпизод хроник удаленного управления начинается после того, как мы создали экземпляр объекта WSMan.Automation, запросили удаленный компьютер (в данном случае atl-fs-01.fabrikam.com) и получили сведения о всех службах, выполняющихся на этом компьютере. Теперь мы оказались на той строке сценария, в которой мы создали цикл Do Until для считывания и обработки возвращенных данных XML. Выполнение этого цикла будет продолжаться до тех пор, пока не останется данных для считывания и обработки. (Или, если сделать вид, что мы понимаем то, о чем говорим, до тех пор, пока свойство XML-файла AtEndOfStream не получит значение True.)

Вот та строка кода, о которой идет речь, и именно с этой строки начинается история этого месяца:

Do Until objResponse.AtEndOfStream

Первое, что мы делаем внутри цикла, — это используем метод ReadItem для считывание первого раздела возвращенных данных XML. (Поскольку мы работаем с WinRM и получаем сведения о службах, в первом разделе будут содержаться данные о первой службе в коллекции.) После сортировки этих данных (естественно, в формате XML) в переменной с именем strXML создается экземпляр объекта Microsoft.XMLDom. В действительности, при этом создается пустой документ XML, с которым можно работать:

Set objXMLDoc = _ CreateObject("Microsoft.XMLDom")

Создав пустой документ, мы устанавливаем для свойства Async значение False и вызываем метод loadXML.

Затем выполняется... Что вы сказали? Почему для свойства Async установлено значение False? И зачем вызывается метод loadXML? Хорошие вопросы; жаль, что мы не подумали о них сами!

Для начинающих поясним, что свойство Async управляет возможностью асинхронной загрузки данных XML. Если это свойство имеет значение True, загрузка начнется и управление будет передано сценарию немедленно, даже если загрузка еще не завершена. Конечно, это звучит довольно привлекательно. Но, к сожалению, выполнение сценария затем будет продолжено, как будто все необходимые данные уже получены. Если же загружены не все сведения, вы рискуете нажить себе неприятности. Вот почему для свойства Async устанавливается значение False.

Примечание. Можно написать код для периодической проверки состояния загрузки и, таким образом, избежать преждевременного выполнения сценария. Это, безусловно, сработает, но гораздо проще установить для свойства Async значение False. В этом случае выполнение сценария блокируется до завершения загрузки; другими словами, как только началась загрузка, сценарий терпеливо ждет ее окончания и лишь затем выполняет следующие действия.

Что касается метода loadXML, о его предназначении легко понять из названия: он загружает документ (или фрагмент документа) XML правильного формата в пустой документ. Все, что нам нужно сделать, — это вызвать метод loadXML, указав переменную strXML в качестве единственного параметра:

objXMLDoc.loadXML(strXML)

Что мы имеем в чистом остатке? Мы преобразовали данные WinRM в виртуальный документ XML. И теперь можно использовать стандартные методы XML для синтаксического анализа этого виртуального документа.

Для этого необходимо использоваться показанную ниже строку кода, чтобы создать объектную ссылку на корневой элемент в XML-файле:

Set objElement = objXMLDoc.documentElement

А теперь можно и повеселиться. (Если, конечно, вы находите веселой процедуру синтаксического анализа XML-файла. Для нас, авторов сценариев, это одно из самых веселых занятий на свете.)

Как вы, возможно, помните, наш запрос на языке запросов WMI (WQL) (или, используя терминологию WinRM, наш фильтр) выглядит следующим образом:

strFilter = "Select Name, DisplayName " & _ "From Win32_Service Where State = 'Running'"

Как можно видеть, мы запросили два свойства класса Win32_Service: Name и DisplayName. (Мы также включили предложение Where, которое ограничивает возвращаемые данные только выполняющимися службами. Однако в этом нет ничего, что может вызвать проблемы.) Насколько важен тот факт, что мы запросили два свойства? И существен ли порядок этого запроса? Возможно. И каким же образом мы должны догадаться об этом?

Неплохое замечание. Возможно, как авторы этой статьи, мы и вправду знаем ответы на эти вопросы. Все правильно. Совершенно случайно мы знаем, что ответ на оба вопроса является положительным. Важен ли тот факт, что мы запросили два свойства в запросе WQL? Да, важно. В конце концов, нам будут возращены только эти два свойства. (В противоположность запросу Select * From, который возвратит значения для всех свойств класса.)

А порядок запроса этих двух свойств важен? Уверены, что да. Значения свойств возвращаются в том порядке, в каком они указываются в запросе. Это важно потому, что каждое значение свойства возвращается в качестве дочернего узла (или раздела) корневого элемента. Какое свойство станет первым дочерним узлом? Ответ на этот вопрос достаточно прост. В нашем случае первым дочерним узлом станет свойство Name, поскольку это свойство указано первым в запросе WQL. Какое свойство станет вторым дочерним узлом? Правильно — свойство DisplayName, поскольку оно является вторым элементом в запросе WQL.

Секундочку. Вы это вычислили сами или кто-то заранее предоставил материал для этой статьи? Хм...

В любом случае, довольно просто получить значение свойства Name. Все, что нужно сделать, — это указать ссылку NodeValue первого элемента (элемента 0) в первой коллекции ChildNodes (элемент 0) примерно следующим образом:

Wscript.Echo "Name: " & _ objElement.ChildNodes(0).ChildNodes(0).NodeValue

А каким образом указать ссылку на значение свойства DisplayName? В этом случае мы указываем ссылку NodeValue первого элемента во второй коллекции ChildNodes (элемент 1):

Wscript.Echo "Display Name: " & _ objElement.ChildNodes(1).ChildNodes(0).NodeValue 

А если в запросе WQL имеется и третье свойство (скажем, Status)? В таком случае мы просто указываем ссылку NodeValue первого элемента в третьей коллекции ChildNodes (элемент 2):

Wscript.Echo "Status: " & _ objElement.ChildNodes(2).ChildNodes(0).NodeValue 

И так далее, пока не получите последнее свойство.

И каким образом теперь будут выглядеть выходные данные нашего сценария? Примерно вот так:

Display Name: Windows Event Log Name: EventLog

Display Name: COM+ Event System Name: EventSystem

Конечно, эти строки по внешнему виду не сильно отличаются от выходных данных WinRM по умолчанию (хотя нам удалось избавиться от глупых заголовков XmlFragment). Главное отличие заключается в следующем: работая с отдельными значениями свойств, мы более не связаны форматированием по умолчанию (обратите внимание, что мы используем метку Display Name, а не DisplayName). И теперь можно не только выводить данные в окне командной строки.

Хотите записать данные в таблицу Excel? Это довольно легко сделать. Для начала вставьте следующий блок кода (который создает и настраивает новую таблицу Excel) сразу после строки сценария WinRM, в которой вызывается метод Enumerate:

Set objExcel = _ CreateObject("Excel.Application") objExcel.Visible = True Set objWorkbook = objExcel.Workbooks.Add() Set objWorksheet = objWorkbook.Worksheets(1)

i = 2 objWorksheet.Cells(1,1) = "Name" objWorksheet.Cells(1,2) = "Display Name"

Теперь замените исходный цикл Do Until циклом, показанным на рис. 2. Попробуйте и посмотрите, что получится.

Figure 2 New Do Until loop

Do Until objResponse.AtEndOfStream strXML = objResponse.ReadItem

  Set objXMLDoc = CreateObject("Microsoft.XMLDom") objXMLDoc.async=False objXMLDoc.loadXML(strXML)

  Set objElement = objXMLDoc.documentElement objExcel.Cells(i, 1) = objElement.ChildNodes(0).ChildNodes(0).NodeValue objExcel.Cells(i, 2) = objElement.ChildNodes(1).ChildNodes(0).NodeValue i = i + 1 Loop

WinRM, часть 3?

Между статьями этого и прошлого месяца у вас было достаточно времени для начала работы с WinRM. Во всяком случае, мы на это надеемся. WinRM — увлекательная новая технология, которая позволит сделать удаленное управление компьютерами гораздо проще (без ущерба для надежности и безопасности). Безусловно, у читателя возникнет резонный вопрос: означает ли это, что нам следует ждать третьей части саги об удаленном управлении?

К сожалению, мы не можем сказать ничего определенного. Дело здесь не в желании защитить конфиденциальную информацию. Просто так уж устроен процесс планирования и принятия решений авторов сценариев: мы не имеем ни малейшего представления, удастся ли нам написать еще что-нибудь о WinRM. Как говорится, следите за информацией!

Головоломка-сценарий доктора Скрипто

С доктором Скрипто случилась неприятность. Он забыл убрать один из своих сценариев (как следовало бы сделать хорошему специалисту по написанию сценариев) и случайно споткнулся о него. В результате, фрагменты сценария разлетелись во все стороны. Ему удалось найти все переменные, ключевые слова, символы и другие служебные объекты и аккуратно сложить их в алфавитном порядке. Но теперь нужно сложить все это вместе, чтобы получить исходный сценарий. Эта задача отнимет у него довольно много времени, но он надеется закончить ко времени выхода очередного выпуска журнала TechNet Magazine в следующем месяце. Тем временем, вы тоже можете попробовать превратить этот беспорядочный набор фрагментов в полноценный сценарий. Удачи!

Подсказка: пожалуй, мы вам немного поможем. Окончательный сценарий удаляет на локальном компьютере все файлы старше указанной даты.

ANSWER:

Головоломка-сценарий доктора Скрипто

Ответ: неловкий доктор Скрипто, декабрь 2007 г.

Да, доктору Скрипто удалось снова сложить свой сценарий. Ниже приведен восстановленный рабочий сценарий, который удаляет на локальном компьютере все файлы старше указанной даты.

strDate = "20060102000000.000000+000"

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService.ExecQuery("Select * From CIM_DataFile Where CreationDate < '" & strDate & "'")
For Each objFile in colFiles
    Wscript.Echo objFile.Name
Next

Программисты корпорации Майкрософт работают... ну хорошо, получают зарплату в корпорации Майкрософт. Когда они не играют в бейсбол, не тренируют бейсбольную команду, не смотрят бейсбол и не занимаются другими делами, они ведут колонку в Центре сценариев TechNet. Познакомьтесь с ними на веб-узле www.scriptingguys.com.

© 2008 Корпорация Майкрософт и компания CMP Media, LLC. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.