скрипт формирования отчета по правам доступа — часть 1

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

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

Скрипт не ищет унаследованные права доступа подкаталогов, унаследованные права ищет только для корневой папки $RootPath. Для включения в поиск унаследованных прав подкаталогов надо редактировать или убрать фильтр свойства IsInherited.

Скрипт не фильтрует «мусорные» списки доступа, оставшиеся после удаления учетных записей (в списках доступа помимо учетных записей и групп доступа попадаются SID-идентификаторы «мертвых душ»).

Скрипт формирует csv-файл со следующими полями:

  • FullName — полный путь текущего каталога;
  • CreationTime — дата и время создания;
  • LastAccessTime — дата и время последнего открытия;
  • LastWriteTime — дата и время последнего редактирования;
  • FileSystemRights — права доступа (FullControl, Read, Modify и пр.);
  • AccessControlType — тип правила (Allow или Deny);
  • IdentityReference — субъект безопасности: учетная запись или SID;
  • IsInherited — унаследовано от родительской папки или нет;
  • InheritanceFlags — определяет семантику наследования элементов управления доступом («Только для этой папки», «Для этой папки и ее подпапок» и т.д.);
  • PropagationFlags — определяет порядок распространения действия элементов управления доступом на дочерние объекты.

Csv-файл создается в директории скрипта, в имени csv-файла указано имя данного скрипта и точное время создания csv-файла. В качестве разделителя используется «;», т.к. Excel по умолчанию разбивает на столбцы по этому разделителю и т.к. среди значений InheritanceFlags присутствует знак запятой.

Для записи в csv-файл указанных выше полей в скрипте формируется новый объект типа PSObject, объединяющий часть свойств объекта директории $dir и часть свойств объекта списка доступа $acl. Это связано с тем, что Export-Csv не умеет принимать значения от нескольких объектов, перечисленных через запятую, также не умеет принимать значения из двумерных массивов, т.к. не может определить заголовки столбцов. Таким образом проблема решается созданием своего объекта типа PSObject с необходимым набором свойств.

В скрипте используется функция записи ошибок в лог-файл в директорию скрипта. Имя лог-файла совпадает с именем скрипта. Над обработкой ошибок пока сильно не заморачивался — для примера обработал ситуацию, когда по ошибке указывается несуществующий путь $RootPath.

Для перебора подкаталогов используется рекурсивная функция Recur(). Алгоритм функции следующий:

  • Поиск подкаталогов корня
  • Перебор подкаталогов:
    • Ищем ненаследованные права подкаталога, запись в csv-файл
    • Поиск дочерних директорий подкаталога
    • Перебор дочерних директорий:
      • Ищем ненаследованные права подкаталога, запись в csv-файл
      • Поиск дочерних директорий
      • Перебор дочерних директорий: очередная рекурсия и т.д.

Использование своей рекурсивной функции связано с тем, что команда Get-ChildItem $dir.path -Recurse проваливается в рекурсию только после просмотра всех директорий более верхнего уровня. Мне же хотелось получить последовательный список директорий. В принципе это с успехом решается упорядочиванием по имени столбца FullName в таблице csv-файла в Excel. В этом случае скрипт будет немного быстрее.

Скрипт тестировал на сервере Windows Server 2012 R2 на структуре папок (свыше 8 тыс. подкаталогов) без dfs репликации с правами администратора домена. На сервере присутствуют только файловые службы, используется dfs-репликация нескольких каталогов, uac отключен. В дальнейшем скрипт будет тестироваться на dfs-папках. Файл со скриптом можно скачать по этой ссылке.

Сам скрипт:

cls

# Задаем путь к корневой папке
$RootPath = "D:\Share\"

# Определяем полный путь к папке с запущенным скриптом
$ScriptPath = $MyInvocation.MyCommand.Definition | `
split-path -parent
# Определяем имя запущенного скрипта
$ScriptName = $MyInvocation.MyCommand.Name
# Вырезаем ".ps1" из имени скрипта
$ScriptName = $ScriptName.Replace(".ps1","")
# Задаем полный путь log-файла
$LogPath = $ScriptPath + "\" + $ScriptName + ".log"

# Запрашиваем текущее точное время
$dt = get-date -Format "yyyy-MM-d_HH-mm-ss"
# Задаем путь csv-файла
$CsvPath = $ScriptPath + "\" + $ScriptName + " (" + $dt + `
")" + ".csv"

# Функция записи в лог передаваемой строки $str
Function Write-Log ($str) {
  # Запрашиваем текущее точное время
  $dt = get-date -Format "d.MM.yyyy HH:mm:ss"
  # Формируем строку с текущим временем
  $str = "[" + $dt + "]" + " " + $str
  # Пишем строку в лог
  $str | Out-File -FilePath $LogPath -Append -Encoding UTF8
}

# Если корневая папка не существует
if (!(Test-Path $RootPath)) {
  # Выводим на экран сообщение с ошибкой
  Write-Host
  Write-Host "Указан неверный путь к папке!"
  Write-Host
  # Записываем сообщение с ошибкой в лог
  Write-Log "Указан неверный путь к папке!"
  # И прерываем скрипт
  exit
}

