◆ アップデータを作ってアップデートする
◆ 別 exe だから アップデート対象を終了させれば消せるので複数バージョン残さなくていい

前記事では 自分自身をアップデートさせようとしても 実行中に自分を消せないので 上書きじゃなくバージョン全部を残す方法でした
その辺のソフトはどうなってるの?と思って考えてみると アップデート機能があるものはだいたい別ソフトとしてアップデータがあります
アップデートを開始すると起動するやつです
インストーラみたいなものというか場合によっては兼用で そっちのソフトでアップデートを行います
別ソフトなので更新対象の exe を消したりできます
メインのアプリケーションが起動してるとダメなので 「◯◯を終了してください」 なんてアラートがたまに出ますね

やっぱり そういうアップデータを作ったほうがいいのかなと思って作ってみました
GUI で操作できるのとバックグラウンドで動くものを作ったのですが GUI の方は手元にソースがないので画面なしのものです

ソース

画面無いと書きましたが エラー時にダイアログを出すために WPF アプリになってます

[App.xaml]
<Application x:Class="updater.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:updater">
<Application.Resources>

</Application.Resources>
</Application>

画面を出さないので StartupUri を消しています
また ビルドアクションを Page に設定します

[App.xaml.cs]
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;

namespace updater
{
/// <summary>
/// </summary>
public partial class App : Application
{
public static readonly string target_appname = ConfigurationManager.AppSettings["target-appname"];
public static readonly string target_folder = ConfigurationManager.AppSettings["target-folder"];
public static readonly string resource_folder = ConfigurationManager.AppSettings["resource-folder"];
public static readonly string selfpath = Assembly.GetEntryAssembly().Location;
public static readonly string self_appname = Path.GetFileNameWithoutExtension(App.selfpath);
public static readonly bool cleanself;

/// <summary>
/// 初期化
/// 設定ファイルチェック
/// </summary>
static App()
{
if (!Directory.Exists(target_folder) || !Directory.Exists(resource_folder))
{
MessageBox.Show("フォルダが見つかりません。設定ファイルを確認してください。", "失敗", MessageBoxButton.OK, MessageBoxImage.Error);
finish();
return;
}
}

public static void Main()
{
var ready = shutdownRunningProcess();
if (!ready)
{
MessageBox.Show("アップデートは行われませんでした。", "失敗", MessageBoxButton.OK, MessageBoxImage.Error);
finish();
return;
}

var ok = update();
if (!ok)
{
MessageBox.Show("アップデートに失敗しました。", "失敗", MessageBoxButton.OK, MessageBoxImage.Error);
finish();
return;
}

finish();
}

/// <summary>
/// 実行中プロセス終了させる
/// </summary>
/// <param name="processes"></param>
public static bool shutdownRunningProcess()
{
var processes = Process.GetProcessesByName(target_appname);
if (processes.Length > 0)
{
var result = MessageBox.Show($"{target_appname} は起動中です。終了してもいいですか?", "確認", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
foreach (var p in processes)
{
p.CloseMainWindow();
p.WaitForExit(5000);

if (!p.HasExited)
{
p.Kill();
}
}
}
else
{
return false;
}
}
return true;
}

public static bool update()
{
try
{
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(resource_folder, target_folder, true);
}
catch
{
return false;
}
return true;
}

/// <summary>
/// 自分を削除して終了
/// </summary>
public static void finish()
{
var self_folder = Directory.GetParent(selfpath).FullName;
var ps_info = new ProcessStartInfo();
ps_info.FileName = @"c:\windows\system32\cmd.exe";
ps_info.Arguments = $@"/C timeout 3 /NOBREAK & taskkill /IM ""{self_appname}.exe"" /F & cd ""{self_folder}\.."" & rd ""{self_folder}"" /S /Q";
ps_info.CreateNoWindow = true;
Process.Start(ps_info);
Environment.Exit(0);
}
}
}

[App.config]
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="resource-folder" value="app" />
<add key="target-folder" value="C:\path\to\target-folder" />
<add key="target-appname" value="app1" />
</appSettings>
</configuration>

やってること

やってることは単純で 起動すると App.config から設定をロードします
設定にあるアプリ名のプロセスがあると 終了してもいいかの確認を表示します
「はい」 が選ばれるとプロセスを終了させます

あとはターゲットのフォルダにアップデートファイルをコピーして アップデータ自身を削除します
アップデータ自身を削除できるならアップデータ自体が要らないような気もしますが 削除する方法は別プロセスでコマンドプロンプトをバックグラウンドに起動して アップデータのプログラムが終了した後にそれを削除するというものです
なのでフォルダを消すくらいな単純なものならともかく 複雑なことはあんまりしたくないところです

⇩ のようなつくりの zip にして配布して

updater-v2.0.zip/
- updater.exe
- updater.exe.config
- app/

解凍後に updater.exe を実行すれば app フォルダが指定の場所にコピーされるので 特定のアプリケーションじゃなくても使えます
使い回せるという点でもアップデータを別に作っておくほうが良さそうですね

updater.exe が自身のあるフォルダを消すので 自動実行ならともかくユーザが手動実行したら混乱しそうなところもあります
実行したものが全部消えてしまうのですからね
あと zip を解凍したフォルダそのままなら問題ないですが 他の場所に移動されると消えては消えないのも消してしまうかもしれません
自分で試してるときに 昔作ったものだからあまり覚えてなくて適当な場所に置いてやってみると全部消えて驚きました
ちょうどいらない物しか無いフォルダだったのでよかったものの あと一つ上の階層だったらユーザデータ全部消えてましたよ……

削除機能無いほうがいいかもとは思いましたが 自動削除なしでアップデータがずっと残るのも嫌ですし こういうツール作るときは注意するしかないのですよね
ちゃんと開発用 VM とかでやればいいのですが Windows は普段遣いのしか無いので 危険が隣り合わせです

アップデート方法

アップデートしたいアプリケーションでは 前の記事と同じ感じで新しいバージョンがあるかをチェックします
アップデートする場合は アップデータの zip をダウンロードして解凍し zip 自体は削除してから updater.exe を起動します
アプリケーションを終了する必要があるので そのまま終了してもいいですが アップデータから止めることもできるのでどっちでもいいです

作ったときは インストール場所が固定のものだったので気にしませんでしたが ユーザごとに好きな場所にインストールされてるかもしれないなら アップデータにインストール先を伝えるように修正したほうがいいかもしれません