Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@int_main_void

ネットワークドライブのファイルパスを絶対パス(UNC)に変換して、クリップボードに出力する

More than 5 years have passed since last update.

概要

ネットワークドライブのファイルパスを絶対パス(UNC)に変換して、クリップボードに出力する話。
たとえば、Z:\testといったパスをそのまま他の人に連絡しても、アクセスできない。

毎度ネットワークドライブに設定しているパスを調べるのは面倒くさい。
勉強を兼ねてC#で取得する方法がないか調べ、自分の要望を満たすツールを作った。

結論

  • C#からWin32API WNetGetUniversalNameを呼び出して使ってUNC変換を行うことができた。
  • 「クリップボード操作」、「送る(SendTo)」と組み合わせることで右クリックでパスを取得することができた。
  • コンソール画面が出ると気になるので、何もフォームを作らないフォームアプリとした。

経緯

.NetFrameworkにメソッドがない?

「ネットワークドライブ UNC変換」で検索すると、VBScriptの例1とか、VBScriptの例2等結構出てくるが、C#(≒.NetFrameworkクラスライブラリ)の情報がない。

じゃんぬねっとさん(Microsoft MVP for C#)のコメント

> ネットワークドライブの絶対パスを取得するにはどうしたらよいでしょうか?
.NET Framework にはそういうメソッドはないのかなぁ...
Google で、小1時間調べてみたんですが見当たりませんでした...。

とのこと。ここでは、WNetGetConnectionを使う方法が示されており、質問者「C#動いた」そうです。

.NetFrameworkにメソッドがあった?

NativeMethods.WNetGetUniversalName メソッドというのを見つけたが、

この型は SecurityCriticalAttribute 属性を持っているため、使用は .NET Framework for Silverlight クラス ライブラリでの内部用に限定されます。アプリケーション コードでこの型の任意のメンバーを使用すると、MethodAccessException 例外がスローされます。

とわけのわからないことを言っているのでとりあえず除外。

その後

Win32API、UNC、C#でもう少し検索したところ、もう少しコードが詳細に示されている回答があったConvert path to UNC path - Stack Overflowので、これをベースとして利用することにした。こちらは、WNetGetUniversalNameを使用している。

コード

一通り動作していることが確認できた。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;//Marshalのために追加
using System.Text; //StringBuilerのために追加

namespace WindowsFormsApplication1
{
   static class Program
   {
       /// <summary>
       /// アプリケーションのメイン エントリ ポイントです。
       /// </summary>
       [STAThread]
       static void Main(string[] args)
       {
           //デフォルトのコードをコメントアウト
           //Application.EnableVisualStyles();
           //Application.SetCompatibleTextRenderingDefault(false);
           //Application.Run(new Form1());

           //ソートしておく - 文字列の比較なので、ファイルとフォルダが混在している引数が与えられると期待している順にならないかもしれない。
           Array.Sort<string>(args);

           StringBuilder unc_path = new StringBuilder();
           foreach(string path in args) 
           {
               unc_path.AppendLine(GetUniversalName(path));
           }
           Clipboard.SetText(unc_path.ToString());

       }

       /* 
        * WNetGetUniversalNameをインポートする
        */
       [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
       [return: MarshalAs(UnmanagedType.U4)] static extern int
       WNetGetUniversalName(
           string lpLocalPath,                                 // ネットワーク資源のパス 
           [MarshalAs(UnmanagedType.U4)] int dwInfoLevel,      // 情報のレベル
           IntPtr lpBuffer,                                    // 名前バッファ
           [MarshalAs(UnmanagedType.U4)] ref int lpBufferSize  // バッファのサイズ
       );


       /*
        * dwInfoLevelに指定するパラメータ
        *  lpBuffer パラメータが指すバッファで受け取る構造体の種類を次のいずれかで指定
        */
       const int UNIVERSAL_NAME_INFO_LEVEL = 0x00000001;
       const int REMOTE_NAME_INFO_LEVEL    = 0x00000002; //こちらは、テストしていない


       /*
        * lpBufferで受け取る構造体
        */
       [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
       struct UNIVERSAL_NAME_INFO
       {
           public string lpUniversalName;
       }

       [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
       struct _REMOTE_NAME_INFO  //こちらは、テストしていない
       {
           string lpUniversalName;
           string lpConnectionName;
           string lpRemainingPath;
       }

       /* エラーコード一覧
        * WNetGetUniversalName固有のエラーコード
        *   http://msdn.microsoft.com/ja-jp/library/cc447067.aspx
        * System Error Codes (0-499)
        *   http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
        */
       const int NO_ERROR                 = 0;
       const int ERROR_NOT_SUPPORTED      = 50;
       const int ERROR_MORE_DATA          = 234;
       const int ERROR_BAD_DEVICE         = 1200;
       const int ERROR_CONNECTION_UNAVAIL = 1201;
       const int ERROR_NO_NET_OR_BAD_PATH = 1203;
       const int ERROR_EXTENDED_ERROR     = 1208;
       const int ERROR_NO_NETWORK         = 1222;
       const int ERROR_NOT_CONNECTED      = 2250;


       /*
        * UNC変換ロジック本体
        */
       static string GetUniversalName(string path_src)
       {
           string unc_path_dest = path_src; //解決できないエラーが発生した場合は、入力されたパスをそのまま戻す
           int size = 1;

           /*
            * 前処理
            *   意図的に、ERROR_MORE_DATAを発生させて、必要なバッファ・サイズ(size)を取得する。
            */
           //1バイトならば、確実にERROR_MORE_DATAが発生するだろうという期待。
           IntPtr lp_dummy = Marshal.AllocCoTaskMem(size);

           //サイズ取得をトライ
           int apiRetVal = WNetGetUniversalName(path_src, UNIVERSAL_NAME_INFO_LEVEL, lp_dummy, ref size);

           //ダミーを解放
           Marshal.FreeCoTaskMem(lp_dummy);


           /*
            * UNC変換処理
            */ 
           switch(apiRetVal)
           {
               case ERROR_MORE_DATA :
                   //受け取ったバッファ・サイズ(size)で再度メモリ確保
                   IntPtr lpBufUniversalNameInfo = Marshal.AllocCoTaskMem(size);

                   //UNCパスへの変換を実施する。
                   apiRetVal = WNetGetUniversalName(path_src, UNIVERSAL_NAME_INFO_LEVEL, lpBufUniversalNameInfo, ref size);

                   //UNIVERSAL_NAME_INFOを取り出す。
                   UNIVERSAL_NAME_INFO a = (UNIVERSAL_NAME_INFO)Marshal.PtrToStructure(lpBufUniversalNameInfo, typeof(UNIVERSAL_NAME_INFO));

                   //バッファを解放する
                   Marshal.FreeCoTaskMem(lpBufUniversalNameInfo);

                   if (apiRetVal == NO_ERROR)
                   {
                       //UNCに変換したパスを返す
                       unc_path_dest = a.lpUniversalName;
                   }
                   else
                   {
                       //MessageBox.Show(path_src +"ErrorCode:" + apiRetVal.ToString());
                   }
                   break;

               case ERROR_BAD_DEVICE   : //すでにUNC名(\\servername\test)
               case ERROR_NOT_CONNECTED: //ローカル・ドライブのパス(C:\test)
                   //MessageBox.Show(path_src +"\nErrorCode:" + apiRetVal.ToString());
                   break;
               default:
                   //MessageBox.Show(path_src + "\nErrorCode:" + apiRetVal.ToString());
                   break;

           }

           return unc_path_dest;
       }

   }

}  

参考

ネットワークドライブ上のパスをUNCにする - チラシの裏
質問:ネットワークドライブからUNCパスを取得する方法 - Microsoft Visual Studio
Convert path to UNC path - Stack Overflow
WNetGetUniversalName - Microsoftデベロッパーセンター
WNetGetConnection - Microsoftデベロッパーセンター
System Error Codes - Microsoftデベロッパーセンター
NativeMethods.WNetGetUniversalName メソッド - Microsoftデベロッパーセンター
C#入門- 第20回 実行時に参照可能な属性 - @IT
VistaとWindows 7の送る(send to)フォルダを簡単に表示させる方法 - とあるソニー好きなエンジニアの日記

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?