# Запрашиваем атрибуты корневой папки
$dir = Get-Item $RootPath | select FullName, CreationTime, `
LastAccessTime, LastWriteTime
# Запрашиваем списки доступа корневой папки
$acls = Get-Acl $RootPath | select -expand access | `
where {$_.IdentityReference -notmatch "BUILTIN|NT AUTHORITY|`
EVERYONE|CREATOR OWNER|ВЛАДЕЛЕЦ"}

# Создаем новый PSObject, "суммирующий" свойства $dir и $acl
$Obj = new-object PSObject
$Obj | add-member -membertype NoteProperty -name `
"FullName" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"CreationTime" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"LastAccessTime" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"LastWriteTime" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"FileSystemRights" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"AccessControlType" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"IdentityReference" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"IsInherited" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"InheritanceFlags" -Value ""
$Obj | add-member -membertype NoteProperty -name `
"PropagationFlags" -Value ""

# Функция "склеивания" свойств объектов папки $dir и
# списков доступа к папке $acl в новом объекте PSObject - $Obj
Function Init-Obj ($dir, $acl) {
  # Задаем свойства $Obj значениями свойств $dir
  $Obj.FullName = $dir.FullName
  $Obj.CreationTime = $dir.CreationTime.ToString()
  $Obj.LastAccessTime = $dir.LastAccessTime.ToString()
  $Obj.LastWriteTime = $dir.LastWriteTime.ToString()
  # Задаем свойства $Obj значениями списка доступа $acl
  $Obj.FileSystemRights = $acl.FileSystemRights
  $Obj.AccessControlType = $acl.AccessControlType
  $Obj.IdentityReference = $acl.IdentityReference.Value
  $Obj.IsInherited = $acl.IsInherited
  $Obj.InheritanceFlags = $acl.InheritanceFlags
  $Obj.PropagationFlags = $acl.PropagationFlags
}

# Перебираем все списки доступа корневой папки $dir
foreach ($acl in $acls) {
  # Задаем значения $Obj из свойств $dir и $acl
  Init-Obj $dir $acl
  # Записываем полученные значения в csv-файл
  $Obj | Export-csv $CsvPath -Append -Encoding utf8 `
  -Force -NoTypeInformation -Delimiter ";"
}

# Рекурсивная функция перебора дерева подкаталогов
# корневой папки $dir
Function Recur ($dir) {
  # Находим подкаталоги в корневой папке
  $childs = Get-ChildItem $dir.FullName -Directory
  # Перебираем подкаталоги
  foreach ($child in $childs) {
    # Поиск ненаследованных прав доступа папки $child
    $acls = Get-Acl $child.FullName | select -expand access | `
    where {($_.IdentityReference -notmatch "BUILTIN|NT AUTHORITY|`
    EVERYONE|CREATOR OWNER|ВЛАДЕЛЕЦ") -and ($_.IsInherited `
    -eq $false)}
    # Если есть ненаследованные права доступа
    if ($acls.count -ne 0) {
      # Перебираем списки доступа к папке $child
      foreach ($acl in $acls) {
        # Формируем $Obj из свойств $child и $acl
        Init-Obj $child $acl
        # Записываем полученные значения в csv-файл
        $Obj | Export-csv $CsvPath -Append -Encoding utf8 -Force `
        -NoTypeInformation -Delimiter ";"
      }
    }
    # Если у подкаталога есть дочерние директории
    if ($child.GetDirectories().Count -ne 0) {
      # Падаем в рекурсию, перебирая дочерние директории
      Recur $child
    }
  }
}

# Перебор дерева подкаталогов корневой папки $dir
Recur $dir

 

скрипт формирования отчета по правам доступа — часть 1: 2 комментария

  1. Класный скрипт..
    Не подскажешь как в твой скрипт добавить фичу, чтобы напротив группы безопасности выводились пользователи в этой группе?
    По примеру вот этого скрипта:

    $share = «\\shd\z$\Сканы»
    (Get-ACL $share).Access | Foreach {
    $acl = $_
    $id = $acl.IdentityReference.Value.split(«\»)[1]
    try {
    $obj = Get-ADObject -Filter «samaccountname -eq ‘$id'»
    if(!$obj) {
    $obj = [pscustomobject]@{Name = $acl.IdentityReference.Value}
    }

    [pscustomobject]@{
    Name = $obj.Name
    IdentityReference = $acl.IdentityReference
    AccessControlType = $acl.AccessControlType
    FilesystemRights = $acl.FilesystemRights
    }
    if($obj.ObjectClass -eq «group») {
    Get-ADGroupMember $obj -Recursive | Foreach {
    [pscustomobject]@{
    Name = $_.Name
    IdentityReference = $acl.IdentityReference
    AccessControlType = $acl.AccessControlType
    FilesystemRights = $acl.FilesystemRights
    }
    }
    }
    }
    catch {
    [pscustomobject]@{
    Name = $acl.IdentityReference.Value
    IdentityReference = $acl.IdentityReference
    AccessControlType = $acl.AccessControlType
    FilesystemRights = $acl.FilesystemRights
    }
    }
    } | Export-Csv -Encoding UTF8 -Path «C:\Users\Rock\Documents\FolderReport3.csv» -Delimiter «;» -NoTypeInformation

    1. Роман, спасибо за комментарий! К сожалению, пока нет времени развивать тему дальше. Но я учту пожелания и в ближайшем будущем, надеюсь, доработаю скрипт.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *