Системный администратор — человек весьма занятой. И если в данный момент он не озабочен ликвидацией серьезной угрозы безопасности сети, то наверняка занимается устранением недоработок по проекту, начатому в прошлом месяце, а то и в прошлом году. Так что сама идея, будто при таком напряженном графике можно выкроить время для каких-то других дел, кажется нелепой. И все же есть одна технология, для которой стоит сделать исключение. Я имею в виду Windows PowerShell — интерактивную среду для работы со сценариями и командную оболочку, позволяющую автоматизировать административные задачи и обращаться к широкому диапазону данных.

С помощью PowerShell можно выполнять команды непосредственно в окне командной строки или запускать сценарии, содержащие такие команды. PowerShell имеет собственный, построенный на основе объектной модели Microsoft .NET язык сценариев, сочетающий средства объектно-ориентированного программирования с простотой использования сценариев командной строки. Для администратора PowerShell представляет собой среду, позволяющую превратить сложные и многократно выполняемые задачи в простые операции. С помощью PowerShell можно обращаться к целому ряду систем и технологий, например Active Directory (AD) и Management Instrumentation (WMI) для выполнения таких задач, как извлечение записей из журналов регистрации событий, отключение пользовательских учетных записей в AD, а также считывание данных из определенных пользователем разделяемых ресурсов компьютера.

PowerShell функционирует в средах Windows Vista, Windows Server 2003 SP1, Windows Server 2003 Release Candidate 2 (R2) и Windows XP SP2. Этот компонент можно устанавливать и в среде Windows Server 2008 (прежнее рабочее название Longhorn Server). PowerShell можно использовать на машинах, оснащенных процессорами x86, x64 и IA64. Однако, перед тем как устанавливать PowerShell, нужно запустить Microsoft .NET Framework 2.0. Этот компонент можно загрузить по адресу http://msdn2.microsoft.com/en-us/netframework/aa569263.aspx. Адрес для загрузки PowerShell — http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx. Чтобы установить любой из этих продуктов, нужно просто запустить установочную программу и следовать указаниям мастера.

После установки PowerShell среда готова к работе. Нужно открыть меню Start и выбрать в нем пункты All Programs, Windows PowerShell 1.0 и далее Windows PowerShell. В окне PowerShell можно выполнять команды или файлы сценариев PowerShell (.ps1), для чего требуется ввести в окне командной строки команду или имя файла. Чтобы протестировать программу, в командной строке наберите

get-help

и нажмите клавишу ввода. На экране отобразится информация о получении справки в среде PowerShell — безусловно полезная команда. Другие команды, или cmdlet, которые могут пригодиться при изучении PowerShell, приводятся во врезке «Рычаги PowerShell».

Теперь все готово к выполнению команд и сценариев. Остается только вкратце ознакомиться с языком PowerShell.

Рассмотрим три демонстрационных сценария — RetrieveAppEvents.ps1, DisableUser.ps1 и FindShares.ps1, которые иллюстрируют многие из базовых концепций языка.

RetrieveAppEvents.ps1

Приведенный в листинге 1 сценарий RetrieveAppEvents.ps1 считывает записи в журнале регистрации событий локального приложения и записывает их в текстовый файл. Как показано во фрагменте A, сценарий начинается с определения переменной $date. Знак доллара всегда предшествует именам параметров и переменных. Для считывания текущей даты и времени (эти данные именуются также datetime) рассматриваемая переменная использует команду Get-Date. Команда аналогична функции. Она выполняет конкретное действие и обычно имеет форму «глагол-существительное». Затем я использую метод AddDays для получения данных datetime по состоянию на момент ровно за 24 часа (т. е. за сутки до текущего datetime) и присваиваю это значение переменной $date.

Сценарий RetrieveAppEvents.ps1
Далее я создаю функцию FormatEntryType, представленную во фрагменте B. Функция — это именованный блок кода, выполняющий то или иное действие. После создания функции можно сослаться на нее в любом месте сценария, и этот блок кода будет выполнен. В данном случае функция FormatEntry считывает содержимое текстового файла, модифицирует его и записывает во второй текстовый файл. Функция принимает параметр $file, который передает функции имя маршрута к целевому текстовому файлу.

Первая команда в блоке операторов функции (заключенном в фигурные скобки) использует команду Get-Content для считывания содержимого текстового файла, записанного в $file. Отметим, что за командой следует символ конвейеризации (|). Он означает, что данное содержимое должно быть по конвейеру передано следующей команде. Одна из особенностей, благодаря которым среда PowerShell оказывается столь удобной в применении, это простота создания конвейеров для передачи информации от одного оператора другому.

