自動アップデート機能を実装してみる

2023年2月7日

今回はmsiを使って簡易的によくある最新を取得して、その場でアップデートをする処理を考えていこうと思います。

仕組みはアプリケーションから自分のバージョンとサーバーで管理しているバージョンの差異を確認し、更新されているようであれば、msiからインストールを実行する。

インストーラーの準備

以下の記事でWindowsインストーラーの作成は以下の記事で公開しています。

Visual Studio Installerを使ってWindowsインストーラー(msi)の作成の仕方
Visual Studio Installerを使ってWindowsインストーラー(msi)の作成の仕方
今回は、Windowsインストーラーを作成する方法を紹介します。 Visual Studio Installe…
https://nomux2.net/create-msi-by-vsi/

EXEのバージョン情報を取得

フォームにバージョン情報を表示するコントロールを置きました。これで現在のバージョンを把握します。

更新する際は一度プログラムを終了させるため、Application.Exit()もしくはthis.Close()などを使用するかと思いますが、Form_Loadイベントで実行するとエラーになるので、Form_Shownイベントでやってみようと思います。

private void Form1_Shown(object sender, EventArgs e)
{
    int major = 0;
    int minor = 0;
    int build = 0;
    int revision = 0;

    //自分自身のバージョン情報を取得する
    System.Diagnostics.FileVersionInfo ver =
        System.Diagnostics.FileVersionInfo.GetVersionInfo(
        System.Reflection.Assembly.GetEntryAssembly().Location);

    major = ver.FileMajorPart;
    minor = ver.FileMinorPart;
    build = ver.FileBuildPart;
    revision = ver.FilePrivatePart;

    //自分自身のバージョン情報を取得する
    this.lblVer.Text = string.Format("Ver {0}.{1}.{2}.{3}", major, minor, build, revision);
}

表示されるようになりました。

バッチの作成

新バージョンが発見された場合、msiを実行し実行が終わったら再度アプリを起動させるためBatを作成します。

echo off

rem msiを実行終了するまでここで止まる
start /wait misexec /i OpenCVSampleInstaller.msi /passive /norestart

rem msi終了後再度プログラムを実行する
start "autoupdate" %1

exit 0

インストーラーの最新バージョン情報の取得

msiのバージョンではなく、msi内にあるexeのバージョンを取得する方法は私の知る限りないです。

なのでここは簡単に考えます。バージョン情報を入れたテキストファイルを用意します。
とりあえずサーバーに置いてみました。

ここで注意です。テキストファイルをアップロードするとき、気を付けないといけないのがASCIIモードで転送すると改行が「\r\n」から「\n」に変わってしまいます。

この状態だとBATが正しく動作しませんのでバイナリモードで転送するようにしてください。
FileZillaの場合は、転送メニューにあります。

バージョン情報であれば
https://nomux2.net/AutoUpdate/OpenCVSampleVersion.txt
をみるとアップロードしたファイルが確認できます。

これをプログラム上で取得できるように考えます。
更新する際は一度プログラムを終了させるため、Application.Exit()もしくはthis.Close()などを使用するかと思いますが、Form_Loadイベントで実行するとエラーになるので、Form_Shownイベントでやってみようと思います。

サーバー上にあるテキストファイルを読み込む

htmlと同様に以下のようにすれば取れるでしょう。

//バージョン情報URL
string version_url = "https://nomux2.net/AutoUpdate/OpenCVSampleVersion.txt";


//WebClientを作成
using (System.Net.WebClient wc = new System.Net.WebClient())
{
    //文字コードを指定
    wc.Encoding = System.Text.Encoding.UTF8;

    //データを文字列としてダウンロードする
    string source = wc.DownloadString(version_url);

    //ピリオドで配列にする
    server_version = source.Split('.');

    //後始末
    wc.Dispose();

    MessageBox.Show(source);
}

実行してみました。サーバーのファイルからバージョン情報が取れていますね。

バージョン比較をする

ではここからバージョンの比較を行い今のバージョンよりサーバーのテキストファイルに書かれているバージョンの方が新しいかチェックするプログラムを考えます。

サーバーのバージョンが低い場合は処理を行いません。

int srv_major = 0;
int srv_minor = 0;
int srv_build = 0;
int srv_revision = 0;

//配列数が足りない場合は抜ける
if (server_version.Length < 4) return;

//バージョン取得、失敗したら抜ける
if (int.TryParse(server_version[0], out srv_major) == false) return;
if (int.TryParse(server_version[1], out srv_minor) == false) return;
if (int.TryParse(server_version[2], out srv_build) == false) return;
if (int.TryParse(server_version[3], out srv_revision) == false) return;

//バージョンアップできるか比較する
bool ver_up_flg = false;
if (major < srv_major) ver_up_flg = true;
if (major == srv_major && minor < srv_minor) ver_up_flg = true;
if (major == srv_major && minor == srv_minor && build < srv_build) ver_up_flg = true;
if (major == srv_major && minor == srv_minor && build == srv_build && revision < srv_revision) ver_up_flg = true;

//バージョンアップの必要がない場合は抜ける
if (ver_up_flg == false) return;

このソースでいうと「ver_up_flg」がtrueの場合に、アップデート処理が走るということになります。

最新バージョンの取得

EXEからバージョンを取得するために以下の処理を行います。
ネット上にあるBATファイル、msiファイルをtmpフォルダにダウンロードします。

一時フォルダ(Temp)の取得

Tempフォルダを取得するには以下のように記述します。

//   C:\\Users\\nomux2\\AppData\\Local\\Temp\\
string temp_dir = System.IO.Path.GetTempPath();

一時フォルダにファイルをダウンロードする

//自動アップロード確認
if (MessageBox.Show("最新バージョンがあります。アップデートを開始してもよろしいでしょうか?", 
            this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
{
    return;
}

string msi_url = @"https://nomux2.net/AutoUpdate/OpenCVSampleInstaller.msi";
string bat_url = @"https://nomux2.net/AutoUpdate/OpenCVSampleUpdate.bat";
string temp_dir = System.IO.Path.GetTempPath();
string msi_file = string.Format("{0}{1}", temp_dir, @"OpenCVSampleInstaller.msi");
string bat_file = string.Format("{0}{1}", temp_dir, @"OpenCVSampleUpdate.bat");

//ファイルのダウンロード
//ユーザーインターフェイスを表示してダウンロードする
Microsoft.VisualBasic.Devices.Network network = new Microsoft.VisualBasic.Devices.Network();

//msiファイルのダウンロード、バッチファイルのダウンロード
network.DownloadFile(msi_url, msi_file);
network.DownloadFile(bat_url, bat_file);

バッチを呼び出す。

インストーラーバッチを呼び出します。

//ダウンロード完了確認
if (System.IO.File.Exists(msi_file) == false || System.IO.File.Exists(bat_file) == false)
{
    MessageBox.Show("最新情報の取得に失敗しました。");
    return;
}

//バージョン更新処理バッチ実行(パラメータに自分の場所を設定)終了まで待たない
System.Diagnostics.Process p = System.Diagnostics.Process.Start(bat_file, System.Reflection.Assembly.GetEntryAssembly().Location);

//インストーラーが動くため、アプリは一時終了
Application.Exit();

バッチ実行をしてプログラムを再起動したらtmpフォルダ内のファイルを削除します。

//いらないファイルは消す
if (System.IO.File.Exists(msi_file) == true) System.IO.File.Delete(msi_file);
if (System.IO.File.Exists(bat_file) == true) System.IO.File.Delete(bat_file);

テストしてみる

さて試してみようと思います。まず普通にインストールします。

次にバージョンを上げてインストーラーを作成し、最新のインストーラーはサーバーへ置きます。
注意点としてインストーラーがインストールしてくれるのはバージョンが上がっている場合です。

なので変えるべき点は2か所

自動アップデートが完了しバージョンが上がりました。

課題

msiexecをオプション「/i」で呼び出しているため、管理者で実行するためにUACの画面が出てきてします。これをなくしたいと思っているのです。

オプション「/a」で実行すると何も起こらず終わってしまいます。なんとかならないかな。

まあUACが出ているおかげでプログラムの終了する時間を十分に稼げているという認識もできなくはありません。私が業務的に差しさわりがないという判断でこれ以上は手を付けていません。

C#

Posted by nomux2