FSharp プロジェクトでビルドしたexeをClickOnceで配布できるように

さて、アイコンも付けたし配布したいですよね。
ということでClickOnceを使って簡単に配布できるようにしましょう。

ClickOnceとは

ClickOnce は、ユーザーとの最小限の対話によってインストールして実行できる、自己更新型の Windows ベースのアプリケーションの作成を可能にする配置テクノロジです。

ClickOnce の配置の概要 | Microsoft Docs
自己更新型なのがすごいところでアプリケーションの起動時や終了時、設定時間毎に新しいバージョンがないか確認しにいって半自動更新も強制更新もできます。
Visual StudioはFSharpプロジェクトのClickOnceをサポートしていないのでコマンドラインツール(Mage.exe)を使ってやります。
GUIツール(MageUI.exe)もありますが今回は使っていません。(随時確認には使いました。)

対象読者

FSharpプロジェクトでビルドしたexeをClickOnceで配布したい人
ClickOnceコマンドラインツール(Mage.exe)でやりたい人

手順

  • 署名用ファイルを作成する
  • 初期配置ファイルを作成する
  • マニフェストファイルを作成する
  • マニフェストファイルに署名する
  • 配置ファイルを作成する
  • 配置ファイルに署名する

上2つは初回のみの作業で、下4つはリリース毎に行う作業になる想定です。

フォルダ階層図

以前作ったIconSampleプロジェクトにClickOnceを適用します。詳細は下で説明します。

   ClickOnce
   ├─init-publish.bat                  ・・・初期配置ファイル作成で一回だけ使うバッチ
   ├─publish.bat                       ・・・更新用配置ファイル作成を行うバッチ
   ├─cert-files                        ・・・署名用ファイルを格納
   │  └─make-cert.bat                 ・・・署名用ファイル作成で一回だけ使うバッチ
   └─publish                           ・・・配置ファイルを格納
       ├ IconSample.application         ・・・初期配置ファイル、更新用配置ファイル
       ├ IconSample_X.X.X.X.application
       ├ IconSample_Y.Y.Y.Y.application
       └─ApplicationFiles              ・・・各バージョンのモジュールを格納
           ├─IconSample_0.0.0.0        ・・・初期配置ファイル用モジュールを格納
           ├─IconSample_X.X.X.X        ・・・バージョン毎のマニフェストファイルを格納
           └─IconSample_Y.Y.Y.Y

署名用ファイルを作成する

以下サイトの、"証明書ファイルの作成"の項を参考にしました。
csc.exeでClickOnceアプリケーションをコンパイルし、mage.exeで配置する - Symfoware

makecert.exeとpvk2pfx.exeで作ります。なければWindowsSDKをインストールするなどして入手してください。
"password"は各自変更してください。

管理者権限のコマンドプロンプトで実行します。

make-cert.bat

set makecert="C:\Program Files (x86)\Windows Kits\10\bin\x64\makecert.exe"
%makecert% -r -pe -n "CN=SYM" -sv symfo.pvk symfo.cer

set pvk2pfx="C:\Program Files (x86)\Windows Kits\10\bin\x64\pvk2pfx.exe"
%pvk2pfx% -pvk symfo.pvk -spc symfo.cer -pfx symfo.pfx -po "password"

途中でダイアログが出てきますが、今回は署名すること自体が目的ではないためNoneを選びます。
symfo.pvk, symfo.cer, symfo.pfxの3ファイルが出力されますのでcert-filesフォルダに移動しておきましょう。
(今気づいたけど、出力ファイル名が完全に参考サイト名由来(symfowareさん)のものだ・・・、変更しましょう)
署名に使うのでセキュリティとか気にする人は各自きちんと調べてください。
何かあっても責任は負いません。

初期配置ファイルを作成する

publish/ApplicationFiles/IconSample_0.0.0.0フォルダを作成して暫定でよいのでexeやdll等のモジュールを放り込みます。
mage.exeがなければWindowsSDKをインストールするなどして入手してください。

