Пока не буду больше ничего писать про новые командлеты в PowerShell V2 CTP3, а вернусь к истории - 1.0. Как показала практика, функции в скриптах администраторов завоевали очень высокую популярность и этому есть причина - функции позволяют однажды написать блок кода и потом его использовать много раз. Но не все знают всех возможностей функций и их разновидностей в PowerShell. Сегодня мы поговорим о том, на чём чаще всего спотыкаются начинающие пользователи PowerShell.

Аргументы в функциях

В общем смысле функции строятся по такому принципу:

function FunctionName ($arg1, $arg2, $argN) { scriptoblock }

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

  • используя список аргументов (как в указанном примере)
  • по конвейеру

в первом случае всё просто. Если при объявлении функции мы указываем список аргументов, то при вызове функции эти переменные будут принимать значения. Возьмём простой пример:

function abc ($a, $b, $c) {
Write-Host '$a = ' $a
Write-Host '$b = ' $b
Write-Host '$c = ' $c
}
[vPodans] function abc ($a, $b, $c) {
>> Write-Host '$a = ' $a
>> Write-Host '$b = ' $b
>> Write-Host '$c = ' $c
>> }
>>
[vPodans] abc arg1 arg2 arg3
$a =  arg1
$b =  arg2
$c =  arg3
[vPodans]

Чтобы вызвать функцию - достаточно написать её название и разделяя проблелом передавать ей значения. Вот здесь кроется первая частая ошибка начинающих при работе с PowerShell. Дело в том, что в некоторых языках программирования функции вызываются иначе: functionname (arg1, arg2, argN). В PowerShell же формат вызова функций вот такой: functionname arg1 arg2 argN. При объявлении функции указывать список принимаемых аргументов не обязательно (но рекомендуется для читаемости скрипта). В таком случае все аргументы будут помещены в специальную переменную $args, которая будет являться массивом аргументов. Но кроме аргументов в функции можно передавать параметры-ключи, как в командлетах (например, -Verbose, -WhatIf и т.д.). Это делается при помощи указания типа аргумента. Данный тип называется [switch]:

function abc ($a, $b, [switch]$parameter) {
Write-Host '$a = ' $a
Write-Host '$b = ' $b
Write-Host 'parameter switch status = ' $parameter
}
[vPodans] function abc ($a, $b, [switch]$parameter) {
>> Write-Host '$a = ' $a
>> Write-Host '$b = ' $b
>> Write-Host 'parameter switch status = ' $parameter
>> }
>>
[vPodans] abc 1 5 -parameter
$a =  1
$b =  5
parameter switch status =  True
[vPodans] abc 1 5
$a =  1
$b =  5
parameter status =  False
[vPodans]

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

Конвейер и функции

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

[vPodans] function sum {$_ * 2}
[vPodans] 1..5 | sum
[vPodans]

Мы захотели просто умножить каждое число на 2. Но результата нету. Это вторая частая ошибка начинающих пользователей, которые путают функцию с конвейерным циклом Foreach-Object:

[vPodans] function sum {$_ * 2}
[vPodans] 1..5 | %{sum}
2
4
6
8
10
[vPodans]

а вот теперь получили ожидаемый результат. Дело в том, что в функцию данные поступают не по мере появления их на входе конвейера, а только когда конвейер будет заполнен. Пока конвейер наполняется данными, они помещаются в специальную переменную $input в виде массива. И только после этого начинает исполняться само тело функции. Смотрим:

[vPodans] function input {$input}
[vPodans] 1..5 | input
1
2
3
4
5
[vPodans]

Однако, данный метод очень непроизводителен, когда требуется произвести некоторую операцию над каждым элементом конвейера. Поскольку мы должны сначала ждать, пока по конвейеру будут собраны все данные в переменную $input и только потом начнётся сама обработка. Т.к. переменная $input является массивом, то мы не можем прямо к ней использовать переменную текущего элемента $_. Как вариант, можно в самой функции разобрать переменную $input циклом и получить требуемый результат:

[vPodans] function sum {$input | %{$_ * 2}}
[vPodans] 1..5 | sum
2
4
6
8
10
[vPodans]

Задача хоть и решена, но кроме потери производительности у нас ещё вырос объём кода. Что тоже не есть хорошо. Чтобы избавиться от переменной $input можно использовать этап функции - Process. Что это такое и с чем его едят можно узнать из замечательного поста Дмитрия Сотникова - $input Gotchas. Материал по ссылке изложен достаточно подробно и он будет понятен даже тем, у кого есть хотя бы базовые навыки английского языка. Итак:

[vPodans] function sum {process {$_ * 2}}
[vPodans] 1..5 | sum
2
4
6
8
10
[vPodans]

на этапе Process мы отказываемся в дополнительном разборе переменной $input, поскольку этап Process уже начинает исполняться при поступлении первого элемента на вход конвейера. Следовательно мы получаем выигрыш в скорости работы. Но для таких вещей, как обработка каждого элемента в функции в PowerShell есть одна разновидность функции и называется фильтр! По сути фильтр является той же функцией, которая помещена в цикл Foreach-Object. Фильтры в PowerShell используются так же, как и функции:

filter FilterName ($arg1, $arg2) {scriptoblock}
[vPodans] filter sum {$_ * 2}
[vPodans] 1..5 | sum
2
4
6
8
10
[vPodans]

это то же самое, что и:

[vPodans] function sum {$_ * 2}
[vPodans] 1..5 | %{sum}
2
4
6
8
10
[vPodans]

Как видите, вариантов решений задачи в функциях существует много и каждый может использовать тот, что ему по душе. Можно сказать, что это мелочи? Да, можно. Но именно вот такие мелочи будут влиять на эстетическую красоту, производительность и ресурсопотребление ваших скриптов в работе. Причём разница в производительности и ресурсопотреблении может доходить до 100(!) раз, в зависимости от выбранного метода. Об этом я уже говорил в своём предыдущем блоге на примере циклов: Foreach, Foreach-Object и оптимизация циклов в PowerShell.

