[C#] 送るメニューにショートカットを登録/解除する方法

エクスプローラーなどでファイルを右クリックしたときに出てくる「送る」メニュー。
この送るメニューにアプリケーションを登録しておくと、選択したファイルの情報を
アプリケーションに渡して起動できるため、使い方によっては便利な機能。
01
例えば、logファイルを"メモ帳"に送って起動するといったことができる。

で、この「送る」メニューに登録するためには、特定のフォルダにショートカットをコピーする必要がある。
例えば、Windows8の場合、以下のフォルダにショートカットを置く。
C:\Users\<ユーザー名>\AppData\Roaming\Microsoft\Windows\SendTo
でもこれ、OS毎にパスが異なったりするので、人に説明するのは厄介だったりする。
知っている人なら、何てことない「送る」メニューへの登録でも、初心者にとっては分かりにくいし煩わしい。
なので、これをプログラム側から簡単に登録できないか調べてみた。
いろいろやり方はあると思うけど、4つほどご紹介します。

方法1:インストーラから登録する方法

セットアップする際に、「送る」メニューへ登録させる方法。
これが一番簡単だけどVisual Studioの場合、Professional Editionなどでなければセットアップを作れないし、インストーラ付きアプリは敬遠されがちなので今回は割愛。

方法2:WSHを使用しショートカット作成する方法

Visual Studio からプロジェクト>参照を追加>COM から、「Windows Script Host Object Model」
を追加し、ショートカットを作成する方法。
まず、Visual Studioのソリューションエクスプローラからプロジェクトを選択し「参照の追加」。
参照の追加
参照マネージャダイアログでCOMの中にある「Windows Script Host Object Model」をチェックし追加する。
参照マネージャ
これでショートカット作成に必要な、IWshRuntimeLibrary.IWshShortcutなどが使用できるようになる。
「送る」フォルダのパス自体は、
System.Environment.GetFolderPath(Environment.SpecialFolder.SendTo)
で取得できるので、あとは、これのショートカットを登録する。
ショートカット作成方法詳細は、こちらが参考になるかも。
http://dobon.net/vb/dotnet/file/createshortcut.html
が、しかし、何故か.NET Framework 4以降を利用するとビルドできなかったため、この方法での実装は止めておくことにした。

方法3:WSHのスクリプトを実行して登録する方法

アプリ(exe)とは関係なく、WSHのスクリプト実行のみで「送る」メニューに登録する方法。
以下の例では、同一フォルダ内の"sendTo.exe"を「送る」メニューに登録(ショートカット作成)します。

  • Sample.js
ws = WScript.CreateObject('WScript.Shell');
ln = ws.SpecialFolders('SendTo') + '\\' + 'テスト.lnk';
sc = ws.CreateShortcut(ln);
sc.TargetPath = ws.CurrentDirectory + '\\sendTo.exe';
sc.Save();

これはシンプルだけど、exeと、jsファイル2つを配布しなければならない。
ちょっとカッコ悪いし、スクリプトを別に実行してくれとも言いにくいので、この方法はパスすることにした。

方法4:exeだけで実現する方法

方法3を応用した形で、C#のアプリ内でWSHのスクリプトファイルを吐き出し、それを実行するという方法。
実行後は後始末も行う。
サンプルは以下の通り。
ちょっと雑だけど、これなら利用者は簡単に「送る」メニューに登録することができそう。

  • Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// 追加
using System.IO;
using System.Reflection;
using System.Diagnostics;
namespace sendTo
{
    public partial class Form1 : Form
    {
        private string aplTitle = null; // アプリ名
        private string exeFile = null;  // exeファイル名
        private string jsFile = null;   // スクリプトファイル名
        private string lnkFile = null;  // リンク名
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // プロジェクト>プロパティ>アセンブリ情報 で指定した「タイトル」を取得
            var assembly = Assembly.GetExecutingAssembly();
            var attribute = Attribute.GetCustomAttribute(
                assembly,
                typeof(AssemblyTitleAttribute)
            ) as AssemblyTitleAttribute;
            aplTitle = attribute.Title;
            this.Text = aplTitle;
            // 自身のexeファイル名を取得
            exeFile = Path.GetFileName(Application.ExecutablePath);
            // WSHスクリプト名
            jsFile = Directory.GetCurrentDirectory() + "\\sendtoAdd.js";
            // ショートカットのリンク名
            lnkFile = Environment.GetFolderPath(Environment.SpecialFolder.SendTo) + "\\" + aplTitle + ".lnk";
            // [送る]フォルダのパスをtextBox1に表示
            textBox1.Text = Environment.GetFolderPath(Environment.SpecialFolder.SendTo);
            textBox1.ReadOnly = true;
            // ボタンマスク処理
            maskButton();
            // 起動時にコマンドライン引数指定があればメッセージボックス表示
            string[] args = Environment.GetCommandLineArgs();
            string msg = null;
            for (int i = 1; i < args.Length; i++)
            {
                msg += args[i] + "\n";
            }
            if (msg != null)
            {
                MessageBox.Show(
                "起動時にコマンドライン引数を受け取りました。\n\n" + msg,
                aplTitle,
                MessageBoxButtons.OK,
                MessageBoxIcon.Information);
            }
        }

        // [登録]ボタン
        private void button1_Click(object sender, EventArgs e)
        {
            // [送る]フォルダにショートカット作成
            try
            {
                // WSHファイル作成
                using (StreamWriter w = new StreamWriter(
                jsFile, false, System.Text.Encoding.GetEncoding("shift_jis")))
                {
                    w.WriteLine("ws = WScript.CreateObject('WScript.Shell');");
                    w.WriteLine("ln = ws.SpecialFolders('SendTo') + '\\\\' + '" + aplTitle + ".lnk';");
                    w.WriteLine("sc = ws.CreateShortcut(ln);");
                    w.WriteLine("sc.TargetPath = ws.CurrentDirectory + '\\\\" + exeFile + "';");
                    w.WriteLine("sc.Save();");
                }
                // sendtoAdd.jsを実行し[送る]メニューにショートカット作成
                if (File.Exists(jsFile))
                {
                    Process p = Process.Start(jsFile);
                    p.WaitForExit(10000); // 終了まで待つ(最大10秒)
                    File.Delete(jsFile);
                }
                // [送る]フォルダに登録されたか確認
                if (File.Exists(lnkFile))
                {
                    MessageBox.Show(
                    "[送る]メニューに登録しました。\n\n" + lnkFile,
                    aplTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                }
                else
                {
                    MessageBox.Show(
                    "[送る]メニューの登録に失敗しました。\n\n" + lnkFile,
                    aplTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                "[送る]メニューの登録に失敗しました。\n\n" + ex.Message,
                aplTitle,
                MessageBoxButtons.OK,
                MessageBoxIcon.Warning);
            }
            maskButton();
        }

        // [解除]ボタン
        private void button2_Click(object sender, EventArgs e)
        {
            // [送る]フォルダに登録済みか確認
            if (File.Exists(lnkFile))
            {
                try
                {
                    File.Delete(lnkFile);
                    MessageBox.Show(
                    "[送る]メニューからショートカットを削除しました。",
                    aplTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                }
                catch (IOException ex)
                {
                    MessageBox.Show(
                    "ショートカットの削除に失敗しました。\n\n" + ex.Message,
                    aplTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
                }
            }
            maskButton();
        }

        // ボタンマスク
        private void maskButton()
        {
            // [送る]フォルダに登録済みか確認
            if (File.Exists(lnkFile))
            {
                button1.Enabled = false;
                button2.Enabled = true;
            }
            else
            {
                button1.Enabled = true;
                button2.Enabled = false;
            }
        }
    }
}

画面デザイン。
GUIデザイン
textBox1、button1、button2を用意。
あと、プロジェクト>プロパティ>アセンブリ情報 内の「タイトル」に「送るサンプル」と指定。
実行時画面。
実行画面
一応、登録済みならボタンをマスク処理とかもしている。
「登録」をクリックすると、[送る]フォルダで示したパス配下に、「送るサンプル」のショートカットを登録する。
あと、「送る」から起動された場合、送られてきた情報をメッセージボックスで表示する。
メッセージボックス
ちなみに、この方法4を「画像振り分けツール」で採用してたりする。

 
 

追記

上記の方法4で完璧。
・・と思い込んでいたら、*.jsファイルの関連付けが異なる環境では以下のようなメッセージが出て実行できない場合があった。
この種類のファイル(.js)を開くには、どのアプリを使いますか?

この種類のファイル(.js)を開くには、どのアプリを使いますか?

 
 
ファイル拡張子".js"を持つスクリプトエンジンはありません。

ファイル拡張子".js"を持つスクリプトエンジンはありません。

 
 

関連付けが出来ていない環境でも動くよう、"cscript"を直接呼び出し実行する。
具体的には、上記ソースの84行目の「Process p = Process.Start(jsFile);」を以下のように書き換えることで解決できた。

//Process p = Process.Start(jsFile);
ProcessStartInfo psi = (new ProcessStartInfo());
psi.FileName = "cscript";
psi.Arguments = @"//e:jscript " + jsFile;
psi.WindowStyle = ProcessWindowStyle.Hidden;
Process p = Process.Start(psi);

← ブログ一覧へ戻る