sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# .Net Framework と Visual C 再配布パッケージのインストール確認

いまでも WPFWindows ネイティブアプリケーションを作るときに .NET Framework を使うことがあるのですが、古い PC やセットアップ不足の PC でうまく動かないことがありました。

発生したことのある原因は(だいたい)これ:

ちなみに、.NET Framework はバージョン 4.8 を最後にして、メジャーアップデートを終了することがアナウンスされています。今後は .NET Core になるはずです。

基本的にプラットフォームが Windows に限定された .NET Framework なんですが、そのわりにインストールされているバージョン情報をパッと確認(しづらかったり)できなかったり、同様に「Visual Studio 20XX Visual C++ 再頒布可能パッケージ」もインストールされているのかどうかわかりづらいので、とりあえずインストールの実行をしたという経験があるかもしれません。

このあたりの確認方法をメモ。

.NET Framework のバージョン確認

Microsoft は、バージョン情報の確認方法を「Microsoft Docs - How to: Determine which .NET Framework versions are installed」でまとめています。

レジストリー領域の話になるので、すぐにめんどくさいことになったと気づきます。しかも、パッとわからない。統一的な記録形式を守ってくれていないので、さらにめんどくさい。

f:id:shikaku_sh:20210406105222p:plain:w300 f:id:shikaku_sh:20210406105225p:plain:w300 f:id:shikaku_sh:20210406105228p:plain:w300 f:id:shikaku_sh:20210406105232p:plain:w300

取得するサンプルコードも併せて載せてありますが、コレクションで取得するようにすると、こんな感じ:

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PackageVersionChecker
{
    /// <summary>
    /// <see cref="DotNetVersion"/> クラスは、.NET のバージョンを確認するためのクラスです。
    /// </summary>
    public class DotNetVersion
    {
        /// <summary>
        /// どの .NET Framework バージョンがインストールされているか登録情報のコレクションを取得します。
        /// </summary>
        /// <returns>登録情報の組のコレクション (<see cref="Version"/>, SP)。登録情報が存在しないとき null を返却します。</returns>
        public static IEnumerable<Tuple<Version, string>> GetVersions()
        {
            var versions = new List<Tuple<Version, string>>();

             var v1 = GetVersion1To45FromRegistry();
             var v2 = GetVersionOver45FromRegistry();

            if (v1 != null)
            {
                versions.AddRange(v1);
            }

            if (v2 != null)
            {
                versions.Add(v2);
            }

            return versions.Count > 0 ? versions : null;
        }

        #region Public Methods

        /// <summary>
        /// .NET Framework 1 - 4.0 までのバージョンがインストールされているかどうかを示す登録情報を取得します。
        /// </summary>
        /// <returns>登録情報の組のコレクション(バージョン、SP)。登録情報が存在しないとき null を返却します。</returns>
        public static IEnumerable<Tuple<Version, string>> GetVersion1To45FromRegistry()
        {
            const string reg = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\";

            var netVersions = new List<Tuple<Version, string>>();

            using (var key = Registry.LocalMachine.OpenSubKey(reg))
            {
                foreach (var keyName in key.GetSubKeyNames())
                {
                    var version = "";
                    var sp = "";
                    var install = "";

                    if (keyName == "v4") continue;
                    if (!keyName.StartsWith("v")) continue;

                    var versionKey = key.OpenSubKey(keyName);

                    version = versionKey.GetValue("Version", "").ToString();
                    sp = versionKey.GetValue("SP", "").ToString();
                    install = versionKey.GetValue("Install", "").ToString();

                    // Version 2-3.5 対応
                    if (!string.IsNullOrEmpty(version))
                    {
                        if (!(string.IsNullOrEmpty(sp)) && install == "1")
                        {
                            netVersions.Add(Tuple.Create(new Version(version), sp));
                        }
                        else
                        {
                            netVersions.Add(Tuple.Create(new Version(version), ""));
                        }

                        continue;
                    }

                    // Version 4.0 対応
                    foreach (var subKeyName in versionKey.GetSubKeyNames())
                    {
                        var subKey = versionKey.OpenSubKey(subKeyName);

                        version = subKey.GetValue("Version", "").ToString();

                        if (!string.IsNullOrEmpty(version))
                        {
                            sp = subKey.GetValue("SP", "").ToString();
                        }

                        install = subKey.GetValue("Install").ToString();

                        if (string.IsNullOrEmpty(install))
                        {
                            netVersions.Add(Tuple.Create(new Version(version), ""));
                        }
                        else
                        {
                            if (!(string.IsNullOrEmpty(sp)) && install == "1")
                            {
                                netVersions.Add(Tuple.Create(new Version(version), sp));
                            }
                            else
                            {
                                netVersions.Add(Tuple.Create(new Version(version), ""));
                            }
                        }
                    }

                }

                return netVersions.Count > 0 ? netVersions : null;
            }

        }

        /// <summary>
        /// .NET Framework 1 - 4.0 までのバージョンがインストールされているかどうかを示す登録情報を取得します。
        /// </summary>
        /// <returns>登録情報の組(バージョン、SP)。登録情報が存在しないとき null を返却します。</returns>
        public static Tuple<Version, string> GetVersionOver45FromRegistry()
        {
            const string reg = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";

            using (RegistryKey ndpKey = Registry.LocalMachine.OpenSubKey(reg))
            {
                if (ndpKey == null) return null;

                var version = ndpKey.GetValue("Version")?.ToString() ?? "";

                if (!string.IsNullOrEmpty(version))
                {
                    return Tuple.Create(new Version(version), "");
                }
                else
                {
                    var result = int.TryParse(ndpKey.GetValue("Release")?.ToString() ?? "", out int release);

                    if (result)
                    {
                        var versionText = ToVersion(release);

                        if (!string.IsNullOrEmpty(versionText))
                        {
                            return Tuple.Create(new Version(versionText), "");
                        }
                    }
                }
            }

            return null;
        }

        #endregion

        /// <summary>
        /// リリースキーの番号からバージョンの番号を取得します。
        /// </summary>
        /// <param name="release">リリースキーの番号。</param>
        /// <returns>バージョンの番号。変換に失敗したときは "" を返却します。</returns>
        private static string ToVersion(int release)
        {
            if (release >= 528040) return "4.8";
            if (release >= 461808) return "4.7.2";
            if (release >= 461308) return "4.7.1";
            if (release >= 460798) return "4.7";
            if (release >= 394802) return "4.6.2";
            if (release >= 394254) return "4.6.1";
            if (release >= 393295) return "4.6";
            if (release >= 379893) return "4.5.2";
            if (release >= 378675) return "4.5.1";
            if (release >= 378389) return "4.5";

            return "";
        }
    }
}