Monday, January 12, 2009 9:04:29 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Наверное, не все ещё знают, что вчера состоялся очередной релиз графического редактора для PowerShell - PowerGUI. Версии 1.5.x.x не были совместимы с PowerShell V2 CTP3, но теперь это поправлено:

  • полная поддержка PowerShell V2 CTP3

pgui3

  • можно теперь переставлять панели (как в Visual Studio), откреплять и прикреплять, вобщем настраивать как вам нужно.

 

pgui1

  • панель инструментов теперь молностью настраиваемая. Так же можно назначать горячие клавиши для каких-то действий:

pgui2

pgui3

pgui4

pgui5

  • и дебаггер теперь умеет переходить во внешние скрипты. Из актуального вроде всё. Ну и ссылка на страницу закачки PowerGUI:

[нажать на паровозик :)]

Thursday, January 08, 2009 7:04:29 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Как моим читателям известно я недавно репортил баг на connect (подробности здесь - Первые впечатления от PowerShell V2 CTP3) и сегодня получил ответ следующего содержания (если кто-то не может туда попасть):

Software restriction policies intentionally apply to PowerShell scripts now in V2
Posted by Microsoft on 2009.01.05. at 10:49

следовательно это теперь не баг, а фича. Фича by design. Ещё раз ссылка на connect:

https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=389878&SiteID=99

И в моём предыдущем посте по этой проблеме временный workaround следует считать постоянным. Я сильно надеюсь, что к релизу подготовят документацию по этому вопросу. И мне до сих пор не ясно, будут ли сделаны какие-то изменения в самой политике SRP (ведь блокируемого расширения .ps1 в списке политики SRP нету). Я считаю, что они должны произведены, чтобы не было никаких неизвестностей при настройке политики. Как это будет реализовано - время покажет. Но для тех, кто планирует использовать PowerShell V2 и Software Restriction Policies будет полезно это знать. Неприятно, конечно же, но что поделать.

Tuesday, January 06, 2009 9:19:18 AM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Продолжая цикл постов о новых командлетах в PowerShell V2 CTP3 хочу рассказать про несколько командлетов для управлением компьютера как клиента в домене Active Directory или рабочей группе. Отмечу сразу, что эти командлеты не управляют доменом Active Directory, а только компьютером-клиентом домена (не знаю, как по-русски правильно сформулировать мысль). По сути эти командлеты повторяют аналог утилиты netdom.exe и вот их список:

  • Add-Computer
  • Remove-Computer
  • Rename-Computer
  • Reset-ComputerMachinePassword
  • Test-ComputerSecureChannel

и ещё 2 на закуску:

  • Restart-Computer
  • Stop-Computer

Ну и как обычно - рассмотрим каждый из них:

1) Add-Computer - добавляет компьютер к домену Active Directory или перемещает компьютер между рабочими группами (Workgroup). Данный командлет содержит следующие параметры и ключи:

  • -ComputerName - параметр. Имя компьютера, который нужно включить в домен или рабочую группу. Можно указывать как NetBIOS имена, так и FQDN (Fully Qualified Domain Name) и IP адреса компьютеров. Для локального компьютера можно использовать точку ( . ) или localhost или не указывать этот параметр совсем, поскольку по умолчанию будет использовать именно локальная машина. Так же можно указывать несколько компьютеров перечисляя их через запятую. Имена можно передавать в командлет по конвейеру по свойству ComputerName.
  • -Credential - параметр. Учётные данные для аутентификации в домене или на удалённых компьютерах рабочей группы. Формат учётных данных такой же, как и в подключениях WMI. Для этого можно использовать командлет Get-Credential. Данный параметр следует использовать только при необходимости использования альтернативных учётных данных. Если не указан, то будут использоваться учётные данные текущего пользователя.
  • -DomainName - параметр. DNS имя домена, куда будет подключаться компьютер. Этот параметр является обязательным при подключении к домену.
  • -WorkgroupName - параметр. Название рабочей группы, в которую перемещается компьютер. Обязателен при переводе компьютера из одной рабочей группы в другую.
  • -OUPath - параметр. Если заранее учётная запись компьютера заранее не создана в AD, то указание этого параметра позволяет добавить компьютер в нужный OU с созданием учётной записи в нём. Если этот параметр не указан, то компьютер будет помещён в контейнер по умолчанию - CN Computers. Так же этот параметр не нужно указывать при перемещении компьютера между рабочими группами.
  • -Server - параметр. Используется только при подключении к домену. Позволяет выбирать контроллер домена, который будет производить ввод компьютера в домен и установку пароля компьютера в домене. Параметр опциональный и может иметь смысл при добавлении машин в домен в удалённых сайтах. Указывается в формате DomainName\DCName.
  • -Unsecure - ключ. Операция по вводу компьютера в домен или рабочую группу будет производиться без установки защищённого канала между клиентом и сервером (контроллером домена). В домене с жёсткими политиками безопасности данный ключ может дать сбой.
  • -Reboot - ключ. Как известно любое изменение местоположения компьютера в сети (ввод в домен, вывод из домена, смена рабочей группы) требуют перезагрузки машины.

Примечание: может показаться, что ключ -Reboot очень полезный, но мне так не кажется. И вот почему. Если командлет сам не перезагружает машину после завершения операции, то это нужно сделать вручную. Но перезагружать машину можно только в случае если всё прошло успешно. Но если по каким-то причинам ожидаемый результат не был достигнут, то компьютер будет перезагружен в пустую. Я на этом не настаиваю, но мне это видится именно так.

Hint: не забывайте, что если указываются позиционные параметры, то их имена указывать не обязательно. Например, ComputerName позиционно является первым параметром и если указывать за командлетом сразу имена компьютеров, то название параметра -ComputerName можно и не указывать.

По умолчанию данный командлет не выводит результат на экран, поэтому для вывода результата исполнения можно использовать ключи -PassThru или -Verbose. Так же командлет имеет такие полезные ключи как -Confirm для ручного подтверждения выполнения операции и -WhatIf для моделирования работы командлета. И несколько примеров использования командлета:

