VBScriptで、フォルダのアクセス権におけるユーザーの継承元を調べる方法を教えてください。

プロパティのセキュリティから詳細設定を開いたところに表示されるユーザーの「継承元」の部分です。

フォルダを指定すると、そこに許可されているユーザー名と継承元が分かるようなものだと嬉しいです。

一応、先の質問の関連です。
http://q.hatena.ne.jp/1210746433

回答の条件
  • 1人10回まで
  • 登録:
  • 終了:2008/05/20 12:43:21
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:ardarim No.1

回答回数897ベストアンサー獲得回数145

ポイント100pt

作ってみました。


親オブジェクトから継承されていない(SE_DACL_PROTECTEDがセットされていない)フォルダに到達するまで1つずつフォルダをたどっていきます。

継承元のフォルダに辿り着いたら、GetSecurityDescriptorメソッドでユーザー名などの詳細を取得します。

Const SE_DACL_PROTECTED = &h1000

On Error Resume Next

If WScript.Arguments.Count = 0 Then
    WScript.Echo "引数にパスを指定してください"
    WScript.Quit
End If
TargetPath = WScript.Arguments.Item(0)

Set fso = CreateObject("Scripting.FileSystemObject")

TargetPath = fso.GetFolder(TargetPath).Path
WScript.Echo "対象パス: " & TargetPath

CurrentPath = ""
Do
    If CurrentPath = "" Then
        CurrentPath = TargetPath
    Else
        Err = 0
        ' 1つ上のフォルダを取得
        CurrentPath = fso.GetFolder(CurrentPath).ParentFolder
        If Err <> 0 Then
            ' ルートに到達
            Exit Do
        End If
    End If

    Set wmiFileSecSetting = GetObject( _
        "winmgmts:Win32_LogicalFileSecuritySetting.path='" & _
        CurrentPath & "'")

Loop Until (wmiFileSecSetting.ControlFlags And SE_DACL_PROTECTED) <> 0

WScript.Echo "継承元パス: " & CurrentPath

Err = 0
RetVal = wmiFileSecSetting.GetSecurityDescriptor(wmiSecurityDescriptor)
If Err <> 0 Then
    WScript.Echo "GetSecurityDescriptor 失敗" & _
        vbCrLf & Err.Number & vbCrLf & Err.Description
    WScript.Quit
End If

WScript.Echo
WScript.Echo "アクセス制御リスト:"

For Each wmiAce in wmiSecurityDescriptor.DACL
    WScript.Echo "    "    & wmiAce.Trustee.Name
Next

参考:

Win32_LogicalFileSecuritySetting Class (Windows)

GetSecurityDescriptor Method of the Win32_LogicalFileSecuritySetting Class (Windows)

id:harrypotter

回答ありがとうございます!

スクリプトまで書いていただきすいません。


確認してみました。

継承元を追えるのでかなり便利なのですが、継承元が複数ある時にも一つのフォルダしか出ません。


たとえば、各ユーザの継承元が

userA→C:\hoge

userB→C:\hoge

userC→C:\hoge\hogehoge

となった状態で子に継承されている場合に、c:\hogeは表示されるのですが、c:\hoge\hogehogeは出ないといった具合です。


今回のチェックの目的は、継承元を拾いたいというのもあるのですが、それよりも、継承のないユーザがいるフォルダを見つけたいということがあります。


たとえば

c:\hoge 以下のサブフォルダにアクセス権の継承をしていている状態で、

c:\hoge\hoge2\hoge3 に継承なしのユーザが追加されている場合、その\hoge3フォルダを見つけたいのです。


質問の内容とは少し違ったというものの、このスクリプトは大変参考になりそうです。

ほんとにありがとうございます。


WMIやVBSに関しては全く無知ですので、コメント欄もとても参考になります。

ポイントを差し上げたいのでぜひ回答欄にも書いていただけたらと思います。


引き続き回答お待ちしております。

2008/05/19 10:31:11
  • id:mj99
    メモ

    ----
    とても興味のある問題です。

    ----
    c:\test1\test2\test3
    のようなフォルダがあり、セキュリティを下記のとおり設定したとする、

    \test1 → 継承なし、User_Aを追加、フルコントロールを設定
    \test2 → 継承あり、User_Bを追加、フルコントロールを設定
    \test3 → 継承あり、User_Cを追加、フルコントロールを設定

    この場合は、test3にはUser_A、User_B、User_Cにフルコントロールがあるが、
    継承元はすべて異なる。

    セキュリティ「継承なし」のフォルダが「継承元」であるとは限らない。

    ----
    フォルダ(ファイル)セキュリティが複数の継承元をもつ場合、「どの権限が」「どこから継承されたか」を判別する方法が見つからない。

    ----
    以下の手順でWMIで得られるフォルダ(ファイル)セキュリティの情報を確認できる。

    ・wbemtest.exe起動
    ・接続をクリック
     →接続先に下記を入力して、接続をクリック
      root\cimv2
    ・メソッドの実行をクリック
     →オブジェクトパスに下記を入力して、OKをクリック
      Win32_LogicalFileSecuritySetting.path="C:\\test1\\test2\\test3"
    ・実行をクリック
    ・Outパラメータの編集をクリック
    ・(画面中段のリストの)Descriptorをダブルクリック
    ・埋め込みを表示をクリック
    ・MOFの表示をクリック

    ----
    Win32_GetSecurityDescriptor::DACL配列(Win32_ACEクラス)には継承元の情報は載っていない。
    Win32_ACE::Trustee(Win32_Trusteeクラス)には継承元の情報は載っていない。
  • id:ardarim
    mj99さん、ご指摘ありがとうございます。
    確かにそういう状況はありえますね。ちょっと単純に考えすぎていたようです。

    上で回答したやり方をベースにWin32_ACE.AceFlagsにCONTAINER_INHERIT_ACEがセットされているかどうかも見ていけばACEごとの継承されている/されていないがわかりそうです。
    ただ個々のACEごとに辿っていかなければならないのでかなり手間はかかりそうです...
  • id:ardarim
    ちなみに...
    NTFSのフォーマット上もWMIで得られる以上の情報はSECURITY_DESCRIPTORには格納されていませんので、直接継承元を参照できるような情報はディスク上には存在しないということはいえると思います。

    非公式ですがNTFSフォーマット情報
    http://old.linux-ntfs.org/content/view/103/42/

    シェルのプロパティ表示ではやはり1つずつディレクトリを辿って情報を取得していると考えざるを得ないと思います。
  • id:harrypotter
    回答修正させていただきました。
    コメント欄もとても有難いです。ありがとうございます。
  • id:ardarim
    各ユーザごとに継承元を調べるように修正してみました。
    1ユーザーごとに調べるので効率は悪いです。もうちょっと効率よくできるはずですが概念を見る分にはわかりやすいかと思います。
    GetAncestorPath()は指定されたACEの継承元フォルダを取得するサブルーチンです。


    基本的には最初の回答と同じく、継承元を見つけるまで1つずつフォルダを上がっていきます。
    継承元は、Win32_ACE.AceFlagsにINHERITED_ACEがセットされていません。継承されているものにはすべてINHERITED_ACEがセットされます。
    ACE(Access Control Entry; アクセス許可エントリ)はユーザー名とアクセスマスク(許可フラグ)のセットで管理されますので、この2つが一致し、かつINHERITED_ACEがセットされていないものを継承元と認定します。


    >|
    Const SE_DACL_PROTECTED = &h1000
    Const INHERITED_ACE = &h10

    On Error Resume Next

    If WScript.Arguments.Count = 0 Then
    WScript.Echo "引数にパスを指定してください"
    WScript.Quit
    End If
    TargetPath = WScript.Arguments.Item(0)

    Set fso = CreateObject("Scripting.FileSystemObject")

    TargetPath = fso.GetFolder(TargetPath).Path
    WScript.Echo "対象パス: " & TargetPath

    Set wmiFileSecSetting = GetObject( _
    "winmgmts:Win32_LogicalFileSecuritySetting.path='" & _
    TargetPath & "'")

    Err = 0
    RetVal = wmiFileSecSetting.GetSecurityDescriptor(wmiSecurityDescriptor)
    If Err <> 0 Then
    WScript.Echo "GetSecurityDescriptor 失敗" & _
    vbCrLf & Err.Number & vbCrLf & Err.Description
    WScript.Quit
    End If

    WScript.Echo
    WScript.Echo "アクセス制御リスト:"

    For Each wmiAce in wmiSecurityDescriptor.DACL
    WScript.Echo " " & wmiAce.Trustee.Name & _
    " / " & Hex(wmiAce.AccessMask) & _
    " / " & GetAncestorPath(TargetPath, wmiAce)
    Next

    WScript.Quit

    Function GetAncestorPath(TargetPath, wmiAceInherited)

    Dim wmiAce

    TargetName = wmiAceInherited.Trustee.Name
    TargetMask = wmiAceInherited.AccessMask

    CurrentPath = ""
    Do
    If CurrentPath = "" Then
    CurrentPath = TargetPath
    Else
    Err = 0
    ' 1つ上のフォルダを取得
    CurrentPath = fso.GetFolder(CurrentPath).ParentFolder
    If Err <> 0 Then
    ' ルートに到達
    Exit Do
    End If
    End If

    Set wmiFileSecSetting = GetObject( _
    "winmgmts:Win32_LogicalFileSecuritySetting.path='" & _
    CurrentPath & "'")

    Err = 0
    RetVal = wmiFileSecSetting.GetSecurityDescriptor _
    (wmiSecurityDescriptor)
    If Err <> 0 Then
    WScript.Echo "GetSecurityDescriptor 失敗" & _
    vbCrLf & Err.Number & vbCrLf & Err.Description
    WScript.Quit
    End If

    For Each wmiAce in wmiSecurityDescriptor.DACL
    If TargetName = wmiAce.Trustee.Name Then
    If TargetMask = wmiAce.AccessMask Then
    If (wmiAce.AceFlags And INHERITED_ACE) = 0 Then
    ' 該当するユーザ名且つアクセスマスクが一致し、
    ' 且つ継承されていない(継承元の)ACEを発見
    Exit Do
    End If
    End If
    End If
    Next

    Loop

    GetAncestorPath = CurrentPath

    End Function
    |<


    なお継承なしのユーザは、上で説明したとおり、Win32_ACE.AceFlagsにINHERITED_ACEがセットされていないことで判別ができます。
  • id:harrypotter
    確認させていただきました。
    すばらしいです!ありがとうございます!

    かなり処理に時間かかるみたいですし、単純に継承元がないユーザだけを抜き出したほうが私の希望する処理をするにはいいみたいですね。

    ほんとにありがとうございます。
  • id:mj99
    ardarimさん、ナイスなスクリプトです。
    やっぱりACEをひとつづつ探って行く方法しかなさそうですね。

    ひとつだけ、、
    ACE=アクセス許可ではなくて、
    ACEには許可エントリと拒否エントリの2種があります。
    この2種を区別したほうがよいですね。

    ----
    とても有意義でした。ありがとうございました。
  • id:ardarim
    回答欄とコメント欄を間違えてしまったのでインデントがきれいさっぱりなくなってしまいました。見づらくってスミマセン。

    許可エントリと拒否エントリを区別するためにTrustee.NameとAccessTypeだけではなくて、AceTypeも入れて3つの組にして比較するのが厳密には正しかったですね。またしても片手落ちでした...
    If TargetName = wmiAce.Trustee.Name Then
      If TargetMask = wmiAce.AccessMask Then
        If wmiAceInherited.AceType = wmiAce.AceType Then '追加
          If (wmiAce.AceFlags And INHERITED_ACE) = 0 Then
            ' 該当するユーザ名且つアクセスマスクが一致し、
            ' 且つ継承されていない(継承元の)ACEを発見
            Exit Do
          End If
        End If ' 追加
      End If
    End If

    まあ、とはいっても通常はマスク値が全く同じ許可エントリと拒否エントリを同時に設定することは無いでしょうから(意味が無いので)、ほとんど問題にはならないと思います。

この質問への反応(ブックマークコメント)

トラックバック

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません