概要
ネットワークドライブのファイルパスを絶対パス(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を使用している。
コード
一通り動作していることが確認できた。
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)フォルダを簡単に表示させる方法 - とあるソニー好きなエンジニアの日記