1. 記事一覧 >
  2. ブログ記事
category logo

カスタムURLスキームでエクスプローラーを起動してフォルダを開く msiビルド全手順

(更新) (公開)

はじめに

カスタム URL スキームの URL(例:diropen:https://...)をクリックすると、対応アプリケーションが開く動作をします。それを利用して、 "ハイパーリンクをクリック → リンクのフォルダを開いた状態でエクスプローラーが立ち上がる" となったらいいなと思い、実現するアプリを実装しました。
カスタム URL スキームの意味、挙動については、別記事カスタム URL スキームのアプリに対応しているか検知する方法をご覧いただくと分かると思います。

カスタム URL スキームでエクスプローラーを起動 動画gif

ハイパーリンクでフォルダを開きたいわけは、別記事「ファイル/フォルダの追加更新削除を WebHook を使って通知するアプリ」の Slack へのファイル/フォルダの追加更新削除通知がただのテキストで、確認したいフォルダへ行くには、パスをコピーペーストする必要があったからです。

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>
と同じ意味です。


Slack ハイパーリンク


スペースは途切れますので、URL Encode が必要でした。

Slack ハイパーリンク スペースは途切れる

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 デスクトップ ウィザード」をクリックします。 Windows デスクトップ ウィザード


プロジェクト名:ExplorerProtocolHandler
ソリューション名:ExplorerProtocolHandler
とします。(任意です。) プロジェクト名 ソリューション名


アプリケーションの種類を「コンソール アプリケーション (.exe)」とし、「OK」ボタンをクリックします。(追加のオプションはチェック無しです。) コンソール アプリケーション (.exe)


ソースコード編集

ExplorerProtocolHandler.cpp 編集画面で、先ほど(上に掲載)のソースコードをコピーペーストします。(元々あったソースコードを全選択して、ペーストです。) ExplorerProtocolHandler.cpp 編集前


この時点で、ソースコードにエラー表示されますが、この後行う設定により、解消します。 ExplorerProtocolHandler.cpp 編集後


ビルド準備

いきなり本番ビルドするため、Release x86に変更します。 Release x86


「プロジェクト」→「ExplorerProtocolHandler のプロパティ」をクリックします。 ExplorerProtocolHandler のプロパティ


「構成プロパティ」→「リンカー」→「システム」→「サブシステム」から
「コンソール(/SUBSYSTEM:CONSOLE)」を選択します。

これは、アプリケーション起動時に DOS 窓(コマンドプロンプト)が開くのを防ぐための設定です。

SUBSYSTEM:CONSOLE


「構成プロパティ」→「詳細」→「文字セット」から
「Unicode 文字セットを使用する」を選択し、「OK」ボタンをクリックします。

これは、先ほどのソースコード(文字セットが Unicode になっていないとエラー表示されるコード)のエラーを解消するためです。

Unicode 文字セットを使用する


ビルド

「ビルド」→「ExplorerProtocolHandler のビルド」をクリックして、
C:\Users\admin\source\repos\ExplorerProtocolHandler\Release\ExplorerProtocolHandler.exeを作成します。

ExplorerProtocolHandler のビルド


ExplorerProtocolHandler のビルド ビルド後


msi ビルド

拡張機能インストール

.msi インストーラーを作成するため、Microsoft Visual Studio Installer Projectsをインストールします。
「拡張機能の管理」をクリックします。
拡張機能の管理


検索欄に「installer」と入力して、Microsoft Visual Studio Installer Projectsを選択、「ダウンロード」をクリックします。 Microsoft Visual Studio Installer Projects


ダウンロードが完了したら、「閉じる」ボタンをクリックし、さらに、Visual Studio 2019 自体も閉じます。 Visual Studio 2019 自体も閉じます


Visual Studio 2019 を閉じたら、自動的にインストールが開始されますので、「はい」をクリックします。 閉じたら、自動的にインストールが開始


「Modify」ボタンをクリックします。 「Modify」ボタンをクリック


「Close」ボタンをクリックします。 「Close」ボタンをクリック


Setup Project

Windows Installer のプロジェクトを作成します。


まず、先ほど開いていたソリューションを開きます。 ソリューションを開く


「ファイル」→「新規作成」→「プロジェクト」をクリックします。 「ファイル」→「新規作成」→「プロジェクト」


"Setup Project"をクリックし、「次へ」をクリックします。 Setup Project


ソリューションから「新しいソリューションを作成する」を選択します。 (プロジェクト名は、任意ですが、デフォルトのままとします。) 新しいソリューションを作成する


「作成」をクリックします。 「作成」をクリック


Application Folder

「Application Folder」をクリックして、DefaultLocation の値を [LocalAppDataFolder]ExplorerProtocolHandler とします。 「Application Folder」をクリック


「Application Folder」選択状態で、右ペインを右クリック →「Add」→「ファイル」をクリックします。 「Add」→「ファイル」


ビルドした exe
C:\Users\admin\source\repos\ExplorerProtocolHandler\Release\ExplorerProtocolHandler.exeを選択します。 ExplorerProtocolHandler.exeを選択


レジストリ設定

レジストリエディタアイコンをクリックします。 レジストリエディタアイコンをクリック


HKEY_CLASSES_ROOTのところで、右クリック →「新しいキー」をクリックします。
以降、同要領で、HKEY_CLASSES_ROOT\diropen\shell\open\commandまで作成するものとします。
「新しいキー」をクリック


commandを選択し、右ペインを右クリック →「New」→「文字列の値」をクリックします。 「New」→「文字列の値」


「New Value #1」が初期値になりますが、これを削除して、空白にします。 「New Value #1」


「New Value #1」を空白にする


別の場所をクリックすると、(Default)になります。 (Default)


(Default)をクリックして、Value の値を "[TARGETDIR]ExplorerProtocolHandler.exe" "%1" とします。 (Default)のValue


(Default)のValue登録後


同要領で、
Name: (Default)
Value: URL: Explorer Protocol

Name: URL Protocol
(Value は何も登録しません。)
を登録します。

URL: Explorer Protocolは、プロコルハンドラーの名称で、任意な文字列です。

URL Protocolは、プロトコルハンドラーの目印で "" 固定です。

プロコルハンドラー


ビルド

「ビルド」→「Setup1 のビルド」をクリックします。 Setup1 のビルド


Setup1 のビルド ビルド後


C:\Users\admin\source\repos\ExplorerProtocolHandler\Setup1\Release\Setup1.msiができれば成功です。(インストール方法は特筆する事は無いため、割愛します。)
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>
動作例1


Slack メッセージその1
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/

動作例2-1

動作例2-2

動作例2-3


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\新しい フォルダー>

動作例3-1

動作例3-2

動作例3-3

loading...