この記事は公開から2年以上経過しています。
Emotet検知ツールを毎日実行する運用ルールのある会社も少なくないと思いますが、EmoCheckにはバージョンチェックや自己アップデート機能がないため、定期的に自分で最新リリースをチェックしてアップデートする必要があるなど少々面倒です。
また、私がEmoCheckを定期実行しているPCはHDDのためなのか、EmoCheckスキャン中はI/O負荷が高すぎてPCの調子が悪くなってしまうことから、EmoCheckの自動更新およびEmoCheckをアイドル優先度のプロセスとして実行できる簡易ツールを作成してみました。
本ツールは2022.5.26時点のものです。
EmoCheckの仕様やGitHubのリポジトリ構成などが変わると正しく機能しなくなる可能性がありますので、予めご了承ください(As Is)。
エラーが発生した場合はログを記録して処理を終了します(EmoCheckは実行しない)。
サンプルソースコード(C#)
ビルド環境は Visual Studio 2022(or 2019) .NET Framework 4.7.2です。
(当初.NET 6で作成しましたがバイナリファイルサイズが大きすぎたので急遽変更。)
ソースとバイナリ一式をこちらに用意しましたので、宜しければお使い下さい。VirusTotalにてウイルスチェック済です。
// EXCEEDSYSTEM EmoCheckUpdater
// https://www.exceedsystem.net/2022/05/26/how-to-update-emocheck-automatically
// License: MIT
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
namespace EmoCheckUpdater
{
internal static class Program
{
private const string APP_NAME = "EmoCheckUpdater";
private const string UPDATE_INFO_FILENAME = "updateinfo.json";
private const string EMOCHECK_FILENAME = "emocheck.exe";
private const string LOG_FILENAME = "emocheckupdater.log";
private const string EMOCHECK_API_URL = "https://api.github.com/repos/JPCERTCC/EmoCheck/releases/latest";
private const string USERAGENT_NAME = "Mozilla/5.0 (Windows NT 10.0)";
private const ProcessPriorityClass EMOCHECK_PROCESS_PRIORITY = ProcessPriorityClass.Idle;
private static readonly string MUTEX_NAME = $@"Global\MTX{Assembly.GetEntryAssembly().GetCustomAttribute<GuidAttribute>().Value}";
[STAThread]
public static int Main(string[] args)
{
if (Mutex.TryOpenExisting(MUTEX_NAME, out var mutex))
{
Log($"{APP_NAME} is already running.");
mutex.Dispose();
return -1;
}
using (mutex = new Mutex(true, MUTEX_NAME))
using (new Cleanup(() => mutex.ReleaseMutex()))
{
string ARCH = String.Empty;
switch (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"))
{
case "x86":
ARCH = "x86";
break;
case "AMD64":
ARCH = "x64";
break;
}
if (ARCH.Length == 0)
{
Log("Unsupported processor architecture.");
return -1;
}
try
{
Log($"{APP_NAME} has started.");
var updateInfo = new UpdateInfo();
if (!File.Exists(UPDATE_INFO_FILENAME))
{
File.WriteAllText(UPDATE_INFO_FILENAME, JsonSerializer.Serialize<UpdateInfo>(updateInfo));
}
updateInfo = JsonSerializer.Deserialize<UpdateInfo>(File.ReadAllText(UPDATE_INFO_FILENAME));
if (updateInfo == null)
{
Log($"'{UPDATE_INFO_FILENAME}' is invalid data structure.");
return -1;
}
Latest latest = null;
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("User-Agent", USERAGENT_NAME);
latest = httpClient.GetFromJsonAsync<Latest>(EMOCHECK_API_URL).Result;
if (latest == null)
{
Log("Failed to get EmoCheck update information.");
return -1;
}
if (latest.tag_name != updateInfo.CurrentTagName)
{
if (latest.assets == null || latest.assets.Length == 0)
{
Log("Failed to get the tag information of EmoCheck.");
return -1;
}
var downloadUrl = latest.assets.First(asset => asset.browser_download_url.EndsWith($"{ARCH}.exe"))?.browser_download_url;
if (string.IsNullOrEmpty(downloadUrl))
{
Log($"Failed to get EmoCheck download URL.");
return -1;
}
var emocheckBin = httpClient.GetByteArrayAsync(downloadUrl).Result;
if (emocheckBin == null)
{
Log($"Failed to download the latest version of EmoCheck.");
return -1;
}
using (var fs = new FileStream(EMOCHECK_FILENAME, FileMode.Create, FileAccess.Write))
fs.Write(emocheckBin, 0, emocheckBin.Length);
Log($"Downloaded the latest version of EmoCheck. ({downloadUrl})");
updateInfo.CurrentTagName = latest.tag_name;
var js = JsonSerializer.Serialize<UpdateInfo>(updateInfo);
File.WriteAllText(UPDATE_INFO_FILENAME, js);
}
}
var emoCheckProcessStartupInfo = new ProcessStartInfo
{
UseShellExecute = false,
FileName = Path.Combine(Environment.CurrentDirectory, EMOCHECK_FILENAME),
Arguments = string.Join(" ", args),
CreateNoWindow = args.Select(s => s.ToUpper()).Contains("/QUIET"),
};
using (var emoCheckProcess = Process.Start(emoCheckProcessStartupInfo))
{
if (emoCheckProcess == null)
{
Log("Failed to start EmoCheck.");
return -1;
}
Log($"EmoCheck has started. ({latest.tag_name})");
emoCheckProcess.PriorityClass = EMOCHECK_PROCESS_PRIORITY;
emoCheckProcess.WaitForExit();
Log($"EmoCheck has completed.");
}
}
catch (Exception ex)
{
Log($"An unexpected error has occurred. ({ex.Message.Replace("\r\n", "␍␊")})");
return -1;
}
Log($"{APP_NAME} has completed.");
return 0;
}
}
private static void Log(string msg)
{
try
{
using (var proc = Process.GetCurrentProcess())
File.AppendAllText(LOG_FILENAME, $"{DateTime.Now:yyyy.MM.dd HH:mm:ss.fff} ({proc.Id}) {msg}\n");
}
catch { }
}
internal sealed class Cleanup : IDisposable
{
private readonly Action _cleanupAction;
public Cleanup(Action cleanUpAction) => _cleanupAction = cleanUpAction;
public void Dispose() => _cleanupAction();
}
#pragma warning disable IDE1006
internal sealed class Latest
{
public string tag_name { get; set; } = string.Empty;
public Asset[] assets { get; set; } = Array.Empty<Asset>();
public sealed class Asset
{
public string updated_at { get; set; } = string.Empty;
public string browser_download_url { get; set; } = string.Empty;
}
}
#pragma warning restore IDE1006
internal sealed class UpdateInfo
{
public string CurrentTagName { get; set; } = string.Empty;
}
}
}
使い方
Binフォルダ内のEmoCheckUpdater.exe
を実行するだけです。
(私はタスクスケジューラーに登録して1日1回実行されるようにしています。)
EmoCheckに引数を渡したい場合はEmoCheckUpdaterへ引数として渡してください。
渡された引数は全てEmoCheckへパススルーされます。
例えばEmoCheckのコンソールウィンドウを表示せずバックグラウンドで実行したい場合にはEmoCheckUpdater.exe /quiet
のようなパラメータで実行します。
ツールの処理結果(EmoCheckの実行結果ではありません)は、カレントディレクトリ内のログファイルemocheckupdater.log
に記録しています。
動作について
-
初回起動時にGitHubから最新リリースバージョンのEmoCheck実行ファイルをダウンロードし、カレントディレクトリ内にファイル名
emocheck.exe
として保存します。 -
ダウンロードしたリリースバージョン名を、カレントディレクトリ内の設定ファイル
updateinfo.json
に保存します。 -
カレントディレクトリ内の
emocheck.exe
をアイドルプロセスとして起動します。 -
起動2回目以降は
updateinfo.json
に保存されているリリースバージョンとGitHubの最新リリースバージョンを比較し、異なる場合はGitHubから最新リリースバージョンのEmoCheck実行ファイルをダウンロードしてカレントディレクトリのemocheck.exe
を上書き(バージョンアップ)します。 -
カレントディレクトリの
emocheck.exe
を優先度低(アイドリングプロセス)で実行します。
参考ウェブサイトなど
- GitHub
JPCERTCC/EmoCheck
以上です。