В рассматриваемой функции я передаю данные, считанные командой Get-Content, по конвейеру команде ForEachObject, для которой можно использовать псевдоним ForEach или %. Команда ForEach позволяет циклически перемещаться по объектам внутри коллекции. В данном случае коллекция состоит из содержимого текстового файла. По умолчанию объекты в файловой коллекции разграничиваются символами конца строки, т. е. на каждую строку коллекции приходится по одному объекту. Стандартную схему можно изменить, но в контексте нашего примера символы конца строк вполне приемлемы.

Для обработки каждого объекта внутри коллекции команда ForEach использует выражение, заключенное в фигурные кавычки. Это выражение начинается с символа $_, который ссылается на текущий объект, введенный из коллекции. Далее выражение использует оператор -replace для замены любого объекта «ошибка» объектом *** ERROR ***. Иными словами, любая строка, содержащая только слово error, заменяется символами *** ERROR ***. Вторая команда ForEach выполняет аналогичную операцию с объектами «предупреждение».

Вторая команда ForEach по конвейеру передает содержимое команде Out-File, которая направляет это содержимое в файл AppEvent_EntryTypes.txt. Всякий раз, когда данная функция активизируется внутри сценария, содержимое вставляется в этот файл.

Код во фрагменте C считывает записи событий приложений и присваивает полученные значения переменной $events. Чтобы считать данные из журнала регистрации событий приложения, я пользуюсь командой Get-Eventlog, а в качестве параметра указываю Application. Затем я передаю по конвейеру данные о событии команде Where-Object. Обратный апостроф (`) в конце строки означает, что оператор переходит на следующую строку. Впрочем, когда строка разрывается у конвейера, использовать обратный апостроф не требуется.

Команда Where-Object фильтрует данные в соответствии с критерием, который указан в выражении, заключенном в фигурные скобки. Как и в случае с командой ForEach, для ссылки на текущий объект внутри коллекции используется символ $_. В данном случае коллекция состоит из записей событий. Кроме того, символ $_ можно использовать для ссылки на те или иные свойства внутри объекта. К примеру, в рассматриваемом выражении символ $_ используется для ссылки на свойство TimeGenerated. Чтобы сослаться на свойство объекта, нужно добавить точку, за которой следует имя свойства. Далее выражение использует оператор «больше, чем» (greater than, -gt) для сравнения значения свойства TimeGenerated и значения переменной $date. В итоге переменная $events включает только те события, которые были сгенерированы за последние 24 часа.

Далее я использую переменную $events для обращения к событиям. Оператор во фрагменте D передает содержимое $events по конвейеру команде ForEach. Выражение ForEach состоит из команды Out-File, которая записывает содержимое событий в файл AppEvents.txt. Команда Out-File принимает параметр -Append, который при добавлении событий в файл не допускает их записи поверх предыдущих событий. Наряду с этим данная команда принимает параметр -InputObject, который с помощью символа $_ и имен свойств указывает типы данных для записи в файл. В результате в выходной файл попадают только метка времени, тип записи, источник и сообщение, связанные с каждым событием.

Наконец, код во фрагменте E вызывает функцию FormatEntryType и передает этой функции полное имя файла AppEvents.txt. Как отмечалось выше, эта функция считывает данные первого текстового файла, обновляет их и добавляет в файл AppEvents_EntryTypes.txt. На рис. 1 показан образец события из файла AppEvents_EntryTypes.txt.

Пример события из AppEvents_EntryTypes.txt
Чтобы запустить на выполнение файл RetrieveAppEvents.ps1 из командной строки PowerShell, достаточно просто ввести полное имя сценария и нажать клавишу ввода. Иными словами, команда могла бы выглядеть следующим образом:

c:scripts etrieveappevents.ps1

Однако по умолчанию PowerShell не допускает выполнения сценариев. Чтобы отменить этот запрет, нужно изменить настройки безопасности PowerShell. Эти настройки можно изменить, введя команду:

set-executionpolicy remotesigned

После этого вы сможете запускать сценарии собственного изготовления; все прочие сценарии должны иметь цифровую подпись.

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

DisableUser.ps1

Представленный в листинге 2 сценарий DisableUser.ps1 отключает учетные записи пользователей в AD. Как показано во фрагменте A, начинается этот сценарий с ключевого слова Param; с его помощью я определяю параметр ($sam), который передает учетную запись пользователя, вводимую из командной строки, когда запускается сценарий. Использовать рассматриваемое ключевое слово можно по-разному:

  • вводить только ключевое слово Param и имя параметра в скобках;
  • если при запуске сценария не указывается какое-либо значение, можно задать значение по умолчанию;
  • можно использовать ключевое слово Throw (что я, собственно, и сделал в этом сценарии), чтобы возвращать сообщения об ошибке в ситуациях, когда значение не указано. При возвращении сообщения об ошибке ключевое слово Throw и сообщение необходимо заключать в скобки, а перед скобкой вводить знак доллара.

Код во фрагменте B обнаруживает учетную запись пользователя в AD. Вначале создается объект, осуществляющий поиск внутри каталога. Для создания объекта я использую команду New-Object с классом DirectorySearcher в пространстве имен DirectoryServices. Информацию о пространстве имен DirectoryServices можно найти по адресу http://msdn2.microsoft.com/en-us/library/system.directoryservices.aspx. Затем этот объект указывается в качестве значения переменной $ds.

Далее я создаю фильтр для объекта $ds, для чего выполняю настройку свойства объекта Filter ($ds.filter). В основу фильтра положены атрибуты интерфейса Active Directory Service Interfaces (ADSI), которые определяются после знака равенства (=). Этот фильтр удаляет все значения, кроме тех, которые соответствуют определениям атрибутов. В результате фильтр возвращает учетную запись пользователя, которая является частью категории данного объекта и принадлежит к классу объектов «пользователь». Она носит имя SAM, соответствующее имени, указанному в параметре $sam.

После создания фильтра я использую метод FindOne объекта $ds ($ds.findOne()) для считывания учетной записи пользователя. Я назначаю эту учетную запись в качестве значения переменной $dn. Код во фрагменте C определяет две переменные. Первая переменная — $desc — применяется для хранения описания учетной записи пользователя. Для считывания этого описания используется переменная $dn, которая обращается к свойствам учетной записи, а затем вызывает свойство Description ($dn.properties.description). Вторая переменная — $date — предназначена для хранения текущего значения datetime, которое я получаю с помощью команды Get-Date. Обе эти переменные используются в сценарии позднее для обновления описания учетной записи пользователя.

Код во фрагменте D отключает учетную запись пользователя. Этот раздел заключается в блок операторов If, который выполняется, когда значение условия If ($dn.path.length -gt 0) расценивается как «истина». В этом условии длина свойства Path объекта $dn сравнивается с нулем. Свойство Path содержит LDAP-местоположение данной учетной записи пользователя в AD. Когда свойство Path содержит некоторое значение, запускается блок операторов If.

Первая команда в блоке операторов If создает с помощью свойства Path объект ADSI для учетной записи пользователя. Объект ADSI, назначаемый переменной $user, используется для доступа к свойствам и методам объекта, включая свойство AccountDisabled.

В следующей команде я задаю свойству AccountDisabled значение «истина». Поскольку AccountDisabled хранится в двоичной коллекции в AD, для обновления этого свойства используется метод InvokeSet базового объекта PowerShell (psBase). Метод InvokeSet принимает два аргумента: имя свойства (AccountDisabled) и новое значение. Чтобы установить значение свойства AccountDisabled «истина», я использую встроенную переменную $true.

Теперь все готово для того, чтобы обновлять описание учетной записи пользователя. Для этого я вызываю метод Put объекта $user, который принимает два аргумента: имя свойства (Description) и значение. В данном случае значение представляет собой комбинацию переменных $desc и $date, а также слова disabled. Значение берет исходное описание и добавляет к нему слово disabled, после чего следует текущая метка datetime.

После обновления информации AD необходимо зафиксировать изменения, поэтому я вызываю метод SetInfo объекта $user. Затем с помощью команды Write-Host я отображаю два сообщения. В первом из них говорится, что учетная запись была отключена. Второе сообщение отображает отличительное имя (distinguished name, DN) этой учетной записи.

Код во фрагменте E отражает финальную часть сценария; это блок операторов Else. Оператор Else выполняется в том случае, когда условие If не выполняется. В рассматриваемом сценарии оператор Else с помощью команды Write-Host отображает сообщение о том, что искомая учетная запись пользователя не была обнаружена.

Вот и все, что можно сказать о файле DisableUser.ps1. При выполнении данного сценария необходимо иметь доступ к хранилищу AD. Я испытывал этот сценарий на компьютере под управлением Windows 2003 Enterprise Edition, настроенном как контроллер домена (DC). Кроме того, я протестировал сценарий на системе Windows XP; при этом на контроллере домена была установлена система Windows 2000.

FindShares.ps1

Сценарий FindShares.ps1, представленный в листинге 3, считывает список указанных пользователем разделяемых ресурсов на компьютере. Как и файл DisableUser.ps1, FindShares.ps1 начинается с определения параметра. Из фрагмента A явствует, что при запуске сценария параметр $computer передает этому сценарию имя компьютера. Однако надо отметить, что в том случае, когда параметр не указан, этот параметр не возвращает сообщение об ошибке, а передает значение по умолчанию. В данном случае значением по умолчанию является точка, которая обозначает локальный компьютер.

Сценарий FindShares.ps1
Код во фрагменте B создает объект WMI с помощью команды Get-WMIObject. Эта команда указывает класс Win32_ Share с помощью параметра -Class, пространство имен rootCIMV2 — с помощью параметра -Namespace и имя компьютера в $computer — с помощью параметра -ComputerName. Самый важный из этих параметров — -Class. Именно он определяет тип сведений, которые нужно получить с помощью объекта WMI.

Получив информацию о классе WMI, я передаю ее по конвейеру команде Where-Object. Заключенное в фигурные скобки выражение Where-Object включает три условия. В первом условии с использованием оператора not equal (-ne) осуществляется сравнение значения свойства Caption и фразы default share. Для выполнения условия необходимо, чтобы значение свойства не совпадало с содержанием фразы. Во втором условии используется оператор -notlike. С его помощью значение свойства Caption сравнивается со значением параметра remote*. Отметим, что здесь применяется подстановочный символ, который может представлять любые символы. Для выполнения условия требуется, чтобы значение свойства Caption не начиналось со слова remote, однако заканчиваться оно может любыми символами. Последнее условие подобно второму с той лишь разницей, что значение свойства Caption не может начинаться со слова logon.

Для связи всех трех условий в выражении Where-Object используется логический оператор -and; иначе говоря, для того, чтобы разделяемый ресурс был включен в список, необходимо, чтобы были выполнены все три условия. Я передаю отфильтрованный список по конвейеру команде Sort-Object, которая сортирует список совместно используемых ресурсов по значению свойства Name (по умолчанию сортировка ведется по возрастанию или в алфавитном порядке). Отсортированную информацию WMI я назначаю переменной $chares.

Код во фрагменте C представляет собой блок оператора If. В условии If ($shares -ne $null) оператор -ne и системная переменная $null используются для того, чтобы определить, может ли переменная $shares иметь значение null. Блок операторов If выполняется в том случае, когда значение переменной не равно null, т. е. когда условие соблюдается.

Блок операторов If начинается с команды Write-Host. Поскольку в команде контент не указывается, она просто возвращает пустую строку. Тем самым обеспечивается дополнительное пространство для более четкого отображения информации в окне PowerShell.

Далее следует оператор ForEach. Этот оператор не идентичен команде ForEach-Object, хотя они выполняют одну и ту же функцию. Ситуацию осложняет то обстоятельство, что ForEach — один из псевдонимов команды ForEachObject. Различать их можно следующим образом. Когда ForEach размещается в начале команды, это оператор ForEach. Когда ForEach находится внутри конвейера, это команда ForEach-Object.

Оператор ForEach «перебирает» объекты (т. е. разделяемые ресурсы) в коллекции $shares. Оператор определяет переменную $share, ссылающуюся на текущий объект. Выражение ForEach использует переменную $share для того, чтобы предпринять некоторые действия в отношении каждого объекта. Первая команда в выражении ForEach — это команда Write-Host; она записывает значение свойства Name в окно PowerShell. Сценарий обращается к свойству имени через переменную $share. Вторая команда WriteHost записывает в окно PowerShell значение свойства Path. Последняя команда Write-Host просто добавляет строку после каждой итерации, чтобы облегчить процесс чтения списка разделяемых ресурсов.

Когда условие If ($shares -ne $null) не выполняется, запускается блок операторов Else во фрагменте D. Блок операторов Else содержит собственные блоки операторов If и Else. Вложенное условие If определяет, что имя компьютера должно выражаться точкой. Когда имя компьютера выражается точкой, Write-Host возвращает это имя с помощью переменной среды COMPUTERNAME. Обратите внимание: для того, чтобы получить доступ к переменной среды, необходимо перед именем переменной ввести $env:. Когда имя компьютера не выражается точкой, Write-Host возвращает имя, хранящееся в $computer.

Пример списка общих ресурсов
При выполнении сценария FindShares.ps1 администратор получает список разделяемых ресурсов. Образец выходных данных этого сценария представлен на рис. 2.

Верхушка айсберга

Как видите, PowerShell открывает доступ к широкому диапазону различных типов данных и предоставляет богатые возможности для использования этих данных. Три рассмотренные в статье сценария — это всего лишь верхушка айсберга. Чем больше времени и сил вы посвятите изучению среды PowerShell, тем больше будет отдача. И кто знает, возможно, полученные новые знания помогут наконец-то покончить со всеми прошлогодними проектами.


Роберт Шелдон (contact@rhsheldon.com) — технический консультант и автор большого количества книг по технологиям Microsoft Windows и базам данных