Add-Computer -DomainName contoso.com -Credential (Get-Credential) -OUPath OU=Test,OU="3th floor",DC=Contoso,DC=com -Reboot
Add-Computer -ComputerName (Get-Content Comps.txt) -WorkgroupName MSHome -PassThru

2) Remove-Computer - удаляет компьютер из домена или рабочей группы. Содержит параметры -ComputerName, -Credential (учётные данные пользователя, который имеет право на вывод машины из домена или локального администратора, если компьютер перемещается в рабочих группах), -Reboot, -Confirm, -PassThru и -WhatIf. Правила использования этих параметров и ключей такие же, что и для Add-Computer, но только в контексте удаления, а не ввода машины во что-то. Пример:

Remove-Computer -ComputerName computer1, computer2 -Credential contoso.com\Administrator -PassThru -Reboot - выводит компьютер из домена. Следует учесть, что при выполнении команды будет запрошен пароль учётной записи, указанной в Credential.
Remove-Computer MyComputer
- просто удаляет компьютер из рабочей группы (интересно, куда? :-D )

Примечание: ни в коем случае не удаляйте так контроллеры домена. Их сначала нужно понизить до роли рядового сервера командой dcpromo и только потом с помощью UI, netdom или командлета Remove-Computer выводить из домена.

3) Rename-Computer - переименовывает компьютер или компьютеры в рабочей группе или домене Active Directory. Обычно использует следующие параметры:

  • -ComputerName - имя компьютера, которые нужно переименовать. Можно не указывать, если переименовывается локальный компьютер. Если компьютер находится в домене, то следует указывать его полное FQDN имя. Не поддерживает указание нескольких компьютеров, только одного.
  • -NewComputerName - новое имя компьютера. Если машина находится в домене, то переименовывается и её учётная запись.
  • -Credential - учётные данные. Данный параметр является обязательным при переименовании удалённых компьютеров-членов домена (кроме переименовывания локального компьютера).

так же командлет содержит такие ключи как -Confirm, -Reboot и -WhatIf.

Примечание: ни в коем случае не пытайтесь этим командлетом переименовать контроллер домена!

4) Reset-ComputerMachinePassword - сбрасывает пароль учётной записи компьютера в базе Active Directory. Используется только для доменных компьютеров. Данный командлет можно использовать при трудностях аутентификации компьютера в домене или при устаревании пароля. Несколько типичных случаев, когда требуется сброс пароля компьютера - компьютер не аутентифицировался в домене более 30 или 60 дней, в зависимости от настроек домена; компьютер был восстановлен из бэкапа (SystemState), срок которого старше 30 или 60 дней или компьютер был восстановлен из образа без дополнительного восстановления актуального SystemState и другие случаи.. Подробности этой темы выходят за рамки этого поста.

Из актуальных параметров содержит -ComputerName (можно указывать несколько компьютеров. Допускаются FQDN, NetBIOS имена или IP адреса), -Server - имя контроллера домена, который будет производить сброс пароля (не обязательный параметр) и -Credental - имя пользователя, который имеет права сброса паролей указанных компьютеров. Из ключей можно выделить такие как -Confirm и -WhatIf. Общий синтаксис такой:

Reset-ComputerMachinePassword (get-content comp.txt) - переустановит пароль всех компьютеров из списка comp.txt
Reset-ComputerMachinePassword - переустановит пароль текущего компьютера
Reset-ComputerMachinePassword -Server dc1.contoso.com -Credential (Get-Credential) -Confirm - переустановит пароль локального компьютера на контроллере домена с именем DC1 и с вводом альтернативных учётных записей. После запуска потребует подтверждения операции.

5) Test-ComputerSecureChannel - проверяет возможность установки безопасного канала между клиентом и сервером. Работает только в домене и возвращает True или False. Имеет один параметр и ключ:

  • -Server - параметр. Имя компьютера в домене (допускается использовать FQDN, NetBIOS имена или IP адрес). Допускается указание только одного компьютера, с которым хотите проверить работоспособность безопасного канала. Параметр обязательный, поскольку нету смысла проверять защищённый канал с самим собой :)
  • -Repair - ключ. Если команда выводит False, то при указании этого ключа команда будет пытаться восстановить его.

И содержит дополнительные ключи как -Confirm и -WhatIf.

Я не придумал к какой категории присвоить эти 2 командлета и решил их описать здесь. Кстати, очень удобные командлеты:

6) Restart-Computer и Stop-Computer. Первый командлет перезагружает локальный или удалённый компьютер (или несколько), а второй выключает их совсем. Это очень полезно, поскольку я видел как минимум 5 различных решений перезагрузки/выключения компьютеров в скриптах PowerShell. Как правило это либо WMI, либо использование штатного shutdown.exe (кстати говоря, я им пользуюсь всегда) либо ещё что-нибудь. Теперь можно будет этот момент стандартизировать. Много говорить про них не буду, а скажу только, что эти командлеты используют метод Win32Shutdown WMI класса Win32_OperatingSystem. Следовательно эти командлеты обладают всеми новыми возможностями, которые описаны в предыдущей статье: Обзор новых командлетов PowerShell V2 CTP3 - WMI. Одно из преимуществ - можно использовать фоновую работу. Скажем, отправить в ребут сотню компьютеров и пока это всё происходит спокойно работать в консоли (шутка :) ). Поэтому в этих командлетах можно использовать такие параметры и ключи как -AsJob, Impersonate, Authentication, ComputerName и другие. Из уникальных отмечу один параметр:

  • -ThrottleLimit - указывает количество одновременных удалённых подключений для конкретно этой команды. Если компьютеров будет много в этой команде, то чтобы не загружать сеть можно ограничить, скажем, по 10-20 одновременных подключений. По умолчанию максимум одновременно установлено 32 подключения.

и ключ:

  • -Force - без комментариев. Точнее перезагружает или выключает компьютер без спроса, форсирует закрытие всех приложений, что может быть риском потери данных, если они в этот момент не были сохранены.

На сегодня вроде всё. Вроде ничего не пропустил.

Monday, January 05, 2009 3:35:10 PM (FLE Standard Time, UTC+02:00)   Comments [2]    

 

Windows Management Instrumentarion или просто WMI в PowerShell V2 так же претерпел изменения, а точнее дополнения по сравнению с версией 1.0. Я планирую более подробно описать новые и обновлённые параметры командлета Get-WMIObject:

1) -List - ключ. Позволяет получать список классов для системы или список свойств и методов для конкретного класса:

[vPodans] Get-WmiObject -List


   NameSpace: ROOT\cimv2

Name                                Methods              Properties
----                                -------              ----------
__NotifyStatus                      {}                   {StatusCode}
__ExtendedStatus                    {}                   {Description, Operation, ParameterInfo, ProviderName...}
Win32_PrivilegesStatus              {}                   {Description, Operation, ParameterInfo, PrivilegesNotHeld...}
Win32_JobObjectStatus               {}                   {AdditionalDescription, Description, Operation, ParameterIn...
__SecurityRelatedClass              {}                   {}
__Trustee                           {}                   {Domain, Name, SID, SidLength...}
Win32_Trustee                       {}                   {Domain, Name, SID, SidLength...}
__NTLMUser9X                        {}                   {Authority, Flags, Mask, Name...}
....

По умолчанию, без указания других параметров, параметр -List выводит список классов, которые расположены в контейнере по умолчанию Root\CimV2. Но можно указывать и другие контейнеры при помощи параметра -NameSpace. Например:

Get-WmiObject -Namespace root\default -List

Или для конкретного класса:

[vPodans] Get-WmiObject Win32_share -List | select * | fl


Name             : Win32_Share
__GENUS          : 1
__CLASS          : Win32_Share
__SUPERCLASS     : CIM_LogicalElement
__DYNASTY        : CIM_ManagedSystemElement
__RELPATH        : Win32_Share
__PROPERTY_COUNT : 10
__DERIVATION     : {CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER         : THOR
__NAMESPACE      : ROOT\cimv2
__PATH           : \\THOR\ROOT\cimv2:Win32_Share
Path             : \\THOR\ROOT\cimv2:Win32_Share
Derivation       : {CIM_LogicalElement, CIM_ManagedSystemElement}
Methods          : {Create, SetShareInfo, GetAccessMask, Delete}
Scope            : System.Management.ManagementScope
Options          : System.Management.ObjectGetOptions
ClassPath        : \\THOR\ROOT\cimv2:Win32_Share
Properties       : {AccessMask, AllowMaximum, Caption, Description...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers       : {CreateBy, DeleteBy, dynamic, Locale...}
Site             :
Container        :



[vPodans]

здесь мы видим доступные методы (Create, SetShareInfo и другие), а так же свойства объекта класса (AccessMask, AllowMaximum, Caption и другие). Можно и более подробно посмотреть методы или свойства объекта:

Get-WmiObject Win32_share -List | %{$_.methods} - получение подробных сведений о методах объекта класса
Get-WmiObject Win32_share -List | %{$_.properties} - получение подробных сведений о свойствах объекта класаа

Кстати говоря, в PowerShell 1.0 параметр -List не работал для конкретных классов, а только для перечисления самих классов. Т.е. приведённые выше 2 команды в 1.0 не работают. Теперь этот недостаток исправлен.

2) -AsJob - ключ. Позволяет выполнять команду в фоновом режиме. Это новая возможность в PowerShell V2, которая позволяет запускать некоторые команды в фоновом режиме не блокируя при этом саму консоль. Коллега, Вася Гусев, уже делал скринкаст по фоновым работам в PowerShell (и не только о них) на TechDays.ru (потребуется авторизация с помощью LiveID или OpenID). И я так же планирую обсудить в своём блоге этот вопрос в обозримом будущем.

3) -Authentication - параметр. Позволяет задавать уровень аутентификации к удалённой машине. Возможные уровни аутентификации могут быть:

  • -1 (Unchanged) - я не смог найти описание этому уровню, но он в Windows XP/Windows Server 2003 используется для локальных подключений на себя.
  • 0 (Default) - необходимый уровень будет согласовываться между сервером и клиентом в зависимости от настроек Windows Authentication.
  • 1 (None) - не использует аутентификацию. Все уровни безопасности будут проигнорированы. Не следует использовать этот уровень, поскольку с ним аутентифицироваться не удастся (разве что в случае разрешённых анонимных подключений на сервере).
  • 2 (Connect) - клиент аутентифицируется только при установке подключения. После установки подключения аутентификационные данные не проверяются.
  • 3 (Call) - пересылаемые учётные данные проверяются при каждом запросе клиента. Учётные данные криптографически подписываются, но не шифруются. Пересылаемые данные во время сессии не подписываются и не шифруются. Гарантируется, что во время пересылки аутентификационных данных они не были никоим образом изменены.
  • 4 (Packet) - похож на уровень Call с учётом того, что сервер проверяет что данные пришли от ожидаемого клиента. Так же учётные данные подписываются и не шифруются. Данные не подписываются и не шифруются (используется по умолчанию при сетевых подключениях).
  • 5 (PacketIntegrity) - подписываются как учётные данные, так и сами пересылаемые данные. Данный уровень гарантирует, что с момента отправки от клиента до получения данных сервером любые данные не были изменены. Но данные (в том числе учётные) по прежнему не шифруются.
  • 6 (PacketPrivacy) - все передаваемые данные между клиентом и сервером подписываются и шифруются.

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

$a = Get-WmiObject Win32_share -filter "Name = 'sharename'"
$a.PSBase.Scope.Options.Authentication = 6
или PacketPrivacy

4) -Authority - параметр. Позволяет клиенту выбирать, кто будет его аутентифицировать (например, LSA удалённой машины или сервер Active Directory) при указании параметра -Credentials.

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

6) -Impersonation - параметр. В этом параметре можно указывать уровень имперсонализации при удалённых подключениях. Может иметь следующие значения:

  • 0 (Default) - так же не смог найти описания для него. Во всяком случае в самом WMI такой уровень нигде не определён и скорее всего это какой-то производный уровень из нескольких внутри самого PowerShell.
  • 1 (Anonymous) - При указании этого уровня учётные данные клиента скрываются. По факту WMI в современных версиях ОС скорее всего не поддерживает этот уровень, а переводит его в уровень Identify и использует его для имперсонализации клиента. В Windows 2000 было так, во всяком случае. Да и особого смысла в нём нету, поскольку с таким уровнем ничего сделать не удастся в общем смысле.
  • 2 (Identify) - позволяет объектам запрашивать учётные данные вызывающего пользователя, но не использовать их. Следовательно удалённый объект не сможет установить вас в контексте безопасности (т.е. подтвердить, что вы именно тот за кого себя выдаёте). С таким уровнем обычно можно только прочитать ACL списки объектов (и то не всегда), но не более.
  • 3 (Impersonate) - позволяет объектам использовать учётные данные вызывающего пользователя. Это в свою очередь позволяет пользователю использовать свои учётные данные для произведения любых операций над объектами в пределах прав пользователя на этот объект (обычно эти права задаются ACL списками). Данный уровень используется в PowerShell по умолчанию. Поэтому для использования уровня Impersonate данный параметр указывать не обязательно.
  • 4 (Delegate) - это уровень транзитивной делегации учётных данных. Это позволяет удалённым объектам передавать ваши учётные данные другим объектам на других машинах. Иными словами, когда пользователь с машины A подключается к машине B, то в этом случае используется уровень Impersonate между A и B. Но если для завершения операции объекту с машины B нужно получить ещё объект с машины C, то Delegate позволит машине B аутентифицироваться на машине C с учётными данными, которые были получены от машины A. Если совсем грубо, то пользователь просто разрешает запрашиваемым объектам использовать его учётные записи для своих нужд. Безусловно, это большой риск, когда объект может распоряжаться полученными учётными данными как хочет, поэтому все пользователи и компьютеры, которые будут участвовать в транзакции (например, пользователь и компьютеры A, B и C) должны быть помечены как Trusted For Delegation в Active Directory.

В версии 1.0 так же можно было изменять уровень имперсонализации с теми же сложностями, которые были и у параметра Authentication. А именно - нельзя было изменять этот уровень при первом подключении к WMI. Делалось это так:

$a = Get-WmiObject Win32_share -filter "Name = 'sharename'"
$a.PSBase.Scope.Options.Impersonation = 4
или Delegate

7) -EnableAllPrivileges - ключ. Данный ключ включает все привилегии безопасности (семейство SeSecurity Privilege), которыми обладает пользователь. Я уже не раз отмечал, что при работе в системе из графической оболочки Explorer, LSA (Local Security Authority) прозрачно для пользователя включает привилегии при необходимости. Однако, в целях безопасности для скриптов LSA по умолчанию не включает их. Привилегии безопасности нужны для многих операций, как смена владельца объектов, редактирования списков ACL множества объектов, выполнение критических для системы операции как удаление журналов событий, восстановления системы из точек восстановления и т.д. И только WMI нам предлагает интерфейс для их использования. Причём теперь это делается простым указанием ключа при использовании командлета Get-WmiObject. В общем смысле в версии 1.0 нельзя было из скриптов включать привилегии, хотя по факту данный функционал был заложен и активно использовался:

$a = Get-WmiObject Win32_share -filter "Name = 'sharename'"
$a.PSBase.Scope.Options.EnablePrivileges = $true

В этом отношении PowerShell является менее безопасным, чем, к примеру, VBS, который позволяет включать только необходимые привилегии безопасности (например для смены владельца объекта только SeRestorePrivilege и SeTakeOwnershipPrivilege). Причём VBS может очень гибко работать с подключаемыми привилегиями, что делает код более безопасным. В PowerShell они включаются все сразу и отдельные привилегии включать нельзя.

Вот, в принципе и рассмотрели основые полезные и интересные нововведения в командлет Get-WmiObject для работы с WMI в PowerShell V2 CTP3. Я допускаю, что эти параметры к релизу вряд ли претерпят существенные изменения и будут справедливы и в финальной версии V2.

Sunday, January 04, 2009 8:37:15 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

В версии 2.0 появились командлеты, которые позволяют в однострочном режиме управлять параметрами восстановления системы. В них входят:

  • Get-ComputerRestorePoint
  • Checkpoint-Computer
  • Restore-Computer
  • Enabe-ComputerRestore
  • Disable-ComputerRestore

Мой вариант костылей для версии 1.0 описан здесь: SystemRestore и PowerShell. По сути в версии 1.0 доступно большинство функционала V2, но только в виде самописных функций, в V2 уже есть всё (или почти всё) в коробке. Для работы этих командлетов так же потребуются клиентские версии ОС Windows XP/Windows Vista. Посмотрим их попорядку:

1) Get-ComputerRestorePoint - получает список доступных точек восстановления.

Get-ComputerRestorePoint 490 - 490 - опциональный параметр, который позволяет получить сведения о конкретной точке восстановления.

[System32] Get-ComputerRestorePoint

CreationTime           Description                    SequenceNumber    EventType         RestorePointType
------------           -----------                    --------------    ---------         ----------------
2008.12.29. 21:14:04   Windows Installer installed... 490               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2008.12.29. 21:56:01   Windows Update                 491               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2008.12.29. 21:58:55   Windows Update                 492               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2008.12.31. 1:38:08    Windows Update                 493               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2009.01.01. 21:53:49   Windows Installer installed... 494               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2009.01.01. 22:03:01   Windows Installer installed... 495               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2009.01.02. 1:38:06    Windows Update                 496               BEGIN_SYSTEM_C... APPLICATION_INSTALL
2009.01.02. 19:45:27   Windows Installer installed... 497               BEGIN_SYSTEM_C... APPLICATION_INSTALL