出力結果は、こんな感じ:

f:id:shikaku_sh:20210406105340p:plain

.NET のバージョンは、いくつかのバージョンがインストールされていることがあるので、Microsoft 公式のサンプルコードでも複数個のバージョンが出力されています。

Visual Studio 20XX Visual C++ 再頒布可能パッケージの確認

再配布パッケージはいくつかのバージョンがあります。

なので、これも .NET と同じように、インストールされている再配布パッケージを一覧するような仕様がよいと思います。やりかたは色々あると思いますが、System.Management の参照を追加してやる方法はこんな感じ:

/// <summary>
/// <see cref="WinProduct"/> クラスは、Win32_Product のデータを表現するクラスです。
/// </summary>
public class WinProduct
{
    public int No { get; set; }

    public string IdentifyingNumber { get; set; }

    public string Name { get; set; }
}
private WinProduct[] GetWinProduct(string likeName)
{
    var products = new List<WinProduct>();
    using (var searcher = new ManagementObjectSearcher($"SELECT Name, IdentifyingNumber FROM Win32_Product WHERE Name LIKE '{likeName}'"))
    {
        int no = 1;

        foreach (ManagementObject obj in searcher.Get())
        {
            var name = obj["Name"] as string;
            var identify = obj["IdentifyingNumber"] as string;

            var product = new WinProduct()
            {
                No = no++,
                Name = name,
                IdentifyingNumber = identify,
            };

            products.Add(product);

            Console.WriteLine(name);
        }
    }

    return products.ToArray();
}

likeName の引数で、取得するインストール済のアプリケーション名を指定します。今回だと、こんな感じでどうか。(%SQL におけるワイルドカード(あいまい)の表現です。)

  • %Microsoft Visual C%

%Microsoft Visual C%Redistributable% だと、2019 などの新しいパッケージは名称がランタイムになっていることもあるのでヒットしないかも。

private async void CheckRedistributablePackage()
{
    var products = await Task<WinProduct[]>.Run(() => GetWinProduct("%Microsoft Visual C%"));

    foreach (var product in products)
    {
        Console.WriteLine(product);
    }
}

このやり方には欠点があって、データ取得に数分かかってしまいます。foreach のところで、固まります。OS のアプリケーションのアンインストール一覧を表示するときでも表示に時間がかかりますが、そんな感じだと思います。

結果を出力するとこんな感じ:

f:id:shikaku_sh:20210406105411p:plain:w600

図でリスト表示してるのは、下記の自作したサンプルになります。

サンプル

今回のテストコードは GitHub に公開しています。

f:id:shikaku_sh:20210406105411p:plain:w600

おまけ(ランタイム is なに)

大丈夫だと思うけど、.NET Framework に限らず、ランタイムの意味を把握できていますか:

  • ランタイム (Rumtime)
    ソフトウェアを実行するためのもの
  • SDK (Software Development Kit)
    ソフトウェアを開発するためのもの

.NET Framework で作られていても、実行するためにはランタイムが必要だし、開発するためには SDK が必要という感じになります。

英語を確認すると意味がわかりやすいのですが、略して SDK と言われたり、カタカナでランタイムというと、初めて見聞きしたときにそのままだと用語の意味がわかりづらいかも。

開発をしてると、ランタイムエラー (Runtime Error) というエラーが発生することがありますよね。これもランタイムの意味を知っていないと、エラーの理由/原因に繋がりづらい。(カタカナ用語の無能さゆえ)

参考