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

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

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


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


# 経緯
##.NetFrameworkにメソッドがない？
「ネットワークドライブ UNC変換」で検索すると、[VBScriptの例1][ネットワークドライブ上のパスをUNCにする]とか、[VBScriptの例2][micrsoft support]等結構出てくるが、C#(≒.NetFrameworkクラスライブラリ)の情報がない。

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

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

とのこと。ここでは、[WNetGetConnection][WNetGetConnection]を使う方法が示されており、[質問者][質問：ネットワークドライブからＵＮＣパスを取得する方法]「C#動いた」そうです。

##.NetFrameworkにメソッドがあった？
[NativeMethods.WNetGetUniversalName メソッド][NativeMethods.WNetGetUniversalName]というのを見つけたが、

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

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

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


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

```C#: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][Convert path to UNC path]
[WNetGetUniversalName - Microsoftデベロッパーセンター][WNetGetUniversalName]
[WNetGetConnection - Microsoftデベロッパーセンター][WNetGetConnection]
[System Error Codes - Microsoftデベロッパーセンター][System Error Codes]
[NativeMethods.WNetGetUniversalName メソッド - Microsoftデベロッパーセンター][NativeMethods.WNetGetUniversalName]
[C#入門- 第20回 実行時に参照可能な属性 - @IT][C#入門- 第20回]
[VistaとWindows 7の送る（send to)フォルダを簡単に表示させる方法 - とあるソニー好きなエンジニアの日記][send to]

[ネットワークドライブ上のパスをUNCにする]:http://blog.livedoor.jp/shingo555jp/archives/1819741.html
[micrsoft support]:http://support.microsoft.com/kb/151027/ja
[質問：ネットワークドライブからＵＮＣパスを取得する方法]:
http://social.msdn.microsoft.com/Forums/vstudio/ja-JP/b33ff797-be1e-4a3b-9cd7-d4565529783f?forum=csharpgeneralja]
[じゃんぬねっとさんコメント]:http://dobon.net/vb/bbs/log3-14/8196.html

[WNetGetUniversalName]:http://msdn.microsoft.com/ja-jp/library/cc447067.aspx
[WNetGetConnection]:http://msdn.microsoft.com/ja-jp/library/cc447035.aspx
[NativeMethods.WNetGetUniversalName]:http://msdn.microsoft.com/ja-jp/library/microsoft.teamfoundation.common.internal.nativemethods.wnetgetuniversalname(v=vs.110).aspx
[System Error Codes]:http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

[Convert path to UNC path]:http://stackoverflow.com/questions/8472558/convert-path-to-unc-path

[send to]:[http://taiseiko.blog.so-net.ne.jp/2009-05-24]

[C#入門- 第20回]:http://www.atmarkit.co.jp/fdotnet/csharp_abc/csharp_abc_020/csharp_abc04.html