Contents of this directory is archived and no longer updated.

Posts on this page:

Из-за сильной занятости на работе в последнее время не получается уделять время блогу. Но сегодня выкроил немного времени и решил рассказать одну, на мой взгляд, интересную тему, которую каждый решает по-своему и не всегда очень удобным способом (о чём свидетельствует книга по PowerShell и ньюсгруппы). И на это есть весомые причины. А так же расскажу о весьма странном поведении ряда командлетов (на примере Get-Acl) при работе с файлами.

Давайте рассмотрим вопрос с начала и выйдем на нашу проблему. При поиске группы файлов PowerShell, как и многие другие командные оболочки, позволяет создавать маску поиска, как "*", "?". Безусловно с ними проблем быть не может, т.к. эти знаки в именах файлов встречаться не могут. Но если в имени файла используется символ открывающейся или закрывающейся квадратной скобки - "[" и "]", соответственно. Данные символы допустимы для именования файлов, но интерпретатор PowerShell их воспринимает как мета-символы подстановочного шаблона/маски, например:

[a-z]* - все объекты, которые начинаются с букв латинского алфавита
*[0-9] - все объекты, которые заканчиваются любой цифрой и т.д.

При этом встаёт вопрос, как сказать интерпретатору, что квадратная скобка в данном случае является литералом, а не мета-символ? Для начала я в папке создал 3 файла, 2 из которых содержат квадратные скобки:

[Folder] dir


    Directory: Microsoft.PowerShell.Core\FileSystem::D:\Users\_Shared Documents\Folder


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         02.12.08.      0:47         11 text.txt
-a---         02.12.08.      0:47        296 text[0].txt
-a---         02.12.08.      0:48        769 text[text.txt


[Folder]

Давайте просмотрим содержимое файла text[text.txt:

[Folder] Get-Content text[text.txt
Get-Content : Cannot retrieve the dynamic parameters for the cmdlet. The specified wildcard pattern is not valid: text[
text.txt
At line:1 char:12
+ Get-Content  <<<< text[text.txt
[Folder]

занятно, не правда ли? Здесь мы столкнулись с проблемой, что интерпретатор PowerShell воспринял скобку в имени файла за мета-символ. Бороться с этим можно различными методами. Как вариант - использовать параметр -LiteralPath, что будет говорить интерпретатору о буквальном чтении пути. Проверим:

[Folder] Get-Content -LiteralPath text[text.txt
Quest, Quest Software, the Quest Software logo, Aelita, Benchmark Factory, Big Brother,
DataFactory, DeployDirector, ERDisk, Fastlane, Final, Foglight, Funnel Web, I/Watch,
Imceda, InLook, InTrust, IT Dad, JClass, JProbe, LeccoTech, LiveReorg, NBSpool, NetBase,
PerformaSure, PL/Vision, Quest Central, RAPS, SharePlex, Sitraka, SmartAlarm, Speed
Change Manager, Speed Coefficient, Spotlight, SQL Firewall, SQL Impact, SQL LiteSpeed,
SQL Navigator, SQLab, SQLab Tuner, SQLab Xpert, SQLGuardian, SQLProtector, SQL
Watch, Stat, Stat!, Toad, T.O.A.D., Tag and Follow, Vintela, Virtual DBA, and XRT are
trademarks and registered trademarks of Quest Software, Inc. Other trademarks and
registered trademarks used in this guide are property of their respective owners.
[Folder]

Как видите, мы получили содержимое файла (там отрывок из дисклаймера Quest Software). Однако, параметром -LiteralPath обладают далеко не все командлеты. Например, Get-Acl. У него нету такого параметра, поэтому получить ACL список такого файла будет очень проблематично. Посмотрим пример:

[Folder] Get-Item -LiteralPath text[text.txt


    Directory: Microsoft.PowerShell.Core\FileSystem::D:\Users\_Shared Documents\Folder


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         02.12.08.      0:59        769 text[text.txt


[Folder] Get-Item -LiteralPath text[text.txt | Get-Acl
Get-Acl : The specified wildcard pattern is not valid: text[text.txt
At line:1 char:45
+ Get-Item -LiteralPath text[text.txt | Get-Acl <<<<

Get-Item спокойно работает с LiteralPath, но при передаче его по конвейеру в командлет Get-Acl мы получаем ошибку. Хотя, по идее это должно было сработать. Кстати говоря, это первый обнаруженный недостаток. Вместо LiteralPath можно использовать 4 backtick (text````[text.txt), либо проще (что мне показалось более очевидным) - поместить литеральную скобку в подстановочный шаблон:

[Folder] Get-Item text````[text.txt


    Directory: Microsoft.PowerShell.Core\FileSystem::D:\Users\_Shared Documents\Folder


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         02.12.08.      0:59        769 text[text.txt


[Folder] Get-Item text[[]text.txt


    Directory: Microsoft.PowerShell.Core\FileSystem::D:\Users\_Shared Documents\Folder


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         02.12.08.      0:59        769 text[text.txt


[Folder]

В первом примере я использовал рецепт из книги Windows PowerShell In Action (страница 310), второй я додумал сам. Однако, оба эти приёма заменяют параметр -LiteralPath, но так же не решают проблемы командлетов, как Get-Acl. Если передавать эти объекты в Gat-Acl, то всё равно мы будем получать ошибки. Как вариант решения - использование промежуточной функции-фильтра (это рецепт решения Kiron из ньюсгрупп):

filter Escape-Bracket {
    $_.psPath = $_.psPath -replace '\[|]','`$
    $_
}

При использовании фильтра наш литеральный символ заменяем подстановочным знаком "?", пропускаем через фильтр и подаём на командлет Get-Acl:

[Folder] Get-Item text?text.txt | Escape-Bracket | Get-Acl | ft -a


    Directory: Microsoft.PowerShell.Core\FileSystem::D:\Users\_Shared Documents\Folder


Path          Owner        Access
----          -----        ------
text[text.txt Thor\vPodans Everyone Allow  FullControl...


[Folder]

Так оно работает. В качестве первого предположения я подумал на тип объектов:

[Folder] (Get-Item 'text[[]text.txt').GetType().fullname
System.IO.FileInfo
[Folder] (Get-Acl text.txt).GetType().fullname
System.Security.AccessControl.FileSecurity
[Folder]

Что при Get-Item мы получаем тип объектов FileInfo, который не содержит ACL, поэтому данный командлет передаёт в конвейер свойство PsPath, который уже Get-Acl использует для повторного извлечения уже ACL объекта.

Однако, я обратил внимание, что при использовании UNC путей вместо локальных промежуточная функция не требуется и вполне работает подстановочная маска заключённая в квадратные скобки:

[Folder] Get-Acl 'text[[]text.txt'
Get-Acl : The specified wildcard pattern is not valid: text[text.txt
At line:1 char:8
+ Get-Acl  <<<< 'text[[]text.txt'
[Folder] Get-Acl 'text[[]0[]].txt'
Get-Acl : Cannot find path 'D:\Users\_Shared Documents\Folder\text[0].txt' because it does not exist.
At line:1 char:8
+ Get-Acl  <<<< 'text[[]0[]].txt'
[Folder] Get-Acl '\\Thor\_Shared Documents\Folder\text[[]text.txt' | ft -a


    Directory: Microsoft.PowerShell.Core\FileSystem::\\Thor\_Shared Documents\Folder


Path          Owner        Access
----          -----        ------
text[text.txt Thor\vPodans Everyone Allow  FullControl...


[Folder] Get-Acl '\\Thor\_Shared Documents\Folder\text[[]0[]].txt' | ft -a


    Directory: Microsoft.PowerShell.Core\FileSystem::\\Thor\_Shared Documents\Folder


Path        Owner        Access
----        -----        ------
text[0].txt Thor\vPodans Everyone Allow  FullControl...


[Folder]

В первых двух командах я поместил квадратную скобку в подстановочный шаблон (заключил во внешние квадратные скобки) для локальных путей, а во втором - сделал то же самое, но для UNC пути. При этом, типы объектов не изменились:

[Folder] (Get-Item '\\thor\_Shared Documents\Folder\text[[]text.txt').gettype().fullname
System.IO.FileInfo
[Folder] (Get-Acl '\\thor\_Shared Documents\Folder\text[[]text.txt').gettype().fullname
System.Security.AccessControl.FileSecurity
[Folder]

Следовательно, моё предположение было неверным. Вполне возможно, что это баг командлетов, поэтому я на всякий случай запостил проблему на Connect'е:

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

Желающие могут перейти по ссылке и подтвердить данную ошибку. Всем спасибо за участие :)

Update 28.10.2010: исправлена ошибка с назначением права ManageDocuments.


Вот и пришло время закрыть тему управления принтерами и их списками ACL в PowerShell с использованием WMI. Я в блоге уже расписывал решение частных задач по основным задачам управления принтеров и по управлению их ACL списками. В этом посте я сложу все наработки по этому вопросу в единый концептуальный скрипт, который будет называться PrinterUtils.ps1 с достаточно объёмным набором функций, которые нацелены на упрощение для администраторов автоматизации принтеров с использованием PowerShell. Если кто-то захочет разобраться в работе скрипта и понять используемые приёмы, то предлагаю ознакомиться с следующими ссылками:

В качестве основы я использовал свои предыдущие наработки с SecurityDescriptor в предыдущем блоге, когда разбирал вопрос управления SharePermissions из PowerShell:

Материала у меня на эту тему набралось достаточно много, чтобы проникнуться в идею работы классов WMI и SecurityDescriptor, который не раз пытался посадить меня в лужу :) Однако версия скрипта ShareUtils чётко говорит о том, что скрипт далеко не идеален и не оптимален, имеет свои недостатки, т.к. это был мой первый опыт работы с функциями. Сейчас я значительно переработал структуру работы скрипта (оставив только Core работы с SecurityDescriptor), добавив удалённое управление (в разумных пределах) и, главное (как мне кажется), реализовал работу функций в конвейере. Примеры использования скрипта распишу чуть ниже. Итак, представляю набор функций, которые реализованы в скрипте:

  1. Подключение (маппинг) сетевого принтера к пользователю;
  2. Отключение маппинга сетевого принтера от пользователя;
  3. Получение сведений о принтерах;
  4. Установка принтера по умолчанию;
  5. Установка принтера для общего пользования (расшаривание принтера);
  6. Отмена принтера для общего пользования;

Данная секция представляет собой базовые возможности по управлению принтерами и полностью работоспособна в среде Windows XP/Windows Server 2003. А вот секция управления ACL списками принтеров доступна только в среде Windows Vista/Windows Server 2008. Сюда входят следующие функции:

  1. Получение сведений о правах доступа на конкретный принтер, конкретный принтсервер или по списку компьютеров;
  2. Импорт сведений о правах доступа из внешнего источника. Это может быть и CSV и XML или другой формат;
  3. Добавление пользователя или группы в ACL список принтера или всех принтеров, которые подключены к принтсерверу;
  4. Удаление пользователя или группы из ACL списка принтера или всех принтеров, которые подключены к принтсерверу;
  5. Установка пользователя или группы в ACL список принтера или всех принтеров, которые подключены к принтеру. При этом все имеющиеся права доступа будут удалены и заменены только одним ACE с правом ManagePrinters.

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


Read more →

Итак, как я и обещал, я вернулся к вопросу изменения ACL принтеров в PowerShell. В первой части (Странности метода SetSecurityDescriptor класса Win32_Printer) я изложил проблематику вопроса. В конечном итоге я сегодня смог найти решение, которое оказалось не совсем понятным, но относительно предсказуемым.

Вернёмся снова к документации MSDN: SetSecurityDescriptor Method of the Win32_Printer Class

Это, конечно же, было моим попустительством, что не указал флаг управления SE_DACL_PRESENT и не включил привилегии SeSecurityPrivilege ("умение читать - первое умение системного администратора" (c) Peter.G). Понимание этого факта пришло после очередного прочтения поста о смене владельца папки (Смена владельца папки или файла в PowerShell (часть 2)). Что касается флагов управления, то выложу здесь значения флагов:


Read more →

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

Что-то меня натолкнуло снова вернуться к этому вопросу. Учитывая проблематику, изложенных в предыдущих статьях, я перестал искать нативный способ изменения владельца в PowerShell через .NET и решил поискать его в WMI (что означает очередные мучения многострадального SecurityDescriptor :( ). Итак, у WMI есть несколько классов для работы с ACL (AccessControlList) файлов и папок. Например:


Read more →

Навеяно темой на форуме: http://forum.sysfaq.ru/index.php?showtopic=13274

Итак, есть задача отслеживать события удаления объектов (файлов и/или папок) с файлового сервера. Итак, для начала необходимо настроить аудит на файловом сервере. Приведу рекомендации коллеги WindowsNT:

  1. Включить аудит успешных событий типа Object Access.
  2. Отрегулировать размер журнала и параметры перезаписи.
  3. В свойствах папки Shared Documents (и других папок общего доступа) включить Аудит на Успешные Everyone: Delete, Delete Subfolders and Files.

Учитывая сопутствующую проблематику удалений, что для части программ при работе нормально удалять файлы при сохранении было предложено фиксировать 5 событий удаления в секунду. Такая схема вполне сгодится для среднестатистического решения. В любом случае предложенный скрипт можно будет изменить под свои нужды вполне свободно. За основу будет взят пост из моего предыдущего блога (Аудит входов на сервере терминалов с использованием PowerShell), но с некоторыми доработками.

Итак, задача разбивается на 4 составные части:

  1. получение всех эвентов с кодом EventID = 560;
  2. из полученных эвентов получить массив с уникальным временем;
  3. подсчитать количество эвентов для каждого уникального времени из массива, который был получен на втором этапе;
  4. если кол-во эвентов больше или равно 5 (цифра может быть и немного другой), то записываем их в лог и отмечаем, как "возможно нелегальное".

Итак, давайте пробовать решить все этапы:

1) получение массива эвентов по коду 560:

$Events = Get-EventLog security | ?{$_.eventid -eq 560 -and
    $_.EntryType -eq "SuccessAudit" -and
    $_.message -like "*Object Name:`tD:\Shared Documents\*" -and
    $_.message -like "*Accesses:`t%delete*"
}

Итак, здесь я сделал 3 фильтра: код события - 560, Тип аудита Success Audit (т.к. под кодом 560 регистрируются и неуспешные аудиты), имя корневой папки, которая нас будет интересовать (только для случаев, если настроено несколько аудитов на различных папках) и явно будем интересоваться действием $Delete. Теперь из этого массива нужно получить массив уникального времени:

$UniqueTime = $Events | select -Unique TimeGenerated | %{($_.timegenerated).datetime}

Здесь я получил массив с набором уникального времени. Отмечу, что я взял свойство DateTime, которое даст нам точность до 1 секунды. Массив мы делаем затем, чтобы потом считать сколько событий удаления зафиксировано с этим временем (отличить легальное удаление от нелегального). Теперь при помощи полученной переменной соберём кол-во эвентов удаления на момент уникального времени. Иными словами мы берём отметку времени, когда зафиксирован лог удаления файла или папки и посмотрим, сколько ещё логов было зафиксировано за эту секунду:

[object[]]$SelectedEvents = $Events | ?{($_.timegenerated).datetime -eq UniqueTime}
$SelectedEvents.count

Переменная $SelectedEvents будет содержать все эвенты удаления в течении секунды. И их количество можно посчитать свойством Count. И дальше можно проводить сравнительный анализ по кол-ву эвентов. Если их за секунду зафиксировано больше 5 (вероятность нелегального удаления файлов), то продолжаем разбирать переменную $SelectedEvents. Разбирается скрипт по той же схеме, что и в упомянутой выше статье по аудиту доступа на терминальный сервер. В итоге мы получим такой скрипт:

########################################################
# FileServerDeleteAudit.ps1
# Version 1.0
#
# Windows Server 2003 eventlog parser
#
# Vadims Podans (c) 2008
# http://www.sysadmins.lv/
########################################################

$Data = New-Object System.Management.Automation.PSObject
$Data | Add-Member NoteProperty Time ($null)
$Data | Add-Member NoteProperty UserName ($null)
$Data | Add-Member NoteProperty FileName ($null)
$Data | Add-Member NoteProperty EventCount ($null)
$Events = Get-EventLog security | ?{$_.eventid -eq 560 -and
    $_.EntryType -eq "SuccessAudit" -and
    $_.message -like "*Object Name:`tD:\Shared Documents\*" -and
    $_.message -like "*Accesses:`t%delete*"
}
$UniqueTime = $Events | select -Unique TimeGenerated | %{($_.timegenerated).datetime}
foreach ($time in $UniqueTime) {[object[]]$SelectedEvents = $Events | ?{($_.timegenerated).datetime -eq $time}
    if ($SelectedEvents.count -ge 5) {
    $Data.EventCount = $SelectedEvents.Count
    $SelectedEvents | %{
    $message = $_.message.split("`n") |    %{$_.trim()}
    $Data.time = $_.TimeGenerated
    if ($message -like "Primary User Name:`t*$") {
        $Data.UserName = ($message | ?{$_ -like "Client User Name:*"} | %{$_ -replace "^.+`t *"})}
    else {
        $Data.UserName = ($message | ?{$_ -like "Primary User Name:*"} | %{$_ -replace "^.+`t *"})}
    $Data.FileName = ($message | ?{$_ -like "Object Name:*"} | %{$_ -replace "^.+`t *"})
    $Data
        }
    }
}

Что касается поля $Data.UserName, то здесь я сделал проверку поля Primary User Name. Если файл или папка были удалены с компьютера локально, то данное поле заполняется именем пользователя. Если же файл или папку удалили по сети, то в данном поле будет указано имя компьютера в формате ComputerName$, а имя пользователя будет отражено в поле Client User Name. Поэтому если в поле Primary User Name будет встречаться знак доллара ($), то мы в поле $Data.UserName записываем имя пользователя из поля эвентлога Client User Name. Жирным в скрипте я отметил те места, которые в зависимости от местных условий могут изменяться. В первом случае отмечена корневая папка, которая будет изучаться и кол-во инцидентов удаления в секунду описывает порог срабатывания скрипта. И результат работы скрипта:

Time                          UserName              FileName                              EventCount
----                          --------              --------                              ----------
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog                        140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin                    140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\AR                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\BG                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\DA                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140

Enjoy!