- 記事一覧 >
- ブログ記事
カスタムURLスキームでエクスプローラーを起動してフォルダを開く msiビルド全手順
はじめに
カスタム URL スキームの URL(例:diropen:https://...
)をクリックすると、対応アプリケーションが開く動作をします。それを利用して、 "ハイパーリンクをクリック → リンクのフォルダを開いた状態でエクスプローラーが立ち上がる" となったらいいなと思い、実現するアプリを実装しました。
カスタム URL スキームの意味、挙動については、別記事カスタム URL スキームのアプリに対応しているか検知する方法をご覧いただくと分かると思います。
ハイパーリンクでフォルダを開きたいわけは、別記事「ファイル/フォルダの追加更新削除を WebHook を使って通知するアプリ」の Slack へのファイル/フォルダの追加更新削除通知がただのテキストで、確認したいフォルダへ行くには、パスをコピーペーストする必要があったからです。
【Slack の表示仕様】
メッセージ | テキスト/ハイパーリンク |
---|---|
\192.168.11.9\share | テキスト |
//192.168.11.9/share | テキスト |
https://itccorporation.jp/ | ハイパーリンク |
diropen://NAS1/share | ハイパーリンク |
diropen:\NAS1\share | テキスト |
Rocket.Chat にこの仕様は有りませんが、Slack は、<url|text>
のフォーマットで、例えば、<diropen://192.168.11.9/share|Go To Share>
がメッセージに有ると、「Go To Share」という表示で、diropen://192.168.11.9/share
へのハイパーリンクになります。<a href="diropen://192.168.11.9/share">Go To Share</a>
と同じ意味です。
スペースは途切れますので、URL Encode が必要でした。
Slack である必要はなく、HTML のアンカーリンク(<A>タグ)でも利用可能です。
<a href="diropen:C:/share/">C:\share</a>とすると、 C:\share が開きます。
【検証環境】
Windows Server 10 PRO 64 ビット
Visual Studio Community 2019
C++ソースコード
ソースコードは、1つで、以下になります。
やっていることは、コメントにもありますが、URLデコード → UTF8 → Unicode → explorer.exe にパスを渡す
です。
バッチファイルでもできると思いますが、かなりめんどくさそうなので、やめました。また、以降、ビルド手順の説明に入りますが、.msi をダブルクリックして、インストールするだけで、必要なレジストリが登録されます。
ExplorerProtocolHandler.cpp
:
#include <windows.h>
#include <iostream>
#include <string>
#include <stdio.h>
static std::string urldecode(const char * url) {
int ln = 0;
std::string conv;
const char * pt = url;
while ( * pt) {
if ( * pt == '%') {
char bf[3];
bf[0] = pt[1];
bf[1] = pt[2];
bf[2] = '\0';
int ch;
ch = strtol(bf, NULL, 16);
conv += ch;
pt += 3;
} else if ( * pt == '+') {
conv += ' ';
pt++;
} else {
conv += * pt;
pt++;
}
ln++;
}
return conv;
}
std::wstring utf8tounicode(const char * utf) {
const unsigned char * src = (const unsigned char * ) utf;
unsigned char * uni = new unsigned char[strlen((const char * ) utf) * 2 + 2];
unsigned char * dst = uni;
while ( * src) {
if ( * src <= 0xc0) { // 1 byte
dst[1] = 0;
dst[0] = src[0];
dst += 2;
src += 1;
} else if ( * src <= 0xe0) { // 2 bytes
dst[1] = (src[0] >> 2) & 0x07;
dst[0] = ((src[0] << 6) & 0xc0) | (src[1] & 0x3f);
dst += 2;
src += 2;
} else if ( * src <= 0xf8) { // 3 bytes
dst[1] = ((src[0] << 4) & 0xf0) | ((src[1] >> 2) & 0x0f);
dst[0] = ((src[1] << 6) & 0xc0) | ((src[2]) & 0x3f);
if (dst[1] == 0x30 && dst[0] == 0x1c) {
dst[1] = 0xff;
dst[0] = 0x5e;
}
dst += 2;
src += 3;
} else { // 4 bytes
dst[3] = 0;
dst[2] = ((src[0] << 2) & 0x1c) | ((src[1] >> 4) & 0x03);
dst[1] = ((src[1] << 4) & 0xf0) | ((src[2] >> 2) & 0x0f);
dst[0] = ((src[2] << 6) & 0xc0) | ((src[2] >> 2) & 0x0f);
dst += 4;
src += 4;
}
}
dst[0] = 0;
dst[1] = 0;
std::wstring result = (WCHAR * ) uni;
return result;
}
std::wstring rtrim(const std::wstring & str,
const std::wstring & charlist) {
if (str.empty()) return str;
WCHAR * bf = _wcsdup(str.c_str());
WCHAR * ed = bf + wcslen(bf) - 1;
while (ed != bf) {
if (!wcschr(charlist.c_str(), * ed)) break;
* ed = 0;
ed--;
}
std::wstring result = bf;
free(bf);
return result;
}
void put_log(LPCTSTR form, ...) {
char str[2048];
va_list args;
va_start(args, form);
vsprintf_s(str, sizeof(str), form, args);
char temp[MAX_PATH];::GetTempPath(sizeof(temp), temp);
std::string path = std::string(temp) + "ExplorerProtocolHandler.log";
HANDLE hFile;
hFile = ::CreateFile(path.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_ARCHIVE,
0);
if (hFile != INVALID_HANDLE_VALUE) {
::SetFilePointer(hFile, 0, NULL, FILE_END);
SYSTEMTIME tm;::GetLocalTime( & tm);
char dt[1024];
sprintf_s(dt, sizeof(dt), "[%4d/%02d/%02d %02d:%02d:%02d.%03d] %s\r\n",
tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds,
str);
DWORD wl;::WriteFile(hFile, dt, strlen(dt), & wl, NULL);::CloseHandle(hFile);
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
char * cmd = ::GetCommandLie();
put_log("(%d): %s", __LINE__, cmd);//%USERPROFILE%\AppData\Local\Tempにログ出力
//ExplorerProtocolHandler.log
//[2022/02/08 23:10:39.788] (140): "C:\Users\admin\AppData\Local\ExplorerProtocolHandler\ExplorerProtocolHandler.exe" "diropen://NAS1/admin/ExplorerProtocolHandler/"
//[2022/02/08 23:10:39.788] (152): //NAS1/admin/ExplorerProtocolHandler/"
if (cmd[0] == '\"') {
cmd = strchr(cmd + 1, '\"') + 2;//コマンドラインからカスタムURL文字列開始位置を返す(exeの呼び出しが"で囲まれている場合。)
} else {
cmd = strchr(cmd, ' ') + 1;//コマンドラインからカスタムURLの位置を返す(exeの呼び出しが"で囲まれていない場合。)
}
std::wstring dir = utf8tounicode(urldecode(cmd).c_str());//URLデコード→UTF8→Unicode
int pos = dir.find(L':');//:の位置
if (pos != std::string::npos) {//:が有る場合
dir = dir.substr(pos + 1);//:の後ろの部分取り出し //NAS1...
}
::ShellExecuteW(NULL, NULL, L"explorer.exe", (L"file:" + dir).c_str(), NULL, SW_SHOWNORMAL);//file://NAS1...を引数にしてエクスプローラ起動。Unicodeのため、Lプレフィックス
}
exe ビルド
Visual Studio 2019 で何も無い状態からスタート →.msi を作成までの手順を説明します。なお、Visual Studio 2019 は、インストールしたての状態です。
プロジェクト作成
「Windows デスクトップ ウィザード」をクリックします。
プロジェクト名:ExplorerProtocolHandler
ソリューション名:ExplorerProtocolHandler
とします。(任意です。)
アプリケーションの種類を「コンソール アプリケーション (.exe)」とし、「OK」ボタンをクリックします。(追加のオプションはチェック無しです。)
ソースコード編集
ExplorerProtocolHandler.cpp 編集画面で、先ほど(上に掲載)のソースコードをコピーペーストします。(元々あったソースコードを全選択して、ペーストです。)
この時点で、ソースコードにエラー表示されますが、この後行う設定により、解消します。
ビルド準備
いきなり本番ビルドするため、Release x86
に変更します。
「プロジェクト」→「ExplorerProtocolHandler のプロパティ」をクリックします。
「構成プロパティ」→「リンカー」→「システム」→「サブシステム」から
「コンソール(/SUBSYSTEM:CONSOLE)」を選択します。
これは、アプリケーション起動時に DOS 窓(コマンドプロンプト)が開くのを防ぐための設定です。
「構成プロパティ」→「詳細」→「文字セット」から
「Unicode 文字セットを使用する」を選択し、「OK」ボタンをクリックします。
これは、先ほどのソースコード(文字セットが Unicode になっていないとエラー表示されるコード)のエラーを解消するためです。
ビルド
「ビルド」→「ExplorerProtocolHandler のビルド」をクリックして、C:\Users\admin\source\repos\ExplorerProtocolHandler\Release\ExplorerProtocolHandler.exe
を作成します。
msi ビルド
拡張機能インストール
.msi インストーラーを作成するため、Microsoft Visual Studio Installer Projects
をインストールします。
「拡張機能の管理」をクリックします。
検索欄に「installer」と入力して、Microsoft Visual Studio Installer Projects
を選択、「ダウンロード」をクリックします。
ダウンロードが完了したら、「閉じる」ボタンをクリックし、さらに、Visual Studio 2019 自体も閉じます。
Visual Studio 2019 を閉じたら、自動的にインストールが開始されますので、「はい」をクリックします。
Setup Project
Windows Installer のプロジェクトを作成します。
「ファイル」→「新規作成」→「プロジェクト」をクリックします。
"Setup Project"をクリックし、「次へ」をクリックします。
ソリューションから「新しいソリューションを作成する」を選択します。 (プロジェクト名は、任意ですが、デフォルトのままとします。)
Application Folder
「Application Folder」をクリックして、DefaultLocation の値を [LocalAppDataFolder]ExplorerProtocolHandler
とします。
「Application Folder」選択状態で、右ペインを右クリック →「Add」→「ファイル」をクリックします。
ビルドした exeC:\Users\admin\source\repos\ExplorerProtocolHandler\Release\ExplorerProtocolHandler.exe
を選択します。
レジストリ設定
HKEY_CLASSES_ROOT
のところで、右クリック →「新しいキー」をクリックします。
以降、同要領で、HKEY_CLASSES_ROOT\diropen\shell\open\command
まで作成するものとします。
command
を選択し、右ペインを右クリック →「New」→「文字列の値」をクリックします。
「New Value #1」が初期値になりますが、これを削除して、空白にします。
(Default)
をクリックして、Value の値を "[TARGETDIR]ExplorerProtocolHandler.exe" "%1"
とします。
同要領で、
Name: (Default)
Value: URL: Explorer Protocol
と
Name: URL Protocol
(Value は何も登録しません。)
を登録します。
URL: Explorer Protocol
は、プロコルハンドラーの名称で、任意な文字列です。
URL Protocol
は、プロトコルハンドラーの目印で "" 固定です。
ビルド
C:\Users\admin\source\repos\ExplorerProtocolHandler\Setup1\Release\Setup1.msi
ができれば成功です。(インストール方法は特筆する事は無いため、割愛します。)
動作例
使い方は、diropen:
リンクをクリックするだけです。
HTMLリンク<a href="diropen:C:/share/%e6%96%b0%e3%81%97%e3%81%84%20%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e3%83%bc/">C:\share\新しい フォルダー</a>
Slack メッセージその1diropen://192.168.11.9/share/%e6%96%b0%e3%81%97%e3%81%84%20%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e3%83%bc/
↓
↓
Slack メッセージその2<diropen://192.168.11.9/share/%e6%96%b0%e3%81%97%e3%81%84%20%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e3%83%bc/|\\192.168.11.9\share\新しい フォルダー>
↓
↓
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。