🗿

C#のスクリプトでアプリのバージョン番号を一括更新

2021/09/25に公開

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

今かかわっているアプリがVisualStudio2019を使ってC#、C++のプロジェクトを含むVSのソリューションを使って開発をしているのだが、リリースの際、そこに含まれるexeやらdllのバージョンを一斉に更新する必要がある。
(今回の場合は、全部を同じVerにする)

開発が進んできて、含まれるプロジェクトの数が増えた=exeやdllの数が増えて、バージョンを振らないといけない数が増えたので、その作業が結構めんどくさい&抜け漏れ発生しそう。

どうにかしてメンドウ&抜け漏れをなくしたい。

やったこと

概要

普段C#を使って開発しているが、そのC#をスクリプトとして実行できる仕組みを利用して、各プロジェクトに含まれるバージョン情報を含むファイルを更新した。

具体的には、
■C#のプロジェクト
[ソリューションフォルダ\プロジェクトフォルダ\Properties\AssemblyInfo.cs]
にある下記の部分の数字を編集する。

[assembly: AssemblyVersion("9.9.9.9")]
[assembly: AssemblyFileVersion("9.9.9.9")]

■C++のプロジェクト
[ソリューションフォルダ\プロジェクトフォルダ\プロジェクト名.rc]
にある下記の部分の数字を編集する。

FILEVERSION 8,8,8,8
PRODUCTVERSION 8,8,8,8
VALUE "FileVersion", "8.8.8.8"
VALUE "ProductVersion", "8.8.8.8"

前提

.NET Core 3.1 or .NET 5.0 SDK.がインストールされていること。
MS githubより

処理の流れ

作ったVer更新ツールを使うときには、

  • 準備フェーズ
    • スクリプト(VerUpCsScript.csx)とバッチ(VerUp.bat)を、ソリューション内のどこかに置く
    • スクリプトを開いて、C#とC++のバージョン情報を含むファイルのパスのリストを相対パスで手入力する
  • 実行フェーズ
    • バッチを実行すると
    • dotnet-scriptをインストールして
    • スクリプトを実行する
      • スクリプト内で新しいVerがなにか聞かれるので入力すると
      • Verが更新される

コード例

C#スクリプトを呼んでくれる入口バッチ

VerUp.bat
rem @echo off

rem .NETのスクリプト機能をインストール
dotnet tool install -g dotnet-script

rem スクリプトを実行
dotnet script ./VerUpCsScript.csx

pause

C#スクリプト本体

VerUpCsScript.csx
using System;
using System.IO;
using System.Text.RegularExpressions;

// 事前ToDo
// dotnet tool install -g dotnet-script
// dotnet script ./jikken.csx

// 検索パスリスト(C#プロジェクト用)
string[] myPaths =
{
    @"..\PInvokeTest\Properties\AssemblyInfo.cs",
};

// 検索パスリスト(C++)
string[] myCppPath =
{
    @"..\PInvokeTestCpp\PInvokeTestCpp.rc",
};

// 正規表現(C#)
var csexp1 = "(^|(?<=\r\n))\\[assembly: AssemblyVersion\\(\".*\"\\)\\]";
var csexp2 = "(^|(?<=\r\n))\\[assembly: AssemblyFileVersion\\(\".*\"\\)\\]";

// 正規表現(C++)
var cppexp1 = " FILEVERSION .*";
var cppexp2 = " PRODUCTVERSION .*";
var cppexp3 = "VALUE \"FileVersion\", \".*\"";
var cppexp4 = "VALUE \"ProductVersion\", \".*\"";

//*******************************
// メイン処理
//*******************************

Console.WriteLine("更新したいバージョンを入力し、ENTを押してください。");
Console.WriteLine("exp : 1.2.3.4");
Console.WriteLine("");
Console.Write("Version? : ");

var upVer = Console.ReadLine();

// C#のバージョンを変換
foreach (var path in myPaths)
{
    var filestr = ReadStringFromFile(path, System.Text.Encoding.UTF8);

    var out1 = Regex.Replace(filestr, csexp1, "[assembly: AssemblyVersion(\"" + upVer + "\")]");
    var final = Regex.Replace(out1, csexp2, "[assembly: AssemblyFileVersion(\"" + upVer + "\")]");

    WriteStringToFile(path, final, System.Text.Encoding.UTF8);
}

// C++のバージョンを変換
foreach (var path in myCppPath)
{
    var filestr = ReadStringFromFile(path, System.Text.Encoding.Unicode);

    var out1 = Regex.Replace(filestr, cppexp1, " FILEVERSION " + upVer.Replace('.', ','));
    var out2 = Regex.Replace(out1, cppexp2, " PRODUCTVERSION " + upVer.Replace('.', ','));
    var out3 = Regex.Replace(out2, cppexp3, "VALUE \"FileVersion\", \"" + upVer + "\"");
    var final = Regex.Replace(out3, cppexp4, "VALUE \"ProductVersion\", \"" + upVer + "\"");

    WriteStringToFile(path, final, System.Text.Encoding.Unicode);
}

//*******************************
// メイン処理 終わり
//*******************************

/// <summary>
/// ファイルから文字列を読みだし
/// </summary>
/// <param name="path">パス</param>
/// <param name="enc">文字コード</param>
/// <returns></returns>
static string ReadStringFromFile(string path, System.Text.Encoding enc)
{
    string data = string.Empty;

    if (File.Exists(path))
    {
        Console.WriteLine("file find..");

        using (var fs = new FileStream(path, FileMode.Open))
        using (var sr = new StreamReader(fs, enc))
        {
            data = sr.ReadToEnd();
        }
    }
    return data;
}

/// <summary>
/// ファイルに文字列を書き込み
/// </summary>
/// <param name="path">パス</param>
/// <param name="data">書き込む文字列</param>
/// <param name="enc">文字コード</param>
static void WriteStringToFile(string path, string data, System.Text.Encoding enc)
{
    var dir = Path.GetDirectoryName(path);
    if (!Directory.Exists(dir))
    {
        Directory.CreateDirectory(dir);
    }

    using (var fs = new FileStream(path, FileMode.Open))
    using (var sw = new StreamWriter(fs, enc))
    {
        sw.Write(data);
        Console.WriteLine("file write ok..");
    }
}

本スクリプトを作るうえでの罠

C++のバージョン情報は下記のようになっているが、

FILEVERSION 8,8,8,8
PRODUCTVERSION 8,8,8,8
VALUE "FileVersion", "8.8.8.8"
VALUE "ProductVersion", "8.8.8.8"

上のFILEVERSION / PRODUCTVERSIONは数字の間を","で区切っているが、
下のFileVersion / ProductVersionは数字の間を"."で区切っている。

単に、正規表現で上の文言を検索して、"8.8.8.8"のところは単に入力した文字列で置き換えるようにして、ユーザーには"8.8.8.8"と入力してもらうようにしていたので、変換した後のrcファイルをVSが(","が"."になっているせいで)読み込み失敗してしまい困った。
(最後はごりっと","を"."に変換して対応)

作ってみた感想

普段使ってるC#でスクリプトかけるのは便利。
これで脱バッチしたい。
(これを使えば、powershellと同等のことができる、ということなんだろうか?)

参考

MSのdotnet-scriptのgithub
インストールのやり方も書いてる
https://github.com/filipw/dotnet-script

dotnet-scriptの使い方などは、↑のMSページと合わせて下記ページを参照。
https://ufcpp.net/study/csharp/cheatsheet/apscripting/

https://qiita.com/ohbashunsuke/items/b6fe5476217485110db1

https://qiita.com/Midoliy/items/a033b763399c242dc5c5

Discussion