[.NET, C#, Разработка под Windows] Пишем установщик на WixSharp. Плюшки, проблемы, возможности
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Каждый маломальский проект сталкивается с дистрибьюцией продукта. В нашем случае это коробочный вариант и так исторически сложилось, что мы предоставляем нашим заказчикам установщик, который должен сделать уйму всего в системе, тем самым упростив заказчику этап внедрения.В первой своей реинкарнации это было решение из множества приложений, которые дергали друг друга и все это подавалось под соусом InnoSetup. Масштабировать функционал уже не представлялось возможным. И мы пришли к решению пересесть на "новые рельсы" и тут понеслось…Знакомство с Wix, а затем и WixSharpВыбор пал на Wix. Но желающих писать xml скрипты Wix в команде не оказалось. Основной приоритет отдавали C#. И, ура, был замечен фреймворк называемый Wix# (WixSharp). По Wix# написано немало статей в сети и есть интересный перевод статьи на Хабре. В каждой статье авторы пытаются донести свой уникальный опыт и помочь читателям с пользой воспользоваться материалом. Поэтому и мы решили поделиться своим опытом с вами.Wix# позволяет реализовать большинство сценариев установки и обновления msi. Также, есть возможность дополнить функционал путем подключения wix расширений и описания новых сущностей Wix. Приятно было обнаружить возможность прикрутить WPF. Однако на начальном этапе мы приняли решение написать формы на WinForm. И в процессе мы выявили ряд важных моментов, про которые расскажем ниже.Особенности работы с WinFormПри проработке форм установщика мы поняли, что сделать простые, не перегруженные формы для наших потребностей невозможно. Поэтому каждая форма требовала лаконичного размещения контролов с учетом ограничений в размерах форм в msi.
Пример формыВ итоге по формам мы смогли более менее раскидать необходимый функционал. И его можно расширить, добавив еще пару-тройку новых форм. Но...Первое, что стало бросаться в глаза при добавлении новых форм, это то, что отрисовка была с "запозданием". Наблюдалось мерцание форм при переходе от одной к другой. Происходило это при подгонке размеров контролов во вновь инициализированной форме под разрешение текущего экрана. В этом оказалось особенность работы с WinForm msi.На данный момент мы остановились на этой реализации. А в ближайшее время запланировали переписать UI на WPF.Основной модульВ основном модуле мы описываем все необходимые опции проекта. В примерах разработчика Wix# обычно это один модуль, в котором перечислена реализация всех опций. Выглядит это так:
var binaries = new Feature("Binaries", "Product binaries", true, false);
var docs = new Feature("Documentation", "Product documentation (manuals and user guides)", true);
var tuts = new Feature("Tutorials", "Product tutorials", false);
docs.Children.Add(tuts);
var project =
new ManagedProject("ManagedSetup",
new Dir(@"%ProgramFiles%\My Company\My Product",
new File(binaries, @"Files\bin\MyApp.exe"),
new Dir("Docs",
new File(docs, "readme.txt"),
new File(tuts, "setup.cs"))));
project.Binaries = new[]
{
new Binary(new Id("EchoBin"), @"Files\Echo.exe")
};
project.Actions = new WixSharp.Action[]
{
new InstalledFileAction("registrator_exe", "/u", Return.check, When.Before, Step.InstallFinalize, Condition.Installed),
new InstalledFileAction("registrator_exe", "", Return.check, When.After, Step.InstallFinalize, Condition.NOT_Installed),
new PathFileAction(@"%WindowsFolder%\notepad.exe", @"C:\boot.ini", "INSTALLDIR", Return.asyncNoWait, When.After, Step.PreviousAction, Condition.NOT_Installed),
new ScriptAction(@"MsgBox ""Executing VBScript code...""", Return.ignore, When.After, Step.PreviousAction, Condition.NOT_Installed),
new ScriptFileAction(@"Files\Sample.vbs", "Execute" , Return.ignore, When.After, Step.PreviousAction, "NOT Installed"),
new BinaryFileAction("EchoBin", "Executing Binary file...", Return.check, When.After, Step.InstallFiles, Condition.NOT_Installed)
{
Execute = Execute.deferred
}
};
project.Properties = new[]
{
new Property("Gritting", "Hello World!"),
new Property("Title", "Properties Test"),
new PublicProperty("NOTEPAD_FILE", @"C:\boot.ini")
}
project.GUID = new Guid("6f330b47-2577-43ad-9095-1861ba25889b");
project.ManagedUI = ManagedUI.Default;
project.UIInitialized += Project_UIInitialized;
project.Load += msi_Load;
project.AfterInstall += msi_AfterInstall;
В своем проекте у нас получилось гораздо больше различных опций и их реализаций. Поэтому мы разнесли все опции по модулям и получили лаконичный вид:
var project = new ManagedProject(ProjectConstants.PROJECT_NAME)
{
GUID = new Guid(ProjectConstants.PROJECT_GUID),
Platform = Platform.x64,
UpgradeCode = new Guid(ProjectConstants.PROJECT_GUID),
InstallScope = InstallScope.perMachine,
Description = ProjectConstants.COMPANY_NAME,
Language = "ru-RU",
LocalizationFile = @"WixUI_ru-ru.wxl",
ControlPanelInfo = productInfo,
MajorUpgradeStrategy = upgradeStrategy,
MajorUpgrade = majorUpgrade,
DefaultRefAssemblies = RefAssembliesGenerator.InitializeRefAssemblies(),
GenericItems = GenericEntitiesGenerator.InitializeGenericEntities(),
Properties = PropertiesGenerator.InitializeProperties(),
Dirs = DirsGenerator.InitializeDirs(),
Binaries = BinariesGenerator.InitializeBinaries(),
Actions = ActionsGenerator.InitializeActions(),
ManagedUI = new ManagedUI(),
ReinstallMode = "amus"
};
Глобальные переменные msiВ нашем проекте мы столкнулись с необходимостью объявить не один десяток свойств (Property), которые, подобно глобальным переменным, могут использоваться практически во всех местах установки, как при работе с формами, так и при обработке Custom Action. Обращение к этим переменным происходит по их имени в текстовом виде. Например, объявив свойство new Property("Gritting", "Hello World!") в конструкторе проекта, далее, чтобы получить к этому свойству доступ, например, из диалога, нужно обратиться к Runtime.Session["Gritting "]Такое обращение к переменным требовало от разработчика помнить, как называется нужное ему свойство и в случае некорректного значения, ошибка была бы обнаружена только в runtime и при отработке именно того куска кода, где была допущена опечатка.В итоге мы решили переместить все свойства и их значения в enum и упростить работу с чтением и записью этих свойств. Объявление свойств стало выглядеть следующим образом:
public enum eProperties
{
[Value("Hello World!"))]
GRITTING,
[Value("Properties Test "))]
TITLE,
// перечисление других свойств
}
А сама генерация свойств на основе enum так:
public static class PropertiesGenerator
{
private static Property InizializeProperty(string propertyName, string propertyValue)
{
return new Property(new Id(propertyName), propertyName, propertyValue) { IsDeferred = true };
}
private static IList<T> ToTypedList<T>(Type entityType, Func<Enum, T> createFunc)
{
if (createFunc != null
&& entityType.IsEnum)
{
return Enum.GetValues(entityType)
.Cast<Enum>()
.Select(createFunc)
.ToList();
}
return null;
}
public static Property[] InitializeProperties()
{
return ToTypedList(typeof(eProperties),
e => InizializeProperty(e.ToString(), e.GetPropertyValue()))
.ToArray();
}
}
Обращение к свойствам из диалога тоже изменилось. На чтение стало this.GetData(GRITTING), а на запись this.SetData(GRITTING, “New value”), где GetData() и SetData() методы расширения для класса ManagedForm.Для обращения из Custom Action стало session.Data(GRITTING)MSI нужны зависимые библиотекиВ ходе работы над установщиком у нас появилась необходимость подключать дополнительные библиотеки (например для работы с СУБД Postgre). Сначала мы подключали все ручками, как было описано в документации Wix#:
project.DefaultRefAssemblies.Add("FontAwesome.Sharp.dll");
project.DefaultRefAssemblies.Add("Newtonsoft.Json.dll");
project.DefaultRefAssemblies.Add("ManagedOpenSsl.dll");
Однако из-за того, что стало возрастать количество зависимостей в проекте, мы решили не делать точечное добавление библиотек, а написали метод, который считывает список всевозможных dll из указанного ресурса:
private static List<string> GetResourceList(string resourcesDirPath) =>
Directory.GetFiles($@"{resourcesDirPath}")
.Where(file => file.EndsWith("dll"))
.ToList();
public static List<string> InitializeRefAssemblies() =>
GetResourceList(Application.StartupPath)
.Concat(GetResourceList("Resources"))
.ToList();
Инициализация каталоговC добавлением каталогов все оказалось, более или менее, очевидно и понятно. Указываем иерархию каталогов с добавлением в них необходимых артефактов и, по необходимости, фильтруем файлы по названиям и расширениям:
private static bool ServicePredicate(string file) => !file.EndsWith(".pdb");
private static IEnumerable<WixEntity> InitializeDirWixEntities(object dirName)
{
var items = new List<WixEntity>();
items.AddRange(new List<WixEntity>
{
new Dir("logs"),
new DirFiles($@"Sources\{dirName}\*.*", ServicePredicate)
});
return new[] { new Dir(dirName.ToString(), items.ToArray()) };
}
private static WixEntity[] InilizeDirItems() =>
new List<WixEntity>()
.Concat(InitializeDirWixEntities(FirstService))
.Concat(InitializeDirWixEntities(SecondService))
.Concat(InitializeDirWixEntities(ThirdService))
.Concat(InitializeDirWixEntities(FourthService))
.ToArray();
public static Dir[] InitializeDirs() =>
new[]
{
new Dir(@"%ProgramFiles%\CompanyName",
new Dir("distr",
new Dir(FluentMigrator, GetMigratorFileList("FluentMigrator")),
),
new Dir("app", InilizeDirItems())
)
};
Развертывание сайтов на IISОдна из задач нашего установщика - это развернуть определенное количество сайтов/сервисов на IIS, при этом должна учитываться возможность включения https с указанием сертификата ssl. Из коробки Wix# такого не умел (до выпуска версии 1.14.3). Поэтому была описана кастомная сущность Wix, которая использовала расширение WixExtension.Iis.Базовый класс, описывающий Wix сущность для создания сайта на IIS:
public abstract class IISWebSite: WixEntity, IGenericEntity
{
[Xml]
public string Condition;
[Xml]
public string Description;
[Xml]
public string IpAddress;
[Xml]
public string Port;
protected string Prefix { get; }
protected XElement Component { get; private set; }
protected string DirId { get; private set; }
protected string DirName { get; private set; }
protected string WebAppPoolId { get; private set; }
protected IISWebSite(string prefix)
{
Prefix = prefix;
}
public virtual void Process(ProcessingContext context)
{
context.Project.Include(WixExtension.IIs);
DirId = context.XParent.Attribute("Id").Value;
DirName = context.XParent.Attribute("Name").Value;
var componentId = $"{DirName}.{Prefix}.Component.Id";
Component = new XElement(XName.Get("Component"),
new XAttribute("Id", componentId),
new XAttribute("Guid", WixGuid.NewGuid(componentId)),
new XAttribute("KeyPath", "yes"));
context.XParent.Add(Component);
Component.Add(new XElement("Condition", new XCData(Condition)));
WebAppPoolId = $"{DirName}.{Prefix}.WebAppPool.Id";
Component.Add(new XElement(WixExtension.IIs.ToXName("WebAppPool"),
new XAttribute("Id", WebAppPoolId),
new XAttribute("Name", $"AppPool{Description}")
));
}
}
Далее класс-наследник, для cоздания сайта с подключением по http:
public sealed class IISWebSiteHttp : IISWebSite
{
public IISWebSiteHttp() : base("Http")
{
}
public override void Process(ProcessingContext context)
{
base.Process(context);
Component.Add(new XElement(WixExtension.IIs.ToXName("WebSite"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebSite.Id"),
new XAttribute("Description", Description),
new XAttribute("Directory", DirId),
new XElement(WixExtension.IIs.ToXName("WebAddress"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebAddress.Id"),
new XAttribute("IP", IpAddress),
new XAttribute("Port", Port),
new XAttribute("Secure", "no")
),
new XElement(WixExtension.IIs.ToXName("WebApplication"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebSiteApplication.Id"),
new XAttribute("WebAppPool", WebAppPoolId),
new XAttribute("Name", $"AppPool{Description}"))
));
}
}
И класс-наследник для создания сайта с подключением по https и с возможностью привязки сертификата ssl:
public sealed class IISWebSiteHttps : IISWebSite
{
private readonly bool _haveCertRef;
public IISWebSiteHttps(bool haveCertRef) : base(haveCertRef ? "HttpsCertRef" : "Https")
{
_haveCertRef = haveCertRef;
}
public override void Process(ProcessingContext context)
{
base.Process(context);
var siteConfig = new XElement(WixExtension.IIs.ToXName("WebSite"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebSite.Id"),
new XAttribute("Description", Description),
new XAttribute("Directory", DirId),
new XElement(WixExtension.IIs.ToXName("WebAddress"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebAddress.Id"),
new XAttribute("IP", IpAddress),
new XAttribute("Port", Port),
new XAttribute("Secure", "yes")
),
new XElement(WixExtension.IIs.ToXName("WebApplication"),
new XAttribute("Id", $"{DirName}.{Prefix}.WebSiteApplication.Id"),
new XAttribute("WebAppPool", WebAppPoolId),
new XAttribute("Name", $"AppPool{Description}")));
if (_haveCertRef)
{
siteConfig.Add(new XElement(WixExtension.IIs.ToXName("CertificateRef"),
new XAttribute("Id", IISConstants.CERTIFICATE_ID)));
}
Component.Add(siteConfig);
}
}
Все параметры сайта указываются на формах и передаются через глобальные переменные в новый экземпляр объекта.Через некоторое время в релиз Wix# добавили схожее расширение. Но, в отличии от реализации во фреймворке, наше расширение позволяет менять протокол у сайтов и делать привязку сертификата ssl.Инициализация наших объектов получилась следующая:
new List<WixEntity>
{
new IISWebSiteHttp
{
Condition = HttpSiteCondition, Description = siteName,
IpAddress = ipAddress,
Port = port
},
new IISWebSiteHttps(false)
{
Condition = HttpsSiteWithoutCertCondition, Description = siteName,
IpAddress = ipAddress,
Port = port
},
new IISWebSiteHttps(true)
{
Condition = HttpsSiteWithCertCondition, Description = siteName,
IpAddress = ipAddress,
Port = port
}
}
Создаем БД из msiВ предыдущей версии установщика, создание/обновление БД делало внешнее приложение. Так как Wix# позволяет запускать свои Custom Action, мы решили добавить возможность создания и обновления БД прямо в msi. В формах заносятся первичные данные по БД (провайдер, адрес сервер, название) и в Custom Action передаются эти данные через глобальные переменные:
[CustomAction]
public static ActionResult ExecMigratorRunner(Session session)
{
var workDir = session.Data(MIGRATOR_FILE_DIR);
var appCmdFile = $@"{workDir}{session.Data(MIGRATOR_FILE_NAME)}";
var args = session.Data(MIGRATOR_ARGS);
return ProccessHelper.RunApplication(appCmdFile, args);
}
Опытный читатель, скорее всего, спросит, почему не использовали коробочные решения Wix#? Например такое:
var project = new Project("MyProduct",
new Dir(@"%ProgramFiles%\My Company\My Product",
new File(@"Files\Bin\MyApp.exe")),
new User("James") { Password = "Password1" },
new Binary(new Id("script"), "script.sql"),
new SqlDatabase("MyDatabase0", ".\\SqlExpress", SqlDbOption.CreateOnInstall,
new SqlScript("script", ExecuteSql.OnInstall),
new SqlString("alter login Bryce with password = 'Password1'", ExecuteSql.OnInstall)
)
);
Все просто. В нашем проекте используется Fluent Migrator. И для разворачивания новой БД нужна только собранная библиотека, которую нужно вызвать через командную строку с параметрами, содержащими информацию по создаваемой БД. А поддержка различных провайдеров СУБД ложится уже на саму библиотеку.Сценарий обновления БД реализуется по всем канонам накатывания миграций.Какой еще функционал мы реализовали?
- Назначение прав доступа на папки приложения. Через CustomAction, т.к. коробочное решение раздает права в определенные момент установки, и мы не нашли возможности переиспользовать наработки Wix.
- Добавление пользователей БД и СУБД (через CustomAction, по тем же причинам).
- Добавление сертификата ssl в локальное хранилище.
- Привязка вновь добавленных сертификатов ssl к сайтам на IIS (через CustomAction).
- Принудительный запуск сайтов на IIS (через CustomAction).
- Обновление старой версии БД (до внедрения Fluent Migrator) путем запуска скрипта t-sql (через CustomAction).
- Проверка соединения с сервером БД (на форме).
- Проверка соединения с сервером RabbitMQ (на форме).
- Проверка сайтов и их адресов на уникальность на текущей IIS (на форме).
- Проверка необходимых компонентов на текущей машине (на форме).
А как же обновление?Да. Без этого сценария, установщик для нас и заказчиков стал бы бесполезным.Мы рассмотрели различные варианты обновлений доступных через msi и остановились на major upgrade. Нам нет необходимости хранить устаревшие исполняемые файлы и, например, выпускать патчи. Нас устроил вариант с полным удалением старой версии ПО и установкой новой версии.Wix# из коробки позволяет сделать достаточно неплохую схему обновления. Но, в нашем случае, все-таки пришлось добавить несколько своих событий.Во-первых, мы сделали сохранение глобальных переменных в реестр в зашифрованном виде, чтобы была связь с предыдущей установкой. Это дало возможность отображать ранее введенные данные в формах по установленной версии.Далее добавили собственную проверку установленной версии продукта, для совместимости с предыдущими версиями установщика (приложения установленные через InnoSetup не определялись msi как тот же продукт).И защитили БД от удаления в режиме обновления.Какие у нас планы по расширению функционала?
- Конфигурирование очередей на сервере RabbitMQ.
- Разворачивание сервиса на IIS, написанного на Python.
- Реализовать режим Modify средствами msi (возможность изменить введенные ранее в установщике настройки приложения).
- Переписать UI c WinForms на WPF.
Надеемся, что наш опыт будет полезен и ждем ваши вопросы и комментарии по нашей реализации.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, .NET, C#, Параллельное программирование] Многопоточность на низком уровне
- [Java, .NET, Go, Kubernetes] Мониторинг производительности приложений в Broadcom DX APM — анонс вебинара
- [Программирование, Assembler] Как писать на ассемблере в 2021 году
- [C#, TypeScript, Искусственный интеллект] Змейка, мышь и Гамильтон
- [.NET, C#] Консольная утилита погоды на C# с помощью .Net
- [Разработка мобильных приложений, Разработка игр, Unity] Не мешай ему взрослеть: как оптимизировать «растущее» приложение, чтобы оно оставалось удобным для пользователя
- [DevOps, Kubernetes] Argo CD: готов к труду и обороне в Kubernetes (перевод)
- [Системное администрирование, Разработка под Windows, Софт] Почему Windows около 20 секунд упорядочивает невидимые значки Рабочего стола? (перевод)
- [.NET, C#] Кодогенерацию с использованием Roslyn можно использовать и без перехода на .Net 5 (перевод)
- [Open source, Git, Agile, DevOps] Bебинар — Автоматизация процессов с GitLab CI/CD
Теги для поиска: #_.net, #_c#, #_razrabotka_pod_windows (Разработка под Windows), #_wix#, #_wixsharp, #_wix, #_msi, #_deployment, #_blog_kompanii_cross_technologies (
Блог компании Cross Technologies
), #_.net, #_c#, #_razrabotka_pod_windows (
Разработка под Windows
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:16
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Каждый маломальский проект сталкивается с дистрибьюцией продукта. В нашем случае это коробочный вариант и так исторически сложилось, что мы предоставляем нашим заказчикам установщик, который должен сделать уйму всего в системе, тем самым упростив заказчику этап внедрения.В первой своей реинкарнации это было решение из множества приложений, которые дергали друг друга и все это подавалось под соусом InnoSetup. Масштабировать функционал уже не представлялось возможным. И мы пришли к решению пересесть на "новые рельсы" и тут понеслось…Знакомство с Wix, а затем и WixSharpВыбор пал на Wix. Но желающих писать xml скрипты Wix в команде не оказалось. Основной приоритет отдавали C#. И, ура, был замечен фреймворк называемый Wix# (WixSharp). По Wix# написано немало статей в сети и есть интересный перевод статьи на Хабре. В каждой статье авторы пытаются донести свой уникальный опыт и помочь читателям с пользой воспользоваться материалом. Поэтому и мы решили поделиться своим опытом с вами.Wix# позволяет реализовать большинство сценариев установки и обновления msi. Также, есть возможность дополнить функционал путем подключения wix расширений и описания новых сущностей Wix. Приятно было обнаружить возможность прикрутить WPF. Однако на начальном этапе мы приняли решение написать формы на WinForm. И в процессе мы выявили ряд важных моментов, про которые расскажем ниже.Особенности работы с WinFormПри проработке форм установщика мы поняли, что сделать простые, не перегруженные формы для наших потребностей невозможно. Поэтому каждая форма требовала лаконичного размещения контролов с учетом ограничений в размерах форм в msi. Пример формыВ итоге по формам мы смогли более менее раскидать необходимый функционал. И его можно расширить, добавив еще пару-тройку новых форм. Но...Первое, что стало бросаться в глаза при добавлении новых форм, это то, что отрисовка была с "запозданием". Наблюдалось мерцание форм при переходе от одной к другой. Происходило это при подгонке размеров контролов во вновь инициализированной форме под разрешение текущего экрана. В этом оказалось особенность работы с WinForm msi.На данный момент мы остановились на этой реализации. А в ближайшее время запланировали переписать UI на WPF.Основной модульВ основном модуле мы описываем все необходимые опции проекта. В примерах разработчика Wix# обычно это один модуль, в котором перечислена реализация всех опций. Выглядит это так: var binaries = new Feature("Binaries", "Product binaries", true, false);
var docs = new Feature("Documentation", "Product documentation (manuals and user guides)", true); var tuts = new Feature("Tutorials", "Product tutorials", false); docs.Children.Add(tuts); var project = new ManagedProject("ManagedSetup", new Dir(@"%ProgramFiles%\My Company\My Product", new File(binaries, @"Files\bin\MyApp.exe"), new Dir("Docs", new File(docs, "readme.txt"), new File(tuts, "setup.cs")))); project.Binaries = new[] { new Binary(new Id("EchoBin"), @"Files\Echo.exe") }; project.Actions = new WixSharp.Action[] { new InstalledFileAction("registrator_exe", "/u", Return.check, When.Before, Step.InstallFinalize, Condition.Installed), new InstalledFileAction("registrator_exe", "", Return.check, When.After, Step.InstallFinalize, Condition.NOT_Installed), new PathFileAction(@"%WindowsFolder%\notepad.exe", @"C:\boot.ini", "INSTALLDIR", Return.asyncNoWait, When.After, Step.PreviousAction, Condition.NOT_Installed), new ScriptAction(@"MsgBox ""Executing VBScript code...""", Return.ignore, When.After, Step.PreviousAction, Condition.NOT_Installed), new ScriptFileAction(@"Files\Sample.vbs", "Execute" , Return.ignore, When.After, Step.PreviousAction, "NOT Installed"), new BinaryFileAction("EchoBin", "Executing Binary file...", Return.check, When.After, Step.InstallFiles, Condition.NOT_Installed) { Execute = Execute.deferred } }; project.Properties = new[] { new Property("Gritting", "Hello World!"), new Property("Title", "Properties Test"), new PublicProperty("NOTEPAD_FILE", @"C:\boot.ini") } project.GUID = new Guid("6f330b47-2577-43ad-9095-1861ba25889b"); project.ManagedUI = ManagedUI.Default; project.UIInitialized += Project_UIInitialized; project.Load += msi_Load; project.AfterInstall += msi_AfterInstall; var project = new ManagedProject(ProjectConstants.PROJECT_NAME)
{ GUID = new Guid(ProjectConstants.PROJECT_GUID), Platform = Platform.x64, UpgradeCode = new Guid(ProjectConstants.PROJECT_GUID), InstallScope = InstallScope.perMachine, Description = ProjectConstants.COMPANY_NAME, Language = "ru-RU", LocalizationFile = @"WixUI_ru-ru.wxl", ControlPanelInfo = productInfo, MajorUpgradeStrategy = upgradeStrategy, MajorUpgrade = majorUpgrade, DefaultRefAssemblies = RefAssembliesGenerator.InitializeRefAssemblies(), GenericItems = GenericEntitiesGenerator.InitializeGenericEntities(), Properties = PropertiesGenerator.InitializeProperties(), Dirs = DirsGenerator.InitializeDirs(), Binaries = BinariesGenerator.InitializeBinaries(), Actions = ActionsGenerator.InitializeActions(), ManagedUI = new ManagedUI(), ReinstallMode = "amus" }; public enum eProperties
{ [Value("Hello World!"))] GRITTING, [Value("Properties Test "))] TITLE, // перечисление других свойств } public static class PropertiesGenerator
{ private static Property InizializeProperty(string propertyName, string propertyValue) { return new Property(new Id(propertyName), propertyName, propertyValue) { IsDeferred = true }; } private static IList<T> ToTypedList<T>(Type entityType, Func<Enum, T> createFunc) { if (createFunc != null && entityType.IsEnum) { return Enum.GetValues(entityType) .Cast<Enum>() .Select(createFunc) .ToList(); } return null; } public static Property[] InitializeProperties() { return ToTypedList(typeof(eProperties), e => InizializeProperty(e.ToString(), e.GetPropertyValue())) .ToArray(); } } project.DefaultRefAssemblies.Add("FontAwesome.Sharp.dll");
project.DefaultRefAssemblies.Add("Newtonsoft.Json.dll"); project.DefaultRefAssemblies.Add("ManagedOpenSsl.dll"); private static List<string> GetResourceList(string resourcesDirPath) =>
Directory.GetFiles($@"{resourcesDirPath}") .Where(file => file.EndsWith("dll")) .ToList(); public static List<string> InitializeRefAssemblies() => GetResourceList(Application.StartupPath) .Concat(GetResourceList("Resources")) .ToList(); private static bool ServicePredicate(string file) => !file.EndsWith(".pdb");
private static IEnumerable<WixEntity> InitializeDirWixEntities(object dirName) { var items = new List<WixEntity>(); items.AddRange(new List<WixEntity> { new Dir("logs"), new DirFiles($@"Sources\{dirName}\*.*", ServicePredicate) }); return new[] { new Dir(dirName.ToString(), items.ToArray()) }; } private static WixEntity[] InilizeDirItems() => new List<WixEntity>() .Concat(InitializeDirWixEntities(FirstService)) .Concat(InitializeDirWixEntities(SecondService)) .Concat(InitializeDirWixEntities(ThirdService)) .Concat(InitializeDirWixEntities(FourthService)) .ToArray(); public static Dir[] InitializeDirs() => new[] { new Dir(@"%ProgramFiles%\CompanyName", new Dir("distr", new Dir(FluentMigrator, GetMigratorFileList("FluentMigrator")), ), new Dir("app", InilizeDirItems()) ) }; Развертывание сайтов на IISОдна из задач нашего установщика - это развернуть определенное количество сайтов/сервисов на IIS, при этом должна учитываться возможность включения https с указанием сертификата ssl. Из коробки Wix# такого не умел (до выпуска версии 1.14.3). Поэтому была описана кастомная сущность Wix, которая использовала расширение WixExtension.Iis.Базовый класс, описывающий Wix сущность для создания сайта на IIS: public abstract class IISWebSite: WixEntity, IGenericEntity
{ [Xml] public string Condition; [Xml] public string Description; [Xml] public string IpAddress; [Xml] public string Port; protected string Prefix { get; } protected XElement Component { get; private set; } protected string DirId { get; private set; } protected string DirName { get; private set; } protected string WebAppPoolId { get; private set; } protected IISWebSite(string prefix) { Prefix = prefix; } public virtual void Process(ProcessingContext context) { context.Project.Include(WixExtension.IIs); DirId = context.XParent.Attribute("Id").Value; DirName = context.XParent.Attribute("Name").Value; var componentId = $"{DirName}.{Prefix}.Component.Id"; Component = new XElement(XName.Get("Component"), new XAttribute("Id", componentId), new XAttribute("Guid", WixGuid.NewGuid(componentId)), new XAttribute("KeyPath", "yes")); context.XParent.Add(Component); Component.Add(new XElement("Condition", new XCData(Condition))); WebAppPoolId = $"{DirName}.{Prefix}.WebAppPool.Id"; Component.Add(new XElement(WixExtension.IIs.ToXName("WebAppPool"), new XAttribute("Id", WebAppPoolId), new XAttribute("Name", $"AppPool{Description}") )); } } public sealed class IISWebSiteHttp : IISWebSite
{ public IISWebSiteHttp() : base("Http") { } public override void Process(ProcessingContext context) { base.Process(context); Component.Add(new XElement(WixExtension.IIs.ToXName("WebSite"), new XAttribute("Id", $"{DirName}.{Prefix}.WebSite.Id"), new XAttribute("Description", Description), new XAttribute("Directory", DirId), new XElement(WixExtension.IIs.ToXName("WebAddress"), new XAttribute("Id", $"{DirName}.{Prefix}.WebAddress.Id"), new XAttribute("IP", IpAddress), new XAttribute("Port", Port), new XAttribute("Secure", "no") ), new XElement(WixExtension.IIs.ToXName("WebApplication"), new XAttribute("Id", $"{DirName}.{Prefix}.WebSiteApplication.Id"), new XAttribute("WebAppPool", WebAppPoolId), new XAttribute("Name", $"AppPool{Description}")) )); } } public sealed class IISWebSiteHttps : IISWebSite
{ private readonly bool _haveCertRef; public IISWebSiteHttps(bool haveCertRef) : base(haveCertRef ? "HttpsCertRef" : "Https") { _haveCertRef = haveCertRef; } public override void Process(ProcessingContext context) { base.Process(context); var siteConfig = new XElement(WixExtension.IIs.ToXName("WebSite"), new XAttribute("Id", $"{DirName}.{Prefix}.WebSite.Id"), new XAttribute("Description", Description), new XAttribute("Directory", DirId), new XElement(WixExtension.IIs.ToXName("WebAddress"), new XAttribute("Id", $"{DirName}.{Prefix}.WebAddress.Id"), new XAttribute("IP", IpAddress), new XAttribute("Port", Port), new XAttribute("Secure", "yes") ), new XElement(WixExtension.IIs.ToXName("WebApplication"), new XAttribute("Id", $"{DirName}.{Prefix}.WebSiteApplication.Id"), new XAttribute("WebAppPool", WebAppPoolId), new XAttribute("Name", $"AppPool{Description}"))); if (_haveCertRef) { siteConfig.Add(new XElement(WixExtension.IIs.ToXName("CertificateRef"), new XAttribute("Id", IISConstants.CERTIFICATE_ID))); } Component.Add(siteConfig); } } new List<WixEntity>
{ new IISWebSiteHttp { Condition = HttpSiteCondition, Description = siteName, IpAddress = ipAddress, Port = port }, new IISWebSiteHttps(false) { Condition = HttpsSiteWithoutCertCondition, Description = siteName, IpAddress = ipAddress, Port = port }, new IISWebSiteHttps(true) { Condition = HttpsSiteWithCertCondition, Description = siteName, IpAddress = ipAddress, Port = port } } Создаем БД из msiВ предыдущей версии установщика, создание/обновление БД делало внешнее приложение. Так как Wix# позволяет запускать свои Custom Action, мы решили добавить возможность создания и обновления БД прямо в msi. В формах заносятся первичные данные по БД (провайдер, адрес сервер, название) и в Custom Action передаются эти данные через глобальные переменные: [CustomAction]
public static ActionResult ExecMigratorRunner(Session session) { var workDir = session.Data(MIGRATOR_FILE_DIR); var appCmdFile = $@"{workDir}{session.Data(MIGRATOR_FILE_NAME)}"; var args = session.Data(MIGRATOR_ARGS); return ProccessHelper.RunApplication(appCmdFile, args); } var project = new Project("MyProduct",
new Dir(@"%ProgramFiles%\My Company\My Product", new File(@"Files\Bin\MyApp.exe")), new User("James") { Password = "Password1" }, new Binary(new Id("script"), "script.sql"), new SqlDatabase("MyDatabase0", ".\\SqlExpress", SqlDbOption.CreateOnInstall, new SqlScript("script", ExecuteSql.OnInstall), new SqlString("alter login Bryce with password = 'Password1'", ExecuteSql.OnInstall) ) );
=========== Источник: habr.com =========== Похожие новости:
Блог компании Cross Technologies ), #_.net, #_c#, #_razrabotka_pod_windows ( Разработка под Windows ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:16
Часовой пояс: UTC + 5