APP_NAME等は各自変更してください。
コマンドプロンプトで実行します。

init-publish.bat

set MAGE="C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\mage.exe"

set APP_NAME=IconSample
set CERT_FILE="cert-files\symfo.pfx"
set OUTPUT_DIR=publish
set BIN_DIR=%OUTPUT_DIR%\ApplicationFiles\%APP_NAME%_0.0.0.0
set PROVIDER_URL="http://127.0.0.1/IconSample.application"

%MAGE% ^
-New Application ^
-ToFile %BIN_DIR%\%APP_NAME%.exe.manifest ^
-Name %APP_NAME% ^
-Version 0.0.0.0 ^
-FromDirectory %BIN_DIR% ^
-Processor "x86" ^
-IconFile myicon.ico

%MAGE% ^
-New Deployment ^
-ToFile %OUTPUT_DIR%\%APP_NAME%.application ^
-AppManifest %BIN_DIR%\%APP_NAME%.exe.manifest ^
-Version 0.0.0.0 ^
-Install true ^
-ProviderURL %PROVIDER_URL% ^
-IncludeProviderURL true ^
-Processor "x86" ^
-Publisher "sunotora" 

まず、-New Applicationで暫定のマニフェストファイルを作ります。
マニフェストファイルというのは、バージョン毎の配布モジュールリストみたいなものです。
各モジュールのチェックサムが格納されており、後で行う作業では証明書で署名します。
そのため署名後にモジュールを変更するとClickOnceインストール時にエラーになりますので注意してください。

次に、下の-New Deploymentで初期配置ファイルを作ります。
以後の更新用配置ファイルはこれを上書きして作るので、ここでできたIconSample.applicationをリポジトリに入れておくと楽だと思います。
(元ファイルの指定と出力先の指定が可能なので、一つ上の階層に置いて上書きせずに使ったほうがよさそうではある。)

これで初回のみの作業が完了です。オプションの説明は後でしますが、
Installオプションだけはここだけの設定です。
trueにするとアプリケーションがWindowsにインストールされオフラインでも使えるようになります。
その変わりアプリケーションの更新確認タイミングがアプリケーション終了時に固定されます。(対処方法は後述)

マニフェストファイルを作成する以後の残りの工程

面倒くさいのでバッチファイルでまとめてやれるようにしました。

そういえば--nowin32manifestオプションを付けてビルドしたexeでないとインストール時エラーになるのでここで付けておきましょう。

リビルドし終わったらpublish/ApplicationFiles/IconSample_0.0.0.1にモジュールを入れます。

あれやこれやは各自変更してください。
コマンドプロンプトで実行します。

publish.bat

@echo off
rem ####################################################
rem # Make ClickOnce deployment manifest
rem ####################################################
if defined NEW_VERSION goto equipment
set NEW_VERSION=0.0.0.1

:equipment
rem ####################################################
rem # Equipment
rem ####################################################
if defined MAGE goto findMage
set MAGE="C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\mage.exe"

:findMage
echo %PROVIDER_URL%
if defined PROVIDER_URL goto findClientProviderUrl
set PROVIDER_URL="http://127.0.0.1/IconSample.application"

:findClientProviderUrl
set APP_NAME=IconSample
set CERT_FILE="cert-files\symfo.pfx"
set OUTPUT_DIR=publish
set BIN_DIR=%OUTPUT_DIR%\ApplicationFiles\%APP_NAME%_%NEW_VERSION%

rem ####################################################
rem # Main Proc
rem ####################################################

%MAGE% ^
-New Application ^
-ToFile %BIN_DIR%\%APP_NAME%.exe.manifest ^
-Name %APP_NAME% ^
-Version %NEW_VERSION% ^
-FromDirectory %BIN_DIR% ^
-Processor "x86" ^
-IconFile myicon.ico

%MAGE% ^
-Sign %BIN_DIR%\%APP_NAME%.exe.manifest ^
-CertFile %CERT_FILE% ^
-Password "password"

%MAGE% ^
-Update %OUTPUT_DIR%\%APP_NAME%.application ^
-AppManifest %BIN_DIR%\%APP_NAME%.exe.manifest ^
-Version %NEW_VERSION% ^
-ProviderURL %PROVIDER_URL% ^
-IncludeProviderURL true ^
-Processor "x86" ^
-Publisher "sunotora"

powershell ./convert.ps1 %OUTPUT_DIR%\%APP_NAME%.application

%MAGE% ^
-Sign %OUTPUT_DIR%\%APP_NAME%.application ^
-CertFile %CERT_FILE% ^
-Password "password"

echo F | xcopy %OUTPUT_DIR%\%APP_NAME%.application %OUTPUT_DIR%\%APP_NAME%_%NEW_VERSION%.application /Y

goto endProc

:fail
exit /b 1

:endProc

Main Procの最初と二番目のコマンドでマニフェストファイルを作成・署名します。
上でも書きましたが、署名後にpublish/ApplicationFiles/IconSample_0.0.0.1内のモジュールは変更できません。

New Applicationオプションの説明
オプション、引数 説明
-ToFile %BIN_DIR%\%APP_NAME%.exe.manifest マニフェストファイル作成場所
-Name %APP_NAME%              アプリケーション名
-Version %NEW_VERSION%           新バージョン
-FromDirectory %BIN_DIR%          モジュール格納フォルダ
-Processor "x86"              アプリケーションを実行するマイクロプロセッサのアーキテクチャ。付けないと警告が出たので。
-IconFile myicon.ico            すべてのプログラムやスタート画面のピン留めで指定したアイコンファイルが使われる。
-FromDirectory内に指定したアイコンファイルが入っている必要がある。
(Mage.exeのドキュメントには完全パスを指定するとか書かれているが嘘)

Signのオプションはほぼ固定となるので割愛します。

3番目と4番目のコマンドで更新用配置ファイルを作成・署名します。
署名の前にpowershellが挟んでありますが、
これで更新確認タイミングをアプリケーション終了時から起動時に変更しています。
なんで-Install trueにするとアプリケーション終了時になるんだろうか、謎。

(単純な文字列置換なので、あまりよろしくないやつ)

Updateオプションの説明
オプション、引数 説明
-Update %OUTPUT_DIR%\%APP_NAME%.application   初期配置ファイル、または前回の更新用配置ファイルを指定
-AppManifest %BIN_DIR%\%APP_NAME%.exe.manifest 署名したマニフェストファイルを指定
-Version %NEW_VERSION%             新バージョン(マニフェストファイルと合わせる必要がある)
-ProviderURL %PROVIDER_URL% アプリケーションの更新チェックURLを指定
-IncludeProviderURL true -ProviderURLの値が含まれているかどうか(上記で指定しているのでtrue)
-Processor "x86"                -New Applicationと同じ
-Publisher "sunotora"              発行元。プログラムの追加と機能に表示されたり、すべてのアプリ - 発行元 - アプリ名に登録されたりする
-MinVersion %NEW_VERSION%(おまけ)        更新確認時に実行中アプリバージョンがこのバージョン以下だったら強制更新される配置ファイルが作成できる

Mage.exeの詳しいドキュメントはこちら
Mage.exe (マニフェストの生成および編集ツール) | Microsoft Docs

IconSample.applicationをIconSample_0.0.0.1.applicationにコピーしているのは念のため。
あとはIconSample.applicationとApplicationFiles\IconSample_0.0.0.1の中身をPROVIDER_URLに配置すれば配布準備完了です。
※publishがContextRootみたいなイメージ
インストール自体はできあがったIconSample.applicationをその場所で実行することでも可能です。

Sample

バッチとかClickOnce用ファイルをgithubに置いてみました。
ツール場所の調整や証明書ファイルとかバージョンフォルダ作成、モジュール配置は各自行う必要がありますがClickOnceを試すことができるはずです。

GitHub - sunotora/FSharpIconSample

まとめ

FSharp プロジェクトでビルドしたexeをClickOnceで配布できるようになった。