[System32] Get-ComputerRestorePoint 490 | select *


__GENUS          : 2
__CLASS          : SystemRestore
__SUPERCLASS     :
__DYNASTY        : SystemRestore
__RELPATH        : SystemRestore.SequenceNumber=490
__PROPERTY_COUNT : 5
__DERIVATION     : {}
__SERVER         : THOR
__NAMESPACE      : root\default
__PATH           : \\THOR\root\default:SystemRestore.SequenceNumber=490
CreationTime     : 20081229191404.400068-000
Description      : Windows Installer installed Quest PowerGUI 1.5.3.
EventType        : 100
RestorePointType : 0
SequenceNumber   : 490
Scope            : System.Management.ManagementScope
Path             : \\THOR\root\default:SystemRestore.SequenceNumber=490
Options          : System.Management.ObjectGetOptions
ClassPath        : \\THOR\root\default:SystemRestore
Properties       : {CreationTime, Description, EventType, RestorePointType...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers       : {dynamic}
Site             :
Container        :



[System32]

Если посмотреть внимательней, то можно легко заметить, что данный командлет (как и все остальные, которые относятся к SystemRestore) реализован через тот же WMI класс SystemRestore. Поэтому на эти командлеты будут накладываться ограничения, которые накладывает этот класс. Это и версия ОС и права доступа. В данном случае для работы с командлетами восстановления системы нужны права администратора и для Windows Vista - проход запроса UAC.

2) Checkpoint-Computer - создаёт новую точку восстановления. Синтаксис командлета может быть такой:

Checkpoint-Computer -RestorePointType Application_Install -Description CustomDescription

-RestorePointType может иметь следующие значения:

  • APPLICATION_INSTALL
  • APPLICATION_UNINSTALL
  • DEVICE_DRIVER_INSTALL
  • MODIFY_SETTINGS
  • CANCELLED_OPERATION

-Description может иметь любое значение, поскольку это будет всего лишь описание. Но лучше использовать рекомендованные значения для описаний, которые документированы в статье MSDN: Restore Point Description Text. Стандартизация в наше время - это очень важно. Время создания точки восстановления на моём нотебуке составило чуть больше минуты и на экране консоли будет такой псевдографический прогресс-бар:

systemrestore

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

3) Restore-Computer - восстанавливает компьютер до указанной точки восстановления:

Restore-Computer -RestorePoint 490 -Confirm -WhatIf

в параметре -RestorePoint указывается номер точки восстановления, который можно получить командой Get-ComputerRestorePoint. Данный командлет обладает ещё опциональными параметрами -Confirm, который будет требовать подтверждения пользователя и -WhatIf, который будет полезен в отладочных целях.

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

4) Enable-ComputerRestore - включает режим восстановления системы для всех дисков или для определённых.

Enable-ComputerRestore -Drive "C:\" -WhatIf

Параметры -Drive и -WhatIf являются не обязательными. Если не указан -Drive, то восстановление системы будет включено для всех допустимых дисков в системе.

5) Disable-ComputerRestore - отключает режим восстановления системы для всех дисков или определеённых:

Disable-ComputerRestore -Drive "C:\" -WhatIf

Параметры -Drive и -WhatIf являются не обязательными. Если не указан -Drive, то восстановление системы будет отключено для всех дисков в системе.

К сожалению функционал изменения настроек SystemRestore, как резервирование места для точек восстановления, время хранения, периодичность создания точек не реализован в командлетах. А ведь лишним это не будет однозначно. Поэтому в отношении CTP3 эти вещи придётся реализовывать с помощью WMI самостоятельно.

Saturday, January 03, 2009 1:45:20 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

PowerShell V2 CTP3 вышел уже давно (23 декабря прошлого года), но всё никак не доходили руки осветить новые командлеты. Сегодня постараюсь рассказать о новых возможностях работы с журналами событий готовыми командлетами. Функционал этих командлетов я уже реализовывал немного раньше:

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

  1. Get-WinEvent
  2. New-EventLog
  3. Write-EventLog
  4. Clear-Eventlog
  5. Limit-EventLog
  6. Remove-EventLog

начнём попорядку:

1) Get-WinEvent. Данный командлет призван заменить ранее использовавшийся командлет Get-Eventlog. Синтаксис команды может быть следующий:

[System32] Get-WinEvent -ListLog * | ft -a

LogName                                                                   MaximumSizeInBytes RecordCount  LogMode
-------                                                                   ------------------ -----------  -------
Application                                                                         20971520       17003 Circular
DFS Replication                                                                     15532032           6 Circular
HardwareEvents                                                                      20971520           0 Circular
Internet Explorer                                                                    1052672           0 Circular
Key Management Service                                                              20971520           0 Circular
ODiag                                                                               16777216         267 Circular
OSession                                                                            16777216         544 Circular
Security                                                                            33554432       16904 Circular
System                                                                              20971520       43146 Circular
Windows PowerShell                                                                  15728640       16803 Circular
ForwardedEvents                                                                     20971520             Circular
Microsoft-Windows-Backup                                                             1052672           6 Circular
Microsoft-Windows-Bits-Client/Analytic                                               1052672             Circular
Microsoft-Windows-Bits-Client/Operational                                            1052672        2226 Circular
....

Показ всех журналов событий в системе, включая журналы в Application and Services Logs (для Vista/2008). Вместо звёздочки можно указать конкретный журнал событий.

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

Последняя колонка показывает режим очистки журнала. Circular означает перезапись более старых событий по мере необходимости. Так же можно посмотреть список провайдеров, которые регистрируют события в журнале:

[System32] Get-WinEvent -ListProvider *

Name                                                        LogLinks
----                                                        --------
.NET Runtime                                                {Application}
.NET Runtime Optimization Service                           {Application}
Active Server Pages                                         {Application}
Application                                                 {Application}
Application Error                                           {Application}
Application Hang                                            {Application}
Application Management                                      {Application}
ASP.NET 1.1.4322.0                                          {Application}
ASP.NET 2.0.50727.0                                         {Application}
CardSpace 3.0.0.0                                           {Application}
Chkdsk                                                      {Application}
Crystal Reports                                             {Application}
DataDynamics ActiveBar 1.0                                  {Application}
Desktop Window Manager                                      {Application}
devenv                                                      {Application}
....

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

Get-WinEvent -ProviderName "Application Hang"

В кавычках просто указываете поставщика событий. Сами события можно посмотреть следующим образом:

[System32] Get-WinEvent -ProviderName "Application Hang" -MaxEvents 1 | select *


Message              :
Id                   : 1002
Version              :
Qualifiers           : 0
Level                : 2
Task                 : 101
Opcode               :
Keywords             : 36028797018963968
RecordId             : 16431
ProviderName         : Application Hang
ProviderId           :
LogName              : Application
ProcessId            :
ThreadId             :
MachineName          : Thor
UserId               :
TimeCreated          : 2008.12.21. 17:19:47
ActivityId           :
RelatedActivityId    :
ContainerLog         : application
MatchedQueryIds      : {}
Bookmark             : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName     :
OpcodeDisplayName    :
TaskDisplayName      :
KeywordsDisplayNames : {}
Properties           : {System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventPrope
                       rty, System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventP
                       roperty...}



[System32]

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

[System32] Get-WinEvent -ProviderName "Application Hang" -MaxEvents 1 | gm -MemberType noteproperty


   TypeName: System.Diagnostics.Eventing.Reader.EventLogRecord

Name    MemberType   Definition
----    ----------   ----------
Message NoteProperty  Message=null


[System32]

в членах объекта мы видим, что свойство Message заткнуто заглушкой (Message=null) и оно ничего не возвращает. Но текст события можно извлечь из свойства Properties:

[System32] (Get-WinEvent -ProviderName "Application Hang" -MaxEvents 1).properties

Value
-----
mstsc.exe
6.0.6002.18000
ac0
01c9637bc8453f5e
8
{67, 0, 114, 0...}


[System32]

текст тут невнятный совсем. Для некоторых событий он возвращает такой же текст, что и в графическом журнале событий (и которые корректно отображаются командлетом Get-EventLog). Те события, которые не показывает корректно Get-EventLog в новом командлете уже не показывают ничего.  Тут вся надежда на разработчиков, что они наконец-то сделают возможность полностью видеть тексты всех эвентов журнала событий, в противном случае придётся снова забивать костыли с помощью адского WMI. Но тут следует обратить внимание на параметр MaxEvents. Без его указания поиск только нескольких событий (например, первых 10 с использованием Select -First 10) займёт значительное время, поскольку команда будет работать пока весь журнал не будет прочёсан. Этот параметр является аналогом параметра -Newest для Get-EventLog. Поэтому если вы хотите посмотреть только часть событий, то используйте этот параметр. В общем смысле это выглядит так:

  • старая нотация: Get-EventLog application -Source "Application Hang" -Newest 5
  • новая нотация: Get-WineVent -ProviderName "Application Hang" -MaxEvents 5

И ещё одно хорошее отличие нового командлета - теперь не обязательно указывать журнал для поиска. Вы можете указать только поставщика событий и количество событий для вывода. Это очень удобно.

Следовательно можно судить, что новый командлет Get-WinEvent является значимо более лучшим, чем прежний Get-EventLog. Хотя его применимость в настоящее время весьма сомнительна, поскольку работу с текстом событий так никто и не сделал.

2) New-EventLog. Нетрудно догадаться, что этот командлет создаёт новый журнал событий:

New-EventLog -LogName "Custom EventLog" -Source "Custom Source"

[System32] New-EventLog -LogName "Custom EventLog" -Source "Custom Source"
[System32] Get-EventLog -List

  Max(K) Retain OverflowAction        Entries Name
  ------ ------ --------------        ------- ----
  20 480      0 OverwriteAsNeeded      17 011 Application
     512      7 OverwriteOlder              0 Custom EventLog
  15 168      0 OverwriteAsNeeded           6 DFS Replication
  20 480      0 OverwriteAsNeeded           0 HardwareEvents
     512      7 OverwriteOlder              0 Internet Explorer
  20 480      0 OverwriteAsNeeded           0 Key Management Service
  16 384      0 OverwriteAsNeeded         267 ODiag
  16 384      0 OverwriteAsNeeded         544 OSession
  32 768      0 OverwriteAsNeeded      16 908 Security
  20 480      0 OverwriteAsNeeded      43 162 System
  15 360      0 OverwriteAsNeeded      16 920 Windows PowerShell


[System32] Get-WinEvent -ListProvider "custom Source"

Name                                                        LogLinks
----                                                        --------
Custom Source                                               {Custom EventLog}


[System32]

Всё очень просто. Внизу я сделал проверку, что наш провайдер событий успешно зарегистрировался и он будет писать только в журнал Custom EventLog. Так же можно и добавлять события (поставщиков) к существующим журналам. Для этого в параметре -LogName нужно указать существующий журнал.

3) Write-Eventlog. Так же по смыслу можно догадаться, что данный командлет добавляет записи в журнал событий. Общий синтаксис его такой:

Write-EventLog -LogName "Custom EventLog" -Source "Custom Source" -EntryType Information -EventID 1 -Message "Привет мир!"

EntryType может иметь следующие значения: Error, Warning, Information, SuccessAudit, FailureAudit. Об этом я уже говорил ранее здесь. Смотрим:

[[System32] Write-EventLog -LogName "Custom EventLog" -Source "Custom Source" -EntryType Information -EventID 1 -Message
"Привет мир!"
[System32] Get-WinEvent -ProviderName "Custom source"

TimeCreated                   ProviderName                                             Id Message
-----------                   ------------                                             -- -------
2009.01.02. 21:05:06          Custom Source                                             1


[System32] Get-WinEvent -ProviderName "Custom source" | select *


Message              :
Id                   : 1
Version              :
Qualifiers           : 0
Level                : 4
Task                 : 1
Opcode               :
Keywords             : 36028797018963968
RecordId             : 1
ProviderName         : Custom Source
ProviderId           :
LogName              : Custom EventLog
ProcessId            :
ThreadId             :
MachineName          : Thor
UserId               :
TimeCreated          : 2009.01.02. 21:05:06
ActivityId           :
RelatedActivityId    :
ContainerLog         : custom eventlog
MatchedQueryIds      : {}
Bookmark             : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName     :
OpcodeDisplayName    :
TaskDisplayName      :
KeywordsDisplayNames : {}
Properties           : {System.Diagnostics.Eventing.Reader.EventProperty}



[System32] (Get-WinEvent -ProviderName "custom source").Properties

Value
-----
Привет мир!


[System32]

Первой строкой мы создали новое событие в журнале, второй посмотрели общую сводку по провайдеру Custom Source, которого мы создали ранее. В нём уже зарегистрировано одно событие, которое мы посмотрели в расширенном виде. И последней строкой посмотрели текст созданного события.

Спросите вы, какая польза от этого, ведь кроме программистов это мало кому нужно? А польза есть от него. Для примера можно взять резервное копирование. Например, у администратор регулярно выполняются скрипты, которые делают резервное копирование данных. И создав нового провайдера или новый журнал администратор может в скрипт добавить код, который будет лог копирования помещать в журнал событий помимо стандартной процедуры отправки лога к себе на почту. Это может оказаться очень удобным! Так же данный командлет содержит параметр -WhatIf, который следует использовать в отладочных целях.

4) Clear-EventLog. Очистка журнала:

Clear-EventLog -LogName "Custom EventLog"

тут всё проще постого. Указываете имя журнала и очищаем его:

[System32] Clear-EventLog "Custom EventLog"
[System32] Get-EventLog -List

  Max(K) Retain OverflowAction        Entries Name
  ------ ------ --------------        ------- ----
  20 480      0 OverwriteAsNeeded      17 011 Application
     512      7 OverwriteOlder              0 Custom EventLog
  15 168      0 OverwriteAsNeeded           6 DFS Replication
  20 480      0 OverwriteAsNeeded           0 HardwareEvents
     512      7 OverwriteOlder              0 Internet Explorer
  20 480      0 OverwriteAsNeeded           0 Key Management Service
  16 384      0 OverwriteAsNeeded         267 ODiag
  16 384      0 OverwriteAsNeeded         544 OSession
  32 768      0 OverwriteAsNeeded      16 908 Security
  20 480      0 OverwriteAsNeeded      43 165 System
  15 360      0 OverwriteAsNeeded      16 920 Windows PowerShell


[System32]

Во второй строчке мы уже видим, что нами созданный журнал очистился от тестового сообщения. Данный командлет содержит параметр -WhatIf, который следует использовать в отладочных целях.

5) Limit-EventLog. Позволяет изменять размер журнала и политику перезаписи журнала. Общий синтаксис такой:

Limit-EventLog -LogName "Custom EventLog" -MaximumSize 64mb -OverflowAction OverWriteOlder -RetentionDays 10

[System32] Limit-EventLog -LogName "Custom EventLog" -MaximumSize 64mb -OverflowAction OverWriteOlder -RetentionDays 10
[System32] Get-EventLog -List

  Max(K) Retain OverflowAction        Entries Name
  ------ ------ --------------        ------- ----
  20 480      0 OverwriteAsNeeded      17 011 Application
  65 536     10 OverwriteOlder              0 Custom EventLog
  15 168      0 OverwriteAsNeeded           6 DFS Replication
  20 480      0 OverwriteAsNeeded           0 HardwareEvents
     512      7 OverwriteOlder              0 Internet Explorer
  20 480      0 OverwriteAsNeeded           0 Key Management Service
  16 384      0 OverwriteAsNeeded         267 ODiag
  16 384      0 OverwriteAsNeeded         544 OSession
  32 768      0 OverwriteAsNeeded      16 908 Security
  20 480      0 OverwriteAsNeeded      43 165 System
  15 360      0 OverwriteAsNeeded      16 920 Windows PowerShell


[System32]

Здесь мы задали размер журнала в 64 мегабайта и политику перезаписи старых логов старше 10 дней.

6) Remove-EventLog. Удаляет журнал событий или провайдера событий:

Remove-EventLog -Source "Custom Source" - удаляет провайдера событий из системы
Remove-EventLog -LogName "Custom EventLog" - удаляет указанный журнал из системы

И этот командлет содержит параметр -WhatIf.

В принципе, есть ещё командлет Remove-Event, который должен позволять удалять события из журналов событий, но мне пока что не удалось заставить его работать. Но вообще удаление эвентов из журналов событий - плохая и порочная практика, которая ни к чему хорошему не приведёт.

Вот мы и рассмотрели все новые командлеты в PowerShell V2 CTP3, которые относятся к работе с журналами событий (эвентлогами). В самом начале поста я привёл ссылки, на альтернативные решения, которые повторяют функционал этих командлетов для версии 1.0, но и даже умеют немножко больше (например, архивация и ротация архивов эвентлогов). В целом можно оценить позитив от новых командлетов, которые позволяют нам более удобным способом работать с журналами событий. Но всё равно нужно ждать рабочего решения, который смог бы получать тексты сообщений из журналов Windows Vista/Windows Server 2008, поскольку без него все эти нововведения мало кому будут нужны. Вобщем, как-то так.

Friday, January 02, 2009 10:11:19 PM (FLE Standard Time, UTC+02:00)   Comments [2]    

 

 · 
All content © 2008 - 2010, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
My former blog -
For english language visitors

Translate via Google Translator

Библиотека
Календарик
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Карта расположения посетителей
Favorites

Домашняя страничка Теры Патрик

Disclaimer
Вся информация на сайте предоставляется на условиях «как есть», без предоставления каких-либо гарантий и прав.

При использовании материалов c данного сайта ссылка на оригинальный источник обязательна.