Кратко. Как можно быстро создать инсталлер без использования всяких шаблонов, конструкторов, xml, ini и прочей херни. Только код и фреймворк Wix.

Зависимости:

  1. Net Framework 3.5 (Wix работает именно на этом фреймворке)
  2. Wix Toolset v.3
  3. WixSharp
  4. (Опционально) WixSharp Visual Studio Template

Создаем проект

И какой нибудь проект чтоб было из чего собирать. Хотя можно будет и указать просто любую папку или файлы какие стоит упаковать в инсталлер

Ну и собственно сам код.


internal class Program
{
    static void Main()
    {
        Compiler.EmitRelativePaths = false;
        Compiler.LightOptions += "-reusecab "; //doesn't delete them..      

        Run();
    }

    private static void Run()
    {
        Version version = new Version(1, 0, 0, 0);

        var msiProject = MsiPackage.CreateProject(version, Path.Combine(Directory.GetCurrentDirectory(), "..", "bin"),
            new string[] { "*.json", "*.dll", "*.exe", "*.txt" });

        msiProject.OutFileName = MsiPackage.ProductName;
        msiProject.OutDir = Directory.GetCurrentDirectory();
        msiProject.BuildMsi();
    }
}
internal class MsiPackage
{
    internal const string ProductId = "ProductPackageId";
    internal const string ProductName = "My Very Awesome App";
    private const string Description = "Моя супер крутая программа";
    internal const string CompanyName = "My Company";

    internal static readonly Guid Guid = new Guid("4FEFE8E1-F19C-40A7-8562-FDAF8D12EC63");
    internal static readonly Guid UpgradeCode = new Guid("2F380BC9-BFC3-479C-8B64-BA7DCA890636"); //DO NOT CHANGE UpgradeCode

    private static readonly string[] _defaultFilter = { "*.*" };

    /// <summary>
    /// Создать пакет MSI
    /// </summary>
    /// <param name="version">Версия продукта</param>
    /// <param name="sourceBaseDir">Путь до файлов для упаковки</param>
    /// <param name="filters">фильтр файлов для установки ["*.dll", "*.exe"]</param>
    /// <returns></returns>
    public static ManagedProject CreateProject(Version version, string sourceBaseDir, IEnumerable<string> filters)
    {
        var itemsDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? string.Empty;
        var project = new ManagedProject(ProductName)
        {
            GUID = Guid,
            Version = version,
            Description = Description,
            ProductId = GuidHelper.GenerateGuid(ProductName, version),
            Codepage = "1251",
            UpgradeCode = UpgradeCode,
            MajorUpgrade = new MajorUpgrade
            {
                AllowSameVersionUpgrades = true,
                Schedule = UpgradeSchedule.afterInstallInitialize,
                DowngradeErrorMessage = "A later version of [ProductName] is already installed. Setup will now exit."
            },
            Language = "ru-ru",
            InstallScope = InstallScope.perMachine,
            Encoding = Encoding.UTF8,
            LocalizationFile = Path.Combine(itemsDir, "Resources", "WixUI_ru-ru.wxl"),
            BackgroundImage = Path.Combine(itemsDir, "Resources", "banner_left.png"),
            BannerImage = Path.Combine(itemsDir, "Resources", "banner_up.png"),
            LicenceFile = Path.Combine(itemsDir, "Resources", "licence.rtf"),
#if (DEBUG)
            PreserveTempFiles = true
#endif
        };

        //custom set of standard UI dialogs
        project.ManagedUI = new ManagedUI();
        project.ManagedUI.InstallDialogs.Add(Dialogs.Welcome)
            .Add(Dialogs.Licence)
            .Add(Dialogs.InstallDir)
            .Add(Dialogs.Progress)
            .Add(Dialogs.Exit);

        project.ManagedUI.ModifyDialogs.Add(Dialogs.MaintenanceType)
            .Add(Dialogs.Features)
            .Add(Dialogs.Progress)
            .Add(Dialogs.Exit);

        if (filters == null) filters = _defaultFilter;

        //файлы для установки, ставим все *.* и все подпапки
        var installDir = new Dir(ProductName);

        installDir.AddDirFileCollections(filters.Select(f => new DirFiles(f)).ToArray());
        //recursive all folders
        foreach (var subDir in Directory.GetDirectories(sourceBaseDir))
            ProcessDir(installDir, "", subDir, filters);

        project.AddDir(new Dir("%ProgramFiles%", new Dir(CompanyName, installDir)));
        project.SourceBaseDir = sourceBaseDir;


        return project;

    }

    private static void ProcessDir(Dir installDir, string parentPath, string dirPath, IEnumerable<string> filters)
    {
        var dirName = Path.GetFileName(dirPath);
        if (string.IsNullOrEmpty(dirName))
            return;

        var dir = new Dir(dirName);
        dir.AddDirFileCollections(filters.Select(f => new DirFiles(Path.Combine(parentPath, dirName, f))).ToArray());

        installDir.AddDir(dir);

        foreach (var subDir in Directory.GetDirectories(dirPath))
            ProcessDir(dir, Path.Combine(parentPath, dirName), subDir, filters);
    }
}

Что здесь вообще происходит?

Мы создаем ManagedProject, это сам пакет MSI и описываем всю информацию о нём: название, имя производителя, версия, некоторые Id, файлы для установки и т.д. В этом коде приведён пример минимального описания для полноценного установщика. Его можно дополнить и многими другими параметрами, например добавить приложение в исключение фаервола, проверка установки зависимых программ (NET Framework, VC++ Libraries и т.д.), но это в следующий раз.

Самые важные параметры это ProductID и UpgradeCode
ProductID должен быть каждый раз новый, при сборке новой версии
UpgradeCode не должен никогда меняться

Это необходимо для того чтобы разные версии инсталлера одной программы распозновались как уникальные, и чтобы была возможность более новую версию установить поверх старой без необходимости удалять предыдшую. А так же есть возможность запретить делать Downgrade версии, то есть только обновление.

Для ProductID я сделал генератор в зависимости от имени и версии, поэтому за этим следить не надо.

Наш установщик будет упаковывать программу из решения SampleConsoleApp, которой указал путь сборки ../bin. А инсталлеру указываю эту папку и фильтры, какие файлы упаковывать. В моём случае это

".json", ".dll", ".exe", ".txt"

Более подробно проект можно посмотреть здесь

В следующий раз попробуем усложнить наш инсталлер.