none
Windows7 のフォームアプリからNetShareGetInfo()の使用について RRS feed

  • 質問

  • VS2008 VB.NET .net 3.5

     

    NetShareGetInfo() APIを使用して、リモートコンピュータの共有フォルダの情報を取得しようとしています。

    当該機能を組み込んだメソッドをWindowsフォームアプリケーションから呼び出して使用しているのですが

     

    ADに参加しているWindows7 (Professional sp1)の端末数台でこのフォームアプリを実行したところ

    何台かでNetShareGetInfo() がERROR_ACCESS_DENIEDを返し値を取得できない現象に陥っています。

     

    条件的には、ADに参加している複数のWindows7 Proの端末に同一アカウントでログインし

    対象のフォームアプリを動作させているのですが、NetShareGetInfo()の結果が成功と失敗の両方のパターンがあるようです。

     

    共有フォルダはWindows Server 2008(ServerはADに参加)上に作成してあります。

     

    同じアプリケーションを同一アカウント下で実行しているため、違いはそれぞれのWin7コンピュータの違いのみ

    だと思いますが、どこに問題があるのかいっこうに見当が付きません

    Win7以降はセキュリティ的にいろいろ強化されていることが影響してるのでしょうか?

     

    ちなみに、共有フォルダのあるWin2k8サーバ側のセキュリティイベントビューアには、特に失敗の監査も無く

    普通に成功の監査が残るだけなのですが..

     

     

     

    2011年9月30日 12:40

すべての返信

  • >同じアプリケーションを同一アカウント下で実行しているため、違いはそれぞれのWin7コンピュータの違いのみ
    >だと思いますが、どこに問題があるのかいっこうに見当が付きません

    それぞれの Win7 コンピュータで起動直後&ログオン直後に、問題の共有フォルダをエクスプローラのアドレスバーに UNC 直打ちでアクセスした場合、どうなるでしょうか?

    同じ条件で、コマンドプロンプトから

    dir \\FileServer\SharedFolder

    のように UNC 指定で問題の共有フォルダのディレクトリエントリ表示を試みた場合どうなるでしょうか?

    >ちなみに、共有フォルダのあるWin2k8サーバ側のセキュリティイベントビューアには、特に失敗の監査も無く
    >普通に成功の監査が残るだけなのですが..

    ERROR_ACCESS_DENIED が返ってくる以上、それも変ですね。

    >共有フォルダはWindows Server 2008(ServerはADに参加)上に作成してあります。

    共有設定でのアクセス許可と、共有で公開しているフォルダのアクセス権はどんな感じでしょう?

    >ADに参加しているWindows7 (Professional sp1)の端末数台でこのフォームアプリを実行したところ

    Windows 7 各機の構成は似たり寄ったりですか? 導入時期とかにバラつきあったりするでしょうか?

     

     

    2011年10月1日 14:49
  • こんにちは、 渋木 様

    レスを付けていただきありがとうございます。

     

    > それぞれの Win7 コンピュータで起動直後&ログオン直後に、問題の共有フォルダをエクスプローラのアドレスバーに UNC 直打ちでアクセスした場合、どうなるでしょうか?

    > 同じ条件で、コマンドプロンプトから

    > dir \\FileServer\SharedFolder

    >のように UNC 指定で問題の共有フォルダのディレクトリエントリ表示を試みた場合どうなるでしょうか?

    こちらに関しては、初回の投稿には記述しておりませんでしたが、explorer 及び コマンドプロンプト共に アクセスは行えます。

     

    > >ちなみに、共有フォルダのあるWin2k8サーバ側のセキュリティイベントビューアには、特に失敗の監査も無く
    > >普通に成功の監査が残るだけなのですが..

    > ERROR_ACCESS_DENIED が返ってくる以上、それも変ですね。

    そうなんですよね、不思議なんです。

    念のために、もう一度クライアントのエラー出力のタイミング辺りの、Server側のイベントビュア(セキュリティ)を

    見なおしてみたのですが...

    クライアントのコンピュータアカウント・ドメインログインユーザからの共有リソースアクセスに関して(IPC$)も含み

    残っているのは成功の監査だけでした..

     

    > >共有フォルダはWindows Server 2008(ServerはADに参加)上に作成してあります。

    > 共有設定でのアクセス許可と、共有で公開しているフォルダのアクセス権はどんな感じでしょう?

    共有アクセス:

    ログインユーザを含む、AD上のグローバルセキュリティグループを共同所有者

    SYSTEMアカウントに共同所有者

    セキュリティ:

    ログインユーザを含む、AD上のグローバルセキュリティグループにフルアクセスコントロール

    SYSTEMアカウントにフルアクセスコントロール

    NETWORK SERVICEにフルアクセスコントロール

    ですね...

    (今は開発環境なのである意味ズブズブですが...^^;)

     

    購入時期や、メーカはまちまちですが、少なくともシステムプロパティから見えるWindows Editionや

    インストールされている.Net Frameworkのバージョン及びHOTFIXの適用状況などに

    差異は無いように見えます。

     

     

     

    2011年10月3日 9:49
  • 直接の回答ではありませんが、問題のない範囲でソースを公開してはいかがでしょうか。
    何かのヒントになるかもしれませんので。
    2011年10月3日 10:31
  • 確かにお聞きする限り、情報として必要かもしれませんね

    ほぼそのまま転記します。

    若干、恥ずかしいコーディングの部分のところはありますが、そこはあえてご指摘されませんよう^^;

    当然Try~Catchでは括って処理もしています

    #szDriveName:UNC形式指定のパスを格納している変数
    #clsDrive.mEnPath()は単に指定されたパスの最後尾にディレクトリセパレータがない場合付加するといったメソッドです。
    #Win32.NetShareGetInfo()はDllImportで宣言したAPI関数です。
    #Commons.Win32.SHARE_INFO_502は自前でStructure定義をおこなっている、SHARE_INFO_502構造体です。


    Dim r As System.Text.RegularExpressions.Regex
    Dim rtc As System.Int32
    Dim driveLeter As System.String = System.String.Empty

    'UNCパスからホスト名を取出し、NetShareGetInfo() APIに渡す型に変換
    r = New System.Text.RegularExpressions.Regex("(?<serverName>\\\\.+?)?[\\\/]", System.Text.RegularExpressions.RegexOptions.Singleline)
    serverName = r.Match(clsDrive.mEnPath(szDriveName)).Groups("serverName").Value
    ReDim serverByteName(serverName.Length)
    serverName.CopyTo(0, serverByteName, 0, serverName.Length)

    sbMsg.AppendFormat("  serverName:[{0}]", serverName).AppendLine("")

    'UNCパスから共有名を取出し、NetShareGetInfo() APIに渡す型に変換
    r = New System.Text.RegularExpressions.Regex("\\\\.+?[\\\/](?<shareName>.+?)?[\\\/]", System.Text.RegularExpressions.RegexOptions.Singleline)
    shareName = r.Match(clsDrive.mEnPath(szDriveName)).Groups("shareName").Value
    ReDim shareByteName(shareName.Length)
    shareName.CopyTo(0, shareByteName, 0, shareName.Length)

    sbMsg.AppendFormat("  shareName:[{0}]", shareName).AppendLine("")

    'NetShareGetInfo() APIの結果格納領域の準備
    Dim pCurrent As Commons.Win32.SHARE_INFO_502
    Dim pBuffer As New IntPtr(System.Runtime.InteropServices.Marshal.SizeOf(GetType(Commons.Win32.SHARE_INFO_502)))


    'NetShareGetInfo() API実行(UNCパスからリモートホストの物理パス情報取得)
    rtc = Win32.NetShareGetInfo(serverByteName, shareByteName, 502, pBuffer)  ← ここでrtcに5が返る。

    sbMsg.AppendFormat("  Win32.NetShareGetInfo() 戻り値:[{0}]", rtc.ToString).AppendLine("")     


    以下、rtcが0ならば、取得できたpBufferを使用し共有フォルダの物理パスからドライブの容量(全体、空きなど)を取得しています。


    と、こんな感じです。

    しかし、場合によってはこのコードで、まがいなりにも期待した結果も取得できていますしね...

    謎ですね

     

    2011年10月3日 12:20
  • #Win32.NetShareGetInfo()はDllImportで宣言したAPI関数です。
    #Commons.Win32.SHARE_INFO_502は自前でStructure定義をおこなっている、SHARE_INFO_502構造体です。

    この2つは今回の現象の肝になるかもしれませんので、こちらも添付してはいかがでしょうか。
    2011年10月3日 15:59
  • 仰せのとおり....

    個人的には、大きな誤りはないと思っているのですが...

    添付させていただきます^^;

    ※ Win32.NetShareGetInfo()

        <System.Runtime.InteropServices.DllImport("netapi32.dll", CharSet:=Runtime.InteropServices.CharSet.Auto, ExactSpelling:=False)> _
        Function NetShareGetInfo(ByVal servername As System.String, ByVal netname As System.String, ByVal level As System.Int32, ByRef bufPtr As System.IntPtr) As System.Int32
        End Function

     

    ※ Commons.Win32.SHARE_INFO_502

        <System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)> _
        Structure SHARE_INFO_502
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Dim shi502_netname As System.String
            Dim shi502_type As Integer
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Dim shi502_remark As System.String
            Dim shi502_permissions As System.Int32
            Dim shi502_max_uses As System.Int32
            Dim shi502_current_uses As System.Int32
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Dim shi502_path As System.String
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Dim shi502_passwd As System.String
            Dim shi502_reserved As System.Int32
            Dim shi502_security_descriptor As System.IntPtr
        End Structure

     

    こんな感じです。

     

    2011年10月4日 2:42
  • ※ Win32.NetShareGetInfo()

        <System.Runtime.InteropServices.DllImport("netapi32.dll", CharSet:=Runtime.InteropServices.CharSet.Auto, ExactSpelling:=False)> _
        Function NetShareGetInfo(ByVal servername As System.String, ByVal netname As System.String, ByVal level As System.Int32, ByRef bufPtr As System.IntPtr) As System.Int32
        End Function


    この部分、VB だとこのような定義で大丈夫なのでしょうか。
    C# だと次のように文字列の引数は LPWStr にマーシャリングすると思うのですが。

    [DllImport("Netapi32.dll", SetLastError=true)]
    public static extern int NetShareGetInfo(
        [MarshalAs(UnmanagedType.LPWStr)] string serverName,
        [MarshalAs(UnmanagedType.LPWStr)] string netName,
        Int32 level,
        out IntPtr bufPtr );
    


    今一つ確証が持てないのですが、既定の BStr にマーシャリングされてしまって、文字列が null で終わっていないということはありませんか?

    よくよく見たら、serverByteName と shareByteName は null で終わっていないように思えます。
    • 編集済み totojo 2011年10月4日 3:17
    2011年10月4日 3:03
  • DllImportや構造体定義 の指定個所の掲載時に漏れていましたが

    APIに渡すパラメータ変数の宣言個所の掲載をしていませんでした

       <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Private Shared serverName As System.String
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Private Shared shareName As System.String
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Private Shared serverByteName(1) As System.Char
            <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
            Private Shared shareByteName(1) As System.Char

    以上のように、一応アンマネージデータへのマーシャリングを意識しているつもりでした

    変数の最後がNull(VB.NET)ではNothingのことをいわれているのだと思いますが

    String型の文字列を、文字列数でリサイズしたChar型の配列に詰めなおして

    それを指定しているわけですが、これではNullで終わる文字列のポインタとしては

    不完全なのでしょうか?

    もしかして配列の最後にChr(0)を明示的に追加するということなのでしょうか?

     

    Chr(0)を明示的に最後に入れる必要がないのであれば、前回提示したコードの中で

    String型 → Char型配列 ⇒ NetShareGetInfo()のパラメータに指定という風に

    コーディングしていた個所は、String型クラスのToArrayメソッドを使って直接Char型配列のポインタを渡すというのと同等?かとも思うのですが、これは誤りでしょうか?

    ReDim serverByteName(serverName.Length)
    serverName.CopyTo(0, serverByteName, 0, serverName.Length)

    rtc = Win32.NetShareGetInfo(serverByteName, shareByteName, 502, pBuffer)

    rtc = Win32.NetShareGetInfo(serverName.ToArray, shareName.ToArray, 502, pBuffer)

     

    2011年10月4日 6:26
  • // 元質問の回答は持ち合わせていませんが……。

    あれ、DllImport の方では第一・第二引数が String になってますが、実際の呼び出しは配列ですか?

    LPC(|W|T)STR のパラメータに対しては、よほどのことがない限り System.String を使用します。マーシャリングされる際自動的に終端文字が付けられます。

    // プラットフォーム呼び出し(DllImport)での文字列の既定のマーシャリングは LPCSTR/LPCWSTR(DllImport の CharSet で区別)なので、この場合パラメータに属性を指定する必要も特にありません。

    気になるとすれば、第一引数・第二引数ともに非 const の LPWSTR で宣言されている点でしょうか(呼び出し先が変更を加えることがあるとも思えませんが)。一応、String の代わりに StringBuilder を使った方が良いかもしれません。

    いずれにせよ、配列を使う必要はありません。あとわざわざ非ローカル変数にする意味もありません。

    それから動作上特に意味は生じませんが、気になった点がひとつ。

    NetShareGetInfo の第四引数に渡す IntPtr をなんか Marshal.SizeOf で初期化していますが、意図が不明です。IntPtr はただの数値であり、IntPtr を何らかの数値で New したからってその分のメモリが確保されるわけではありませんし、メモリ確保は NetShareGetInfo 側の仕事です。この場合、初期化しないか、するにしても IntPtr.Zero で十分です。

    2011年10月4日 7:02
  • HongLiang様

    いろいろご指摘感謝します。

    該当箇所は、鋭意改修することにします。(無駄なと言うか冗長な処理は排除するべきですよね)

     

    ただ、やはり本題が解決していないのが悲しいです(T-T)

     

    2011年10月4日 7:15
  • // プラットフォーム呼び出し(DllImport)での文字列の既定のマーシャリングは LPCSTR/LPCWSTR(DllImport の CharSet で区別)なので、この場合パラメータに属性を指定する必要も特にありません。


    ここはパラメーターに属性を指定すべきなのではないかと、私は思います。
    NetShareGetInfo の引数は LPTSTR でなくて LPWSTR であるからです。
    (プラットフォームは Windows 7 なので、結果的に UNICODE になるんでしょうけれども。)
    あるいは、DllImport の CharSet を Unicode にしてしまうとか。
    2011年10月4日 9:08