1

Тема: JScript: Определить доступность папки - как?

Подробности (причины и "степень" недоступности) не слишком интересны. Интересует, в какие из папок (ну, например, в корне C:\) нельзя зайти двойным щелчком в проводнике. У меня это "MSOCache" (У вас нет разрешений на доступ к этой папке.), "Recovery" и "System Volume Information" (Расположение недоступно. Отказано в доступе.). Ничего умнее попытки создания файла в каждой папке не придумал, но в этом случае папка "Program Files", например, тоже считается недоступной, а зайти в неё можно. Т.е. интересует доступность "на чтение", возможность записи неинтересна. Свойства объектов Folder для FSO и ShellApplication изучил, ничего подходящего не обнаружил.

2

Re: JScript: Определить доступность папки - как?

Если вопрос ставится как "можно ли войти в папку?", то логичнее не пробовать создать в ней файл, а попробовать получить список файлов из данной папки.


fso = new ActiveXObject("Scripting.FileSystemObject");
fld = fso.GetFolder('c:\\TMP\\2');
fc = fld.Files.Count;

Для пустой, но доступной папки fc будет присвоено значение 0. В случае если в папку не попасть(отсутствие прав чтения, или запрет чтения) третья строка даст RuntimeError "Разрешение отклонено".

3

Re: JScript: Определить доступность папки - как?

BeS Yara, огромное спасибо. Жаль, сам не додумался.

Serge Yolkin пишет:

... возможность записи неинтересна ...

оказывается, всё же интересна: нет ли способа проверить возможность записи в папку, не создавая там объектов, которые придётся удалять?

4

Re: JScript: Определить доступность папки - как?


strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colFolders = objWMIService. _
    ExecQuery("Select * from Win32_Directory where name = 'c:\\Scripts'")
For Each objFolder in colFolders
WScript.Echo "Writeable: " & objFolder.Writeable
Next

5

Re: JScript: Определить доступность папки - как?

JSman, спасибо, но результат несколько неожиданный:
1. для папок MSOCache, Program Files и Users - ложь, как и должно быть;
2. для папок Recovery и System Volume Information - истина, ну что ж, может, они и райтбл, но не для всякого юзера;
3. для $RECYCLE.BIN - истина ??? но там же файл не создаётся...

6

Re: JScript: Определить доступность папки - как?

Serge Yolkin

...ну что ж, может, они и райтбл, но не для всякого юзера...

Судя по коду, то работать оно будет только с правами администратора, если конечно последний не открыл доступ к объектам Windows простому пользователю (что равносильно созданию системной черной дыры).
Не проще ли использовать что-то вроде accesschk\cacls\xcacls?

7

Re: JScript: Определить доступность папки - как?

greg zakharov, правильно ли я понял, что для cacls.exe надо парсить вывод на предмет наличия FILE_WRITE_DATA в специальном доступе юзера?

8

Re: JScript: Определить доступность папки - как?

Serge Yolkin пишет:

...правильно ли я понял, что для cacls.exe надо парсить вывод...

Какбэ... не совсем понял что имеется в виду. Если речь о переоформлении вывода cacls, то да, в остальном вывода cacls достаточно, чтобы понять кто имеет доступ к папке\файлу, а кто нет.

9

Re: JScript: Определить доступность папки - как?

Имеется ввиду автоматизация. Результат нужен не оператору, а скрипту. Перед выполнением (или невыполнением) скрипта (js) проверить папку, переданную скрипту в качестве параметра. По всей видимости, надо вызывать cacls и парсить StdOut. Попытки создания (с немедленным удалением) файлов - по многим причинам не устраивают. Ищу (действительно - ищу, не только жду подсказки в этом топике) "мягкий" способ. Для файлов всё просто: пытаемся открыть на дописывание (fso.openTextFile, IOmode=8), а для папок пока не нашёл.

10

Re: JScript: Определить доступность папки - как?

Раз с JScript плавно переключились на VBScript...
Можно взять за основу Сценарий 2. Получение подробного (полного или частичного) списка управления доступом NTFS (DACL) указанного каталога текущего компьютера..
Дальше получаем имя пользователя и список групп в которых он состоит, и смотрим что разрешено/запрещено делать с папкой.

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


option explicit
Dim Path
Path = "c:\Windows"

немного помогло и это: https://forum.sysadmins.su/index.php?showtopic=111#entry4624
Dim oNet, oUser, UserName, UserGroup
Set oNet = CreateObject("WScript.Network")
Set oUser = GetObject("WinNT://" & oNet.UserDomain & "/" & oNet.UserName & ",user")
UserName = oNet.UserName

Dim DicUsrGrp
Set DicUsrGrp = CreateObject("Scripting.Dictionary")
DicUsrGrp.CompareMode = vbTextCompare
DicUsrGrp.Add oNet.UserName, "UserName"

Dim oGroup
For Each oGroup In oUser.Groups
  DicUsrGrp.Add oGroup.Name, "UserGroup"
Next

Dim a, i
a = DicUsrGrp.Keys
For i = 0 to UBound(a)
  wscript.echo a(i)
  ChkUserRights Path, a(i)
Next

Sub ChkUserRights(Folder, UserOrGroup)
  ' http://forum.script-coding.com/viewtopic.php?id=5142
  Dim objShell, objFolder, strPath
  Dim objWsNet, strDomain, strComputer, blnIsDomain, intOSVersion
  Dim objWMI, objCollection, objItem, objSecSettings, objSD
  Dim strAccount, strSID, strList
  Dim intHasAccount 'Флаг-признак режима работы:
                    '-1 - не составлять список, т.к. указанная "учётка" не найдена;
                    '0  - составлять полный список;
                    '1  - составлять частичный список (только для указанной "учётки").
  intHasAccount = 1

  '>>>> тут задаётся имя папки
  strPath = Folder
  '>>>> тут задаётся учётка
  strAccount = UserOrGroup

  Set objWsNet = CreateObject("WScript.Network")
  strComputer = objWsNet.ComputerName
  Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
  Set objSecSettings = objWMI.Get("Win32_LogicalFileSecuritySetting.Path='" & strPath & "'")

  Set objCollection = objWMI.ExecQuery("SELECT SID FROM Win32_Account WHERE Name='" & strAccount & "'")
  '--- Определение SID заданной "учётки"
  For Each objItem In objCollection
    strSID = UCase(objItem.SID)
  Next

  If objSecSettings.GetSecurityDescriptor(objSD) = 0 Then 'Чтение содержимого дескриптора безопасности каталога
      strList = vbNullString
      If Not IsNull(objSD.DACL) Then 'Проверка наличия хотя бы одной записи в DACL каталога
          Call Get_DACLInfo(objSD.DACL, strList, intHasAccount, strSID, intOSVersion)
          If Len(strList) > 0 Then
              WScript.Echo strList
          Else
              WScript.Echo "В DACL не обнаружено ни одной записи для объекта " & UCase(strDomain & "\" & strAccount)
          End If
      Else
          WScript.Echo "Список управления доступом к каталогу " & UCase(strPath) & " пуст."
      End If
  Else
      WScript.Echo "Не удалось прочитать дескриптор безопасности каталога " & UCase(strPath)
  End If

  Set objSecSettings = Nothing
  Set objCollection = Nothing
  Set objWMI = Nothing
  Set objFolder = Nothing
  Set objShell = Nothing
End Sub
'======

Function Get_DACLInfo(arrACE(), strRes, intMode, strAccSID, intVer)
Dim objEntry, strTemp, i, j, lngMask, lngTemp
Dim arrFlagValue, arrFlagName, arrGenericValue, arrGenericName
Dim arrSieveGE, arrSieveGW, arrSieveGR, arrTemp
Const PART_MODE = 1 'Флаг-признак составления частичного списка
'--- Значения универсальных масок
Const GENERIC_ALL = &H10000000
Const GENERIC_EXECUTE = &H20000000
Const GENERIC_WRITE = &H40000000
Const GENERIC_READ = &H80000000
'------
Const ACCESS_ALLOWED_ACE_TYPE = 0 'Флаг-признак записи типа "РАЗРЕШЕНИЕ"
Const ACCESS_DENIED_ACE_TYPE  = 1 'Флаг-признак записи типа "ЗАПРЕТ"
Const INHERITED_ACE = 16 'Флаг-признак того, что текущая запись DACL унаследована от родительского каталога
Const FULL_ACCESS = 983551 'Значение маски полного разрешения или запрета
Const FLAG_SYNCHRONIZE = 1048576 'Значение флага синхронизации доступа к объекту файловой системы
                                 '(в версиях ОС "2000/XP", применим только для записей типа "РАЗРЕШЕНИЕ")

'arrFlagValue = Array(&H20, &H1, &H80, &H8, &H2, &H4, &H100, &H10, &H40, &H10000, &H20000, &H40000, &H80000)
arrFlagValue = Array(32, 1, 128, 8, 2, 4, 256, 16, 64, 65536, 131072, 262144, 524288)
arrFlagName = Array("Траверс папок / Выполнение файлов", _
                    "Содержание папки / Чтение данных", _
                    "Чтение атрибутов", _
                    "Чтение дополнительных атрибутов", _
                    "Создание файлов / Запись данных", _
                    "Создание папок / Дозапись данных", _
                    "Запись атрибутов", _
                    "Запись дополнительных атрибутов", _
                    "Удаление подпапок и файлов", _
                    "Удаление", _
                    "Чтение разрешений", _
                    "Смена разрешений", _
                    "Смена владельца")

arrGenericValue = Array(&H20000000, &H40000000, &H80000000)
'arrGenericValue = Array(536870912, 1073741824, 2147483648)
arrGenericName = Array("Выполнение (универсальная маска)", "Запись (универсальная маска)", "Чтение (универсальная маска)")
'--- Вспомогательные массивы, предназначенные для детализации универсальных масок
arrSieveGE = Array(-1, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0)
arrSieveGW = Array(0, 0, 0, 0, -1, -1, -1, -1, 0, 0, -1, 0, 0)
arrSieveGR = Array(0, -1, -1, -1, 0, 0, 0, 0, 0, 0, -1, 0, 0)
'------

'--- Настройка правильного наименования одного из флагов маски доступа в зависимости от версии ОС
'If intVer < 60 Then
'    arrFlagName(0) = "Обзор папок / Выполнение файлов"
'End If
'------
For Each objEntry In arrACE
    '--- Определение режима наследования записи и области её действия
    If CBool(objEntry.AceFlags And INHERITED_ACE) Then
        strTemp = " (унаследовано; "
        lngTemp = objEntry.AceFlags - INHERITED_ACE
    Else
        strTemp = " (не унаследовано; "
        lngTemp = objEntry.AceFlags
    End If
    Select Case lngTemp
        Case 0: strTemp = strTemp & "действует на: только текущий каталог)"
        Case 1: strTemp = strTemp & "действует на: текущий каталог и его файлы)"
        Case 2: strTemp = strTemp & "действует на: текущий каталог и его подкаталоги)"
        Case 3: strTemp = strTemp & "действует на: текущий каталог, его подкаталоги и файлы)"
        Case 9: strTemp = strTemp & "действует на: только файлы текущего каталога)"
        Case 10: strTemp = strTemp & "действует на: только подкаталоги текущего каталога)"
        Case 11: strTemp = strTemp & "действует на: подкаталоги и файлы текущего каталога)"
        Case Else: strTemp = strTemp & "область действия не определена); "
    End Select
    strTemp = strTemp & vbNewLine & "---" & vbNewLine
    '------
    '--- Определение типа записи
    If objEntry.AceType = ACCESS_ALLOWED_ACE_TYPE Then
        strTemp = strTemp & "РАЗРЕШЕНО:" & vbNewLine
    ElseIf objEntry.AceType = ACCESS_DENIED_ACE_TYPE Then
        strTemp = strTemp & "ЗАПРЕЩЕНО:" & vbNewLine
    End If
    '------
    '--- Определение значения маски "Полный доступ" в зависимости от версии ОС
    If intVer < 52 Then
        lngTemp = FULL_ACCESS + Abs(objEntry.AceType - 1) * FLAG_SYNCHRONIZE
        'Выражение FULL_ACCESS + Abs(objEntry.AceType - 1) * FLAG_SYNCHRONIZE
        'учитывает разницу между значениями маски "Полный доступ" у записей разных типов
        'в ОС версий "2000/XP"
    Else
        lngTemp = FULL_ACCESS + FLAG_SYNCHRONIZE
    End If
    '------
    lngMask = objEntry.AccessMask
    Select Case Abs(lngMask)
        Case lngTemp: strTemp = strTemp & "Полный доступ" & vbNewLine
        Case GENERIC_ALL: strTemp = strTemp & "Полный доступ (универсальная маска)" & vbNewLine
        Case Else
            '--- Детальный анализ маски доступа текущей записи:
            'обработка универсальных масок (биты №№ 29 - 31)
            If Abs(lngMask) > lngTemp Then
                For i = 0 To UBound(arrGenericValue)
                    If lngMask And arrGenericValue(i) Then
                        strTemp = strTemp & arrGenericName(i) & vbNewLine & vbTab & "{" & vbNewLine
                        Select Case arrGenericValue(i)
                            Case GENERIC_EXECUTE: arrTemp = arrSieveGE
                            Case GENERIC_WRITE: arrTemp = arrSieveGW
                            Case GENERIC_READ: arrTemp = arrSieveGR
                        End Select
                        For j = 0 To UBound(arrTemp)
                            If arrTemp(j) Then strTemp = strTemp & vbTab & arrFlagName(j) & vbNewLine
                        Next
                        strTemp = strTemp & vbTab & "}" & vbNewLine
                    End If
                Next
            End If
            'обработка обычных масок (биты №№ 0 - 20)
            For i = 0 To UBound(arrFlagValue)
                If lngMask And arrFlagValue(i) Then
                    strTemp = strTemp & arrFlagName(i) & vbNewLine
                End If
            Next
            '------
    End Select
    strTemp = UCase(objEntry.Trustee.Domain & "\" & objEntry.Trustee.Name) & strTemp & "===" & vbNewLine
    If intMode = PART_MODE Then
        If StrComp(UCase(objEntry.Trustee.SIDString), strAccSID, vbTextCompare) = 0 Then
            strRes = strRes & strTemp
        End If
    Else
        strRes = strRes & strTemp
    End If
Next
End Function

В принципе, нужно просто разобраться с флагами, обрабатываемыми при выводе разрешений, и заменить формирование отчёта на анализ доступности папки на чтение/запись для пользователя/групп. Следует иметь ввиду, что запрещающая политика имеет приоритет. Поэтому при наличии полного доступа для группы Users вместе с наличием явного запрета доступа для пользователя User(члена Users) будет обозначать именно отсутствие доступа у User к данной папке.

Упомянутый в обсуждении xcacls - штука интересная уже тем, что это не программа, а скрипт на VBS(Использование сценария Xcacls.vbs для изменения разрешений NTFS) . Так что работу с ACL можно поизучать копаясь в этом скрипте. Можно попробовать допилить его под свои нужды. Но мне кажется, что для описанной задачи гораздо проще плясать от сценария из коллекции. Как срастить с JS, это второй вопрос. Можно просто запускать его как внешнюю программу, получая назад код выхода(0 - запись разрешена, 666 - запись невозможна).

Serge Yolkin пишет:

Результат нужен не оператору, а скрипту. Перед выполнением (или невыполнением) скрипта (js) проверить папку, переданную скрипту в качестве параметра.

Может тогда такие сложности лишние? Если для работы скрипта требуется создать там файл, то чем не подходит вариант с попыткой создания временного или окончательного файла с отслеживанием ошибок? "По науке" конечно всяко приятнее делать, но если по науке три+ экрана кода, а костылём несколько строк(при том, что через WMI явно не быстрее будет по скорости)... Тут я за KISS.

11 (изменено: Serge Yolkin, 2013-11-13 12:08:57)

Re: JScript: Определить доступность папки - как?

BeS Yara пишет:

если по науке три+ экрана кода, а костылём несколько строк

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

Да, за пример и ссылку на xcacls - спасибо.