WiX хитрости и советы
Мы уже давно используем WiX, и, несмотря на обычные жалобы на простоту использования, все идет довольно хорошо. То, что я ищу, это полезный совет относительно:
- Настройка проекта WiX (макет, ссылки, шаблоны файлов)
- Интеграция WiX в решения и процессы сборки / выпуска
- Настройка установщиков для новых установок и обновлений
- Любые хорошие взломы WiX, которыми вы хотели бы поделиться
31 ответ
Храните переменные в отдельном
wxi
включить файл. Разрешает повторное использование, переменные быстрее находят и (при необходимости) облегчают манипулирование внешним инструментом.Определите переменные платформы для сборок x86 и x64
<!-- Product name as you want it to appear in Add/Remove Programs--> <?if $(var.Platform) = x64 ?> <?define ProductName = "Product Name (64 bit)" ?> <?define Win64 = "yes" ?> <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?> <?else ?> <?define ProductName = "Product Name" ?> <?define Win64 = "no" ?> <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?> <?endif ?>
Сохраните место установки в реестре, чтобы обновления могли найти правильное местоположение. Например, если пользователь устанавливает пользовательский каталог установки.
<Property Id="INSTALLLOCATION"> <RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)" Key="Software\Company\Product" Name="InstallLocation" /> </Property>
Примечание. Гуру WiX Rob Mensching опубликовал отличную запись в блоге, в которой более подробно рассматривается и устраняется крайний случай, когда свойства задаются из командной строки.
Примеры с использованием 1. 2. и 3.
<?include $(sys.CURRENTDIR)\Config.wxi?> <Product ... > <Package InstallerVersion="200" InstallPrivileges="elevated" InstallScope="perMachine" Platform="$(var.Platform)" Compressed="yes" Description="$(var.ProductName)" />
а также
<Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="$(var.PlatformProgramFilesFolder)"> <Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
Самый простой подход - это всегда делать крупные обновления, поскольку он позволяет как новые установки, так и обновления в одном MSI. Код UpgradeCode привязан к уникальному Guid и никогда не изменится, если мы не хотим обновить существующий продукт.
Примечание: в WiX 3.5 появился новый элемент MajorUpgrade, который делает жизнь еще проще!
Создание значка в "Установка и удаление программ"
<Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" /> <Property Id="ARPPRODUCTICON" Value="Company.ico" /> <Property Id="ARPHELPLINK" Value="http://www.example.com/" />
В выпусках сборки мы устанавливаем версии для наших установщиков, копируя файл msi в каталог развертывания. Пример этого с использованием цели wixproj, вызываемой из цели AfterBuild:
<Target Name="CopyToDeploy" Condition="'$(Configuration)' == 'Release'"> <!-- Note we append AssemblyFileVersion, changing MSI file name only works with Major Upgrades --> <Copy SourceFiles="$(OutputPath)$(OutputName).msi" DestinationFiles="..\Deploy\Setup\$(OutputName) $(AssemblyFileVersion)_$(Platform).msi" /> </Target>
Используйте тепло для сбора файлов с помощью шаблона Guid. Полезно, если вы хотите повторно использовать файлы WXS в нескольких проектах (см. Мой ответ о нескольких версиях одного и того же продукта). Например, этот пакетный файл автоматически собирает выходные данные RoboHelp.
@echo off robocopy ..\WebHelp "%TEMP%\WebHelpTemp\WebHelp" /E /NP /PURGE /XD .svn "%WIX%bin\heat" dir "%TEMP%\WebHelp" -nologo -sfrag -suid -ag -srd -dir WebHelp -out WebHelp.wxs -cg WebHelpComponent -dr INSTALLLOCATION -var var.WebDeploySourceDir
Там немного происходит,
robocopy
удаляет метаданные рабочей копии Subversion перед сбором;-dr
ссылка на корневую директорию установлена на нашем месте установки, а не на TARGETDIR по умолчанию;-var
используется для создания переменной, указывающей исходный каталог (выходные данные веб-развертывания).Простой способ включить версию продукта в заголовок приветствия с помощью Strings.wxl для локализации. ( saschabeaumont: saschabeaumont. Добавлено, поскольку этот замечательный совет скрыт в комментарии)
<WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization"> <String Id="WelcomeDlgTitle">{\WixUI_Font_Bigger}Welcome to the [ProductName] [ProductVersion] Setup Wizard</String> </WixLocalization>
Избавьте себя от боли и следуйте советам Вима Коэна по одному компоненту на файл. Это также позволяет вам опустить (или подстановочный знак
*
) GUID компонента.Роб Менчинг (Rob Mensching) имеет удобный способ быстро отследить проблемы в файлах журналов MSI, выполнив поиск
value 3
, Обратите внимание на комментарии, касающиеся интернационализации.При добавлении условных функций более интуитивно понятно установить уровень функции по умолчанию на 0 (отключен), а затем установить уровень условия на желаемое значение. Если вы установите уровень функции по умолчанию>= 1, уровень условия должен быть 0, чтобы отключить его, а это означает, что логика условия должна быть противоположна ожидаемой, что может сбивать с толку:)
<Feature Id="NewInstallFeature" Level="0" Description="New installation feature" Absent="allow"> <Condition Level="1">NOT UPGRADEFOUND</Condition> </Feature> <Feature Id="UpgradeFeature" Level="0" Description="Upgrade feature" Absent="allow"> <Condition Level="1">UPGRADEFOUND</Condition> </Feature>
Проверка, установлен ли IIS:
<Property Id="IIS_MAJOR_VERSION">
<RegistrySearch Id="CheckIISVersion" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp" Name="MajorVersion" Type="raw" />
</Property>
<Condition Message="IIS must be installed">
Installed OR IIS_MAJOR_VERSION
</Condition>
Проверка, установлена ли совместимость с метабазой IIS 6 в Vista+:
<Property Id="IIS_METABASE_COMPAT">
<RegistrySearch Id="CheckIISMetabase" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp\Components" Name="ADSICompatibility" Type="raw" />
</Property>
<Condition Message="IIS 6 Metabase Compatibility feature must be installed">
Installed OR ((VersionNT < 600) OR IIS_METABASE_COMPAT)
</Condition>
Храните все идентификаторы в отдельных пространствах имен
- Особенности начинаются с
F.
Примеры: F.Documentation, F.Binaries, F.SampleCode. - Компоненты начинаются с
C.
Например: C.ChmFile, C.ReleaseNotes, C.LicenseFile, C.IniFile, C.Registry - CustomActions являются
CA.
Например: CA.LaunchHelp, CA.UpdateReadyDlg, CA.SetPropertyX - Файлы
Fi.
- Каталоги
Di.
- и так далее.
Я считаю, что это очень помогает в отслеживании всех идентификаторов во всех категориях.
Фантастический вопрос. Я хотел бы увидеть некоторые лучшие практики.
У меня есть много файлов, которые я распространяю, поэтому я настроил свой проект на несколько исходных файлов wxs.
У меня есть исходный файл верхнего уровня, который я называю Product.wxs, который в основном содержит структуру для установки, но не фактические компоненты. Этот файл имеет несколько разделов:
<Product ...>
<Package ...>
<Media>...
<Condition>s ...
<Upgrade ..>
<Directory>
...
</Directory>
<Feature>
<ComponentGroupRef ... > A bunch of these that
</Feature>
<UI ...>
<Property...>
<Custom Actions...>
<Install Sequences....
</Package>
</Product>
Остальные файлы.wix состоят из фрагментов, содержащих ComponentGroups, на которые есть ссылки в теге Feature в Product.wxs. Мой проект содержит хорошую логическую группу файлов, которые я распространяю
<Fragment>
<ComponentGroup>
<ComponentRef>
....
</ComponentGroup>
<DirectoryRef>
<Component... for each file
....
</DirectoryRef>
</Fragment>
Это не идеально, мое чувство паука OO немного покалывает, потому что фрагменты должны ссылаться на имена в файле Product.wxs (например, DirectoryRef), но мне легче поддерживать этот один большой исходный файл.
Я хотел бы услышать комментарии по этому поводу, или если у кого-то тоже есть хорошие советы!
Установите флажок в диалоговом окне выхода, чтобы запустить приложение или файл справки.
...
<!-- CA to launch the exe after install -->
<CustomAction Id ="CA.StartAppOnExit"
FileKey ="YourAppExeId"
ExeCommand =""
Execute ="immediate"
Impersonate ="yes"
Return ="asyncNoWait" />
<!-- CA to launch the help file -->
<CustomAction Id ="CA.LaunchHelp"
Directory ="INSTALLDIR"
ExeCommand ='[WindowsFolder]hh.exe IirfGuide.chm'
Execute ="immediate"
Return ="asyncNoWait" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT"
Value="Launch MyApp when setup exits." />
<UI>
<Publish Dialog ="ExitDialog"
Control ="Finish"
Order ="1"
Event ="DoAction"
Value ="CA.StartAppOnExit">WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT</Publish>
</UI>
Если вы делаете это таким образом, "стандартный" внешний вид не совсем подходит. Флажок всегда серый фон, а диалог белый:
Одним из способов решения этой проблемы является указание собственного настраиваемого ExitDialog с другим флажком. Это работает, но, кажется, много работы, чтобы изменить цвет одного элемента управления. Другой способ решения этой проблемы - постобработка созданного MSI для изменения полей X,Y в таблице Control для этого конкретного элемента управления CheckBox. Код JavaScript выглядит так:
var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql = "UPDATE `Control` SET `Control`.`Height` = '18', `Control`.`Width` = '170'," +
" `Control`.`Y`='243', `Control`.`X`='10' " +
"WHERE `Control`.`Dialog_`='ExitDialog' AND " +
" `Control`.`Control`='OptionalCheckBox'";
var view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
Выполнение этого кода в виде сценария командной строки (с использованием cscript.exe) после создания MSI (из light.exe) приведет к созданию ExitDialog, который выглядит более профессионально:
Создание версий Live, Test, Training, ... с использованием одних и тех же исходных файлов.
В двух словах: создайте уникальный код UpgradeCode для каждого установщика и автоматически определите первый символ каждого идентификатора Guid для каждого установщика, оставив оставшиеся 31 уникальными.
Предпосылки
Предположения
- Переменные WiX используются для определения UpgradeCode, ProductName, InstallName.
- У вас уже есть работающий установщик. Я не буду пытаться это делать, пока вы не сделаете.
- Все ваши компоненты хранятся в одном файле (Components.wxs). Этот процесс будет работать, если у вас есть несколько файлов, просто будет больше работы.
Структура каталогов
- Setup.Library
- Все файлы wxs (Компоненты, Возможности, Диалоги Интерфейса,...)
- Common.Config.wxi (ProductCode = "*", ProductVersion, PlatformProgramFilesFolder,...)
- Setup.Live (wixproj)
- Свяжите все файлы Setup.Library с помощью "Добавить существующий файл" -> "Добавить как ссылку" (маленькая кнопка со стрелкой вниз рядом с кнопкой "Добавить" в Visual Studio)
- Config.wxi (имеет уникальный код обновления, ProductName, InstallName,...)
- Setup.Test,...
- согласно live, но Config.wxi настроен для тестовой среды.
Процесс
- Создайте каталог Setup.Library и переместите все ваши файлы wxs и wxi (кроме Config.wxi) из существующего проекта.
- Создайте Setup.Live, Setup.Test и т. Д. В соответствии с обычным wixproj.
- Добавьте цель BeforeBuild в wixproj в Setup.Live и т. Д., Чтобы выполнить задачу FileUpdate сообщества MSBuild для изменения Guids (я использовал A для Live, B для Test и C для обучения)
- Добавьте цель AfterBuild, чтобы вернуть Components.wxs Guids обратно в 0.
- С Orca убедитесь, что у каждого компонента в каждом MSI есть измененный guid.
- Убедитесь, что оригинальные направляющие восстановлены.
- Убедитесь, что каждый MSI устанавливает (и обновляет) правильный продукт и местоположение.
Пример Config.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Upgrade code should not change unless you want to install
a new product and have the old product remain installed,
that is, both products existing as separate instances. -->
<?define UpgradeCode = "YOUR-GUID-HERE" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?define ProductName = "Foo 64 Bit [Live]" ?>
<?else ?>
<?define ProductName = "Foo [Live]" ?>
<?endif ?>
<!-- Directory name used as default installation location -->
<?define InstallName = "Foo [Live]" ?>
<!-- Registry key name used to store installation location -->
<?define InstallNameKey = "FooLive" ?>
<?define VDirName = "FooLive" ?>
<?define AppPoolName = "FooLiveAppPool" ?>
<?define DbName = "BlahBlahLive" ?>
</Include>
Пример Config.Common.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Auto-generate ProductCode for each build, release and upgrade -->
<?define ProductCode = "*" ?>
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define ProductVersion = "1.0.0.0" ?>
<!-- Minimum version supported if product already installed and this is an upgrade -->
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define MinimumUpgradeVersion = "0.0.0.0" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<?define ProductManufacturer = "Foo Technologies"?>
<!-- Decimal Language ID (LCID) for the Product. Used for localization. -->
<?define ProductLanguage = "1033" ?>
<?define WebSiteName = "DefaultWebSite" ?>
<?define WebSitePort = "80" ?>
<?define DbServer = "(local)" ?>
</Include>
Пример Components.wxs
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- The pre-processor variable which allows the magic to happen :) -->
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?include ..\Setup.Library\Config.Common.wxi?>
<Fragment Id="ComponentsFragment">
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
<Component Id="ProductComponent" Guid="0XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" KeyPath="yes">
...
Примечание: я бы сейчас предложил оставить атрибут Guid вне компонента (эквивалент *
), используя один файл для каждого компонента и устанавливая файл в качестве ключевого пути. Это избавляет от необходимости звонить ModifyComponentsGuids
а также RevertComponentsGuids
цели показаны ниже. Это может быть возможно не для всех ваших компонентов.
Пример Setup.Live.wixproj
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
<CallTarget Targets="ModifyComponentsGuids" />
</Target>
<Target Name="AfterBuild">
<CallTarget Targets="RevertComponentsGuids" />
</Target>
<!-- Modify the first character of every Guid to create unique value for Live, Test and Training builds -->
<Target Name="ModifyComponentsGuids">
<FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="A" />
</Target>
<!-- Revert the first character of every Guid back to initial value -->
<Target Name="RevertComponentsGuids">
<FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="0" />
</Target>
Последние мысли
- Этот процесс также должен работать для создания разных установщиков для разных модулей слияния (Live, Test, ... как функции) для одного и того же установщика. Я использовал разные установщики, так как это казалось более безопасным вариантом, поэтому существует больший риск того, что кто-то может обновить Live вместо Training, если они находятся в одном окне и вы просто используете функции для различных модулей слияния.
- Если вы используете MSI для выполнения обновлений, а также новых установок, то есть подхода с использованием только основного обновления, и сохраняете место установки в реестре, не забудьте создать переменную для имени ключа для каждой установки.
- Мы также создаем переменные в каждом Config.wxi для включения уникальных имен виртуальных каталогов, пулов приложений, имен баз данных и так далее для каждого установщика.
ОБНОВЛЕНИЕ 1: Автоматически генерируемый компонент Guids устраняет необходимость вызова задачи FileUpdate, если вы создаете компонент с Guid="*" для каждого файла, устанавливая файл в качестве ключевого пути.
ОБНОВЛЕНИЕ 2: Одна из проблем, с которыми мы столкнулись, заключается в том, что если вы не генерируете Guid вашего компонента автоматически, а сборка завершается неудачно, то временные файлы необходимо удалить вручную.
ОБНОВЛЕНИЕ 3: Найден способ убрать зависимость от svn:externals и создания временных файлов. Это делает процесс сборки более гибким (и это лучший вариант, если вы не можете подстановить подстановочные знаки ваших гидов) и менее ломким при сбое сборки при свете или свечах.
ОБНОВЛЕНИЕ 4: Поддержка нескольких экземпляров, использующих преобразования экземпляров, в WiX 3.0+, безусловно, также стоит посмотреть.
Использование журнала Msi Diagnostic для получения подробной информации об ошибках
msiexec /i Package.msi /l*v c:\Package.log
куда
Package.msiэто название вашей посылки и
C:\Package.logгде вы хотите вывод журнала
Wix Intro Video
Да, и случайное вступительное видео "Wix" с участием "Мистера WiX" Роба Меншинга полезно для "концептуальной общей картины".
Используйте Javascript CustomActions, потому что они очень просты
Люди говорят, что Javascript не подходит для пользовательских действий MSI. Причины: трудно отлаживать, сложно сделать надежным. Я не согласна Это не сложно для отладки, конечно, не сложнее, чем C++. Это просто по-другому. Я обнаружил, что написание CustomActions в Javascript очень просто, намного проще, чем использование C++. Намного быстрее. И так же надежно.
Есть только один недостаток: Javascript CustomActions можно извлечь через Orca, тогда как C/C++ CA потребует обратного инжиниринга. Если вы считаете, что ваше установочное волшебство защищено интеллектуальной собственностью, вам следует избегать сценариев.
Если вы используете скрипт, вам просто нужно начать с какой-то структуры. Вот некоторые, чтобы вы начали.
Javascript "шаблонный" код для CustomAction:
//
// CustomActions.js
//
// Template for WIX Custom Actions written in Javascript.
//
//
// Mon, 23 Nov 2009 10:54
//
// ===================================================================
// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons = {
OkOnly : 0,
OkCancel : 1,
AbortRetryIgnore : 2,
YesNoCancel : 3
};
var Icons = {
Critical : 16,
Question : 32,
Exclamation : 48,
Information : 64
};
var MsgKind = {
Error : 0x01000000,
Warning : 0x02000000,
User : 0x03000000,
Log : 0x04000000
};
// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus = {
None : 0,
Ok : 1, // success
Cancel : 2,
Abort : 3,
Retry : 4, // aka suspend?
Ignore : 5 // skip remaining actions; this is not an error.
};
function MyCustomActionInJavascript_CA() {
try {
LogMessage("Hello from MyCustomActionInJavascript");
// ...do work here...
LogMessage("Goodbye from MyCustomActionInJavascript");
}
catch (exc1) {
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException(exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
// Pop a message box. also spool a message into the MSI log, if it is enabled.
function LogException(exc) {
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "CustomAction: Exception: 0x" + decimalToHexString(exc.number) + " : " + exc.message;
Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}
// spool an informational message into the MSI log, if it is enabled.
function LogMessage(msg) {
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "CustomAction:: " + msg;
Session.Message(MsgKind.Log, record);
}
// http://msdn.microsoft.com/en-us/library/d5fk67ky(VS.85).aspx
var WindowStyle = {
Hidden : 0,
Minimized : 1,
Maximized : 2
};
// http://msdn.microsoft.com/en-us/library/314cz14s(v=VS.85).aspx
var OpenMode = {
ForReading : 1,
ForWriting : 2,
ForAppending : 8
};
// http://msdn.microsoft.com/en-us/library/a72y2t1c(v=VS.85).aspx
var SpecialFolders = {
WindowsFolder : 0,
SystemFolder : 1,
TemporaryFolder : 2
};
// Run a command via cmd.exe from within the MSI
function RunCmd(command)
{
var wshell = new ActiveXObject("WScript.Shell");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var tmpdir = fso.GetSpecialFolder(SpecialFolders.TemporaryFolder);
var tmpFileName = fso.BuildPath(tmpdir, fso.GetTempName());
LogMessage("shell.Run("+command+")");
// use cmd.exe to redirect the output
var rc = wshell.Run("%comspec% /c " + command + "> " + tmpFileName, WindowStyle.Hidden, true);
LogMessage("shell.Run rc = " + rc);
// here, optionally parse the output of the command
if (parseOutput) {
var textStream = fso.OpenTextFile(tmpFileName, OpenMode.ForReading);
while (!textStream.AtEndOfStream) {
var oneLine = textStream.ReadLine();
var line = ParseOneLine(oneLine);
...
}
textStream.Close();
}
if (deleteOutput) {
fso.DeleteFile(tmpFileName);
}
return {
rc : rc,
outputfile : (deleteOutput) ? null : tmpFileName
};
}
Затем зарегистрируйте настраиваемое действие примерно так:
<Fragment>
<Binary Id="IisScript_CA" SourceFile="CustomActions.js" />
<CustomAction Id="CA.MyCustomAction"
BinaryKey="IisScript_CA"
JScriptCall="MyCustomActionInJavascript_CA"
Execute="immediate"
Return="check" />
</Fragmemt>
Вы можете, конечно, вставить столько функций Javascript, сколько захотите, для нескольких пользовательских действий. Один пример: я использовал Javascript для выполнения запроса WMI на IIS, чтобы получить список существующих веб-сайтов, на которые можно установить фильтр ISAPI. Этот список затем использовался для заполнения списка, показанного позже в последовательности пользовательского интерфейса. Все очень просто.
На IIS7 нет поставщика WMI для IIS, поэтому я использовал shell.Run()
подход для вызова appcmd.exe для выполнения работы. Легко.
Связанный вопрос: О Javascript CustomActions
Я удивлен, что никто не упомянул использование T4 для создания файла WXS во время сборки. Я узнал об этом с помощью Henry Lee @ New Age Solutions.
По сути, вы создаете пользовательскую задачу MSBuild для выполнения шаблона T4, и этот шаблон выводит WXS непосредственно перед компиляцией проекта Wix. Это позволяет (в зависимости от того, как вы его реализуете) автоматически включать все выходные сборки, полученные при компиляции другого решения (это означает, что вам больше не нужно редактировать wxs при каждом добавлении новой сборки).
Используя Heat.exe, чтобы разбить лицо и нанести "Epic Pwnage" на мучительно больших инсталляциях
Расширяя ответы si618 и Robert P о жаре.
Перевод:
(Использование высокой температуры, чтобы избежать ввода отдельных файлов в проект вручную и для автоматизации сборок для упрощения процесса в целом.)
Синтаксис WiX 2.0 Heat подробно
Для более новых версий (не все, что отличается от более старых версий, но есть потенциально раздражающие изменения синтаксиса....) перейдите в каталог Heat is from cmd.exe и просто введите Heat, но у меня есть один пример прямо здесь для помощи с более новыми версиями, если это необходимо.
Добавление следующего к вашему событию сборки в visual studio 2010.
(Щелкните правой кнопкой мыши Project-> Properties -> Build Events-> Pre-Build Events)
$(WIX)bin\heat.exe" dir "$(EnviromentVariable)" -cg GroupVariable -gg -scom -sreg -sfrag -
srd -dr INSTALLLOCATION -var env.LogicPath -out "$(FragmentDir)\FileName.wxs
-gg
Генерирует направляющие при нагреве (как при выполнении команды выше)
-scom
Не берите "COM-файлы"
-sreg
Не берите "Файлы реестра"
-sfrag
Не хватайте "Фрагменты"
-srd
Не берите "Корень Dir"
реж
dir указывает, что вы хотите, чтобы Heat просматривал папку
"$(EnviromentVariable)"
Имя переменной, которую вы бы добавили к переменным препроцессора в свойствах проекта (щелкните правой кнопкой мыши, перейдите в свойства) -> раздел Построение, где указано определение переменных препроцессора (предполагается, что Visual Studio 2010)
Пример: EnviromentVariable=C:\Project\ Bin\Debug;Нет двойных кавычек, но заканчивается точкой с запятой
-cg GroupVariable
ComponentGroup, на которую будут ссылаться из созданного фрагмента в основной файл wxs
FragmentDir
Каталог фрагментов, в котором будет храниться выходной фрагмент wxs
FileName.wxs
Имя файла
Полный учебник здесь, так чертовски полезно
Питер Тэйт уже показал, как вы можете определить определения многократного использования ComponentGroup в отдельных фрагментах wix. Некоторые дополнительные трюки, связанные с этим:
Псевдоним каталога
Фрагментам группы компонентов не нужно знать о каталогах, определенных основным продуктом wxs. В вашем фрагменте группы компонентов вы можете говорить о такой папке:
<DirectoryRef Id="component1InstallFolder">
...
</DirectoryRef>
Затем основной продукт может создать псевдоним одного из своих каталогов (например, "productInstallFolder"), например:
<Directory Id="productInstallFolder" Name="ProductName">
<!-- not subfolders (because no Name attribute) but aliases for parent! -->
<Directory Id="component1InstallFolder"/>
<Directory Id="component2InstallFolder"/>
</Directory>
График зависимостей
Элементы ComponentGroup могут содержать дочерние элементы ComponentGroupRef. Это замечательно, если у вас есть большой пул повторно используемых компонентов со сложным графом зависимостей между ними. Вы просто устанавливаете ComponentGroup в своем собственном фрагменте для каждого компонента и объявляете зависимости следующим образом:
<ComponentGroup Id="B">
<ComponentRef Id="_B" />
<ComponentGroupRef Id="A">
</ComponentGroup>
Если вы теперь ссылаетесь на группу компонентов "B" в своей настройке, так как это прямая зависимость вашего приложения, она автоматически вытянет группу компонентов "A", даже если автор приложения никогда не осознавал, что это была зависимость "B". Это "просто работает", пока у вас нет циклических зависимостей.
Многоразовый wixlib
Вышеупомянутая идея графа зависимостей работает лучше всего, если вы скомпилируете компоненты big-pool-o-reusable-компоненты в повторно используемый пакет wixlib с lit.exe. При создании настройки приложения вы можете ссылаться на этот wixlib во многом как файл wixobj. Компоновщик Candle.exe автоматически удалит все фрагменты, которые не "извлекаются" основным файлом (файлами) wxs.
Включая COM-объекты:
heat
генерирует все большинство (если не все) записи реестра и другую необходимую для них конфигурацию. Радуйтесь!
Включение управляемых COM-объектов (иначе, .NET или C# COM-объектов)
С помощью heat
на управляемом объекте COM даст вам почти полный документ wix.
Если вам не нужна библиотека, доступная в GAC (т. Е. Глобально доступная: в большинстве случаев это вам не нужно с вашими сборками.NET в любом случае - вы, вероятно, сделали что-то не так в этот момент, если она не предназначена для этого). общей библиотекой) вы захотите убедиться, что обновили CodeBase
раздел реестра для установки [#ComponentName]
, Если вы планируете установить его в GAC (например, вы создали новую классную библиотеку, которую все захотят использовать), вы должны удалить эту запись и добавить два новых атрибута в File
элемент: Assembly
а также KeyPath
, Сборка должна быть установлена на ".net" и KeyPath
должен быть установлен на "да".
Тем не менее, некоторым средам (особенно всем, что связано с управляемой памятью, например языками сценариев) также потребуется доступ к Typelib. Не забудьте запустить heat
на вашем typelib и включите его. heat
сгенерирует все необходимые ключи реестра. Как это круто?
Установка вC:\ProductName
Некоторые приложения должны быть установлены в C:\ProductName
или что-то подобное, но 99,9% (если не 100%) примеров в сети устанавливают в C:\Program Files\CompanyName\ProductName
,
Следующий код может быть использован для установки TARGETDIR
свойство к корню C:
диск (взят из списка пользователей WiX):
<CustomAction Id="AssignTargetDir" Property="TARGETDIR" Value="C:\" Execute="firstSequence" />
<InstallUISequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallExecuteSequence>
ПРИМЕЧАНИЕ. По умолчанию TARGETDIR
не указывает на C:\
! Это скорее указывает на ROOTDRIVE
который в свою очередь указывает на корень диска с наибольшим количеством свободного места ( см. здесь) - и это не обязательно C:
привод. Там может быть другой жесткий диск, раздел или USB-накопитель!
Затем где-то ниже вашего <Product ...>
как обычно, вам нужны следующие теги каталога:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="APPLICATIONFOLDER" Name="$(var.ProductName)">
<!-- your content goes here... -->
</Directory>
</Directory>
Переменные среды
При компиляции документов Wxs в код wixobj вы можете использовать переменные среды для определения различной информации. Например, допустим, вы хотите изменить, какие файлы будут включены в проект. Допустим, у вас есть переменная окружения RELEASE_MODE, которую вы установили непосредственно перед сборкой MSI (с помощью скрипта или вручную, это не имеет значения). В исходном коде wix вы можете сделать что-то вроде:
<define FILESOURCE = c:\source\output\bin\$(env.RELEASE_MODE) >
а затем позже в своем коде, используйте его на месте, чтобы на лету изменить ваш документ wxs, например:
<Icon Id="myicon.ico" SourceFile="$(var.FILESOURCE)" />
Использование специальной модели RobM "Помни свойство"
http://robmensching.com/blog/posts/2010/5/2/The-WiX-toolsets-Remember-Property-pattern
Редактирование диалогов
Хорошей способностью редактировать диалоги является использование SharpDevelop в версии 4.0.1.7090 (или выше). С помощью этого инструмента можно открыть, просмотреть и отредактировать автономное диалоговое окно (файлы wxs из источников WiX, например InstallDirDlg.wxs) в режиме конструктора.
Создание пользовательских действий для WIX, написанных в управляемом коде (C#) без Votive
Установка флага IIS enable32BitAppOnWin64 http://trycatchfail.com/blog/post/WiX-Snippet-change-enable32BitAppOnWin64.aspx
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallFinalize" />
<Custom Action="ConfigureAppPool" After="InstallFinalize" >
<![CDATA[NOT Installed AND VersionNT64 >= 600]]>
</Custom>
</InstallExecuteSequence>
<CustomAction Id="ConfigureAppPool" Return="check" Directory="TARGETDIR" ExeCommand="[SystemFolder]inetsrv\appcmd set apppool /apppool.name:[APPPOOLNAME] /enable32BitAppOnWin64:false" />
Мы показываем версию продукта где-то (крошечная) на первом экране графического интерфейса. Потому что люди склонны делать ошибки, выбирая правильную версию каждый раз. (И держать нас, разработчиков, ищущих целую вечность..)
Мы настроили TFSBuild для генерации преобразований (MST-файлов) с конфигурацией для наших различных сред. (Мы знаем обо всех средах, в которых нам нужно развернуться).
Поскольку оригинальное сообщение в блоге Гранта Холлидея не работает, я скопировал его содержимое здесь:
Задача MSBuild для создания файлов MSI Transform из XMLMarch 11 2008
В моем предыдущем посте я описал, как вы можете использовать файлы MSI Transform (*.mst), чтобы отделить параметры конфигурации, зависящие от среды, от стандартного пакета MSI.
Хотя это обеспечивает определенный уровень гибкости в вашей конфигурации, есть два недостатка файлов Transform:
- Они в двоичном формате
- Вы не можете "редактировать" или "просматривать" файл преобразования. Вы должны применить его или воссоздать его, чтобы увидеть, какие изменения он включает.
К счастью, мы можем использовать библиотеку объектов установщика Microsoft Windows (c:windowssystem32msi.dll), чтобы открывать "базы данных" MSI и создавать файлы преобразования.
Авторы снова возвращаются к Алексу Шевчуку - От MSI к WiX - Часть 7. Настройка установки с использованием Transforms, чтобы показать нам, как этого добиться с помощью VbScript. По сути, все, что я сделал, это взял пример Алекса и, используя Interop.WindowsInstaller.dll, я реализовал задачу MSBuild. Задача MSBuild
Загрузите исходный код и пример transforms.xml здесь (~7Kb Zipped VS2008 Solution)
Изменить "Готов к установке?" диалог (он же VerifyReadyDlg), чтобы предоставить сводку сделанных выборов.
Это выглядит так:
http://i46.tinypic.com/s4th7t.jpg
Сделайте это с помощью Javascript CustomAction:
Javascript код:
// http://msdn.microsoft.com/en-us/library/aa372516(VS.85).aspx
var MsiViewModify =
{
Refresh : 0,
Insert : 1,
Update : 2,
Assign : 3,
Replace : 4,
Merge : 5,
Delete : 6,
InsertTemporary : 7, // cannot permanently modify the MSI during install
Validate : 8,
ValidateNew : 9,
ValidateField : 10,
ValidateDelete : 11
};
// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons =
{
OkOnly : 0,
OkCancel : 1,
AbortRetryIgnore : 2,
YesNoCancel : 3
};
var Icons=
{
Critical : 16,
Question : 32,
Exclamation : 48,
Information : 64
}
var MsgKind =
{
Error : 0x01000000,
Warning : 0x02000000,
User : 0x03000000,
Log : 0x04000000
};
// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus =
{
None : 0,
Ok : 1, // success
Cancel : 2,
Abort : 3,
Retry : 4, // aka suspend?
Ignore : 5 // skip remaining actions; this is not an error.
};
function UpdateReadyDialog_CA(sitename)
{
try
{
// can retrieve properties from the install session like this:
var selectedWebSiteId = Session.Property("MSI_PROPERTY_HERE");
// can retrieve requested feature install state like this:
var fInstallRequested = Session.FeatureRequestState("F.FeatureName");
var text1 = "This is line 1 of text in the VerifyReadyDlg";
var text2 = "This is the second line of custom text";
var controlView = Session.Database.OpenView("SELECT * FROM Control");
controlView.Execute();
var rec = Session.Installer.CreateRecord(12);
rec.StringData(1) = "VerifyReadyDlg"; // Dialog_
rec.StringData(2) = "CustomVerifyText1"; // Control - can be any name
rec.StringData(3) = "Text"; // Type
rec.IntegerData(4) = 25; // X
rec.IntegerData(5) = 60; // Y
rec.IntegerData(6) = 320; // Width
rec.IntegerData(7) = 85; // Height
rec.IntegerData(8) = 2; // Attributes
rec.StringData(9) = ""; // Property
rec.StringData(10) = vText1; // Text
rec.StringData(11) = ""; // Control_Next
rec.StringData(12) = ""; // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);
rec = Session.Installer.CreateRecord(12);
rec.StringData(1) = "VerifyReadyDlg"; // Dialog_
rec.StringData(2) = "CustomVerifyText2"; // Control - any unique name
rec.StringData(3) = "Text"; // Type
rec.IntegerData(4) = 25; // X
rec.IntegerData(5) = 160; // Y
rec.IntegerData(6) = 320; // Width
rec.IntegerData(7) = 65; // Height
rec.IntegerData(8) = 2; // Attributes
rec.StringData(9) = ""; // Property
rec.StringData(10) = text2; // Text
rec.StringData(11) = ""; // Control_Next
rec.StringData(12) = ""; // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);
controlView.Close();
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException("UpdatePropsWithSelectedWebSite", exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
function LogException(loc, exc)
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "Exception {" + loc + "}: " + exc.number + " : " + exc.message;
Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}
Объявите Javascript CA:
<Fragment>
<Binary Id="IisScript_CA" SourceFile="CustomActions.js" />
<CustomAction Id="CA.UpdateReadyDialog"
BinaryKey="IisScript_CA"
JScriptCall="UpdateReadyDialog_CA"
Execute="immediate"
Return="check" />
</Fragment>
Присоедините CA к кнопке. В этом примере CA запускается при нажатии Next из CustomizeDlg:
<UI ...>
<Publish Dialog="CustomizeDlg" Control="Next" Event="DoAction"
Value="CA.UpdateReadyDialog" Order="1"/>
</UI>
Связанный вопрос SO: Как я могу установить, во время выполнения, текст, который будет отображаться в VerifyReadyDlg?
Поместите компоненты, которые могут быть исправлены по отдельности внутри своих фрагментов
Это относится как к созданию установщиков продукта, так и к исправлениям: если вы включаете какой-либо компонент во фрагмент, вы должны включить все компоненты в этот фрагмент. В случае сборки установщика, если вы пропустите какие-либо ссылки на компоненты, вы получите ошибку компоновки из light.exe. Однако, когда вы делаете исправление, если вы включаете ссылку на один компонент во фрагмент, тогда все измененные компоненты из этого фрагмента будут отображаться в вашем исправлении.
как это:
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)f\Sample1.txt" />
</Component>
</DirectoryRef>
</Fragment>
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)f\Sample2.txt" />
</Component>
</DirectoryRef>
</Fragment>
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)f\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>
вместо этого:
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)\Sample1.txt" />
</Component>
<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)\Sample2.txt" />
</Component>
<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>
Кроме того, при исправлении с использованием темы "Использование Purely WiX" из файла справки WiX.chm используйте следующую процедуру для создания исправления:
torch.exe -p -xi 1.0\product.wixpdb 1.1\product.wixpdb -out patch\diff.wixmst
candle.exe patch.wxs
light.exe patch.wixobj -out patch\patch.wixmsp
pyro.exe patch\patch.wixmsp -out patch\patch.msp -t RTM patch\diff.wixmst
недостаточно просто иметь версию 1.1 product.wixpdb, собранную с использованием компонентов в отдельных фрагментах. Поэтому перед отправкой обязательно правильно фрагментируйте свой продукт.
Перед развертыванием установочного пакета я всегда контролирую его содержимое.
Это всего лишь простой вызов в командной строке (в соответствии с постом "Terferences"), откройте командную строку и введите
msiexec /a Package.msi /qb TARGETDIR="%CD%\Extract" /l*vx "%CD\install.log%"
Это извлечет содержимое пакета в подкаталог "Извлечь" с текущим путем.
Печать лицензионного соглашения от Wix3.0 и выше
1) Когда вы компилируете исходный код wix, light.exe должен ссылаться на WixUIExtension.dll в командной строке. Для этого используйте ключ командной строки -ext.
2) Если при добавлении ссылки на WixUIExtension.dll ваш проект не скомпилируется, это, скорее всего, из-за столкновений идентификаторов диалогов, т.е. ваш проект использовал те же идентификаторы диалогов, что и некоторые стандартные диалоги в WixUIExtension.dll, дать разные идентификаторы для ваших диалогов. Это довольно распространенная проблема.
3) В вашем диалоговом окне лицензии должен быть элемент управления ScrollableText с идентификатором "LicenseText". Wix ищет именно это имя элемента управления при печати.
<Control Id="LicenseText" Type="ScrollableText" X="20" Y="60" Width="330" Height="160" Sunken="yes" TabSkip="no">
<Text SourceFile="License.rtf" />
</Control>
и кнопка, которая ссылается на пользовательское действие
<Control Type="PushButton" Id="PrintButton" Width="57" Height="17" X="19" Y="244" Text="Print">
<Publish Event="DoAction" Value="PrintEula">1</Publish>
</Control>
4) Определите CustomAction с помощью Id="PrintEula" следующим образом:
<CustomAction Id="PrintEula" BinaryKey="WixUIWixca" DllEntry="PrintEula" Return="ignore" Execute="immediate" />
Примечание: BinaryKey отличается в Wix3.0 по сравнению с Wix2.0 и должен быть точно "WixUIWixca" (с учетом регистра).
Когда пользователь нажимает кнопку, ему / ей будет представлен стандартный диалог выбора принтера, и он сможет печатать оттуда.
Регистрация сборок.NET для COM Interop с совместимостью с x86/x64
NB Этот фрагмент по сути такой же, как REGASM Assembly.dll /codebase
В этом примере происходит несколько вещей, так что вот код, и я объясню это позже...
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?if $(var.Win64) ?>
<?define CLSIDRoots = "CLSID;Wow6432Node\CLSID"?>
<?else ?>
<?define CLSIDRoots = "CLSID"?>
<?endif?>
<!-- ASCOM Driver Assembly with related COM registrations -->
<Fragment>
<DirectoryRef Id="INSTALLLOCATION" />
</Fragment>
<Fragment>
<ComponentGroup Id="cgAscomDriver">
<Component Id="cmpAscomDriver" Directory="INSTALLLOCATION" Guid="{0267031F-991D-4D88-A748-00EC6604171E}">
<File Id="filDriverAssembly" Source="$(var.TiGra.Astronomy.AWRDriveSystem.TargetPath)" KeyPath="yes" Vital="yes" Assembly=".net" AssemblyApplication="filDriverAssembly" />
<RegistryKey Root="HKCR" Key="$(var.DriverId)" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="CLSID">
<RegistryValue Type="string" Value="$(var.DriverGuid)" />
</RegistryKey>
</RegistryKey>
<?foreach CLSID in $(var.CLSIDRoots) ?>
<RegistryKey Root="HKCR" Key="$(var.CLSID)" Action="none">
<RegistryKey Key="$(var.DriverGuid)" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="InprocServer32">
<RegistryValue Type="string" Value="mscoree.dll" />
<RegistryValue Type="string" Name="ThreadingModel" Value="Both"/>
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
<RegistryKey Key="!(bind.fileVersion.filDriverAssembly)" >
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
</RegistryKey>
</RegistryKey>
<RegistryKey Key="ProgId" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverId)" />
</RegistryKey>
<RegistryKey Key="Implemented Categories" Action="createAndRemoveOnUninstall" >
<RegistryKey Key="{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Action="createAndRemoveOnUninstall" />
</RegistryKey>
</RegistryKey>
</RegistryKey>
<?endforeach?>
</Component>
</ComponentGroup>
</Fragment>
</Wix>
Если вам интересно, это на самом деле для драйвера телескопа ASCOM.
Во-первых, я воспользовался советом сверху и создал несколько переменных платформы в отдельном файле, вы можете увидеть их, разбросанные по XML.
Часть if-then-else рядом с топом имеет дело с совместимостью между x86 и x64. Моя сборка нацелена на "Любой процессор", поэтому в системе x64 мне нужно зарегистрировать его дважды, один раз в 64-битном реестре и один раз в 32-битном Wow6432Node
области. If-then-else настраивает меня на это, значения используются в foreach
цикл позже. Таким образом, мне нужно только один раз создать ключи реестра (принцип СУХОГО).
Элемент file указывает фактическую сборку dll, устанавливаемую и зарегистрированную:
<File Id="filDriverAssembly" Source="$(var.TiGra.Astronomy.AWRDriveSystem.TargetPath)" KeyPath="yes" Vital="yes" Assembly=".net" AssemblyApplication="filDriverAssembly" />
Ничего революционного, но обратите внимание на Assembly=".net"
- один только этот атрибут приведет к тому, что сборка будет помещена в GAC, а это НЕ то, что я хотел. С использованием AssemblyApplication
Атрибут для указания на себя - это просто способ остановить Wix, помещающий файл в GAC. Теперь, когда Wix знает, что это сборка.net, он позволяет мне использовать некоторые переменные связующего в моем XML, такие как !(bind.assemblyFullname.filDriverAssembly)
чтобы получить полное имя сборки.
Вместо ORCA используйте InstEd, который является хорошим инструментом для просмотра таблиц MSI. Также он имеет возможность различать два пакета с помощью Transform -> Compare To...
Дополнительно доступна версия Plus с дополнительными функциями. Но также бесплатная версия предлагает хорошую альтернативу для Orca.
Установить DISABLEADVTSHORTCUTS
свойство, чтобы все объявленные ярлыки в вашем установщике становились обычными ярлыками, и вам не нужно включать фиктивный ключ reg для использования в качестве пути к ключу.
<Property Id="DISABLEADVTSHORTCUTS" Value="1"/>
Я думаю, что Windows Installer 4.0 или выше является обязательным требованием.
Это хорошая структура, но, исходя из моего опыта, мне интересно, как вы решаете эти условия:
A. Все ваши установки появляются в одном и том же месте. Если пользователю необходимо установить все 3 версии одновременно, ваш процесс разрешит это. Могут ли они однозначно сказать, какую версию каждого исполняемого файла они запускают?
B. Как вы обрабатываете новые файлы, которые существуют в TEST и / или TRAINING, но еще не в LIVE?
Вот способ помочь крупным веб-проектам проверить, что количество развернутых файлов соответствует количеству файлов, встроенных в MSI (или модуль слияния). Я только что запустил пользовательскую задачу MSBuild для нашего основного приложения (все еще находящегося в разработке), и оно подняло довольно много отсутствующих файлов, в основном изображений, но несколько файлов javascript уже проскользнули!
Этот подход (просмотр таблицы файлов MSI путем подключения к цели AfterBuild проекта WiX) может работать для других типов приложений, где у вас есть доступ к полному списку ожидаемых файлов.
Создайте пользовательский интерфейс с пользовательским действием, которое будет устанавливать переменную, и пользовательский интерфейс отключит / включит следующую кнопку (или аналогичную) на основе переменной, установленной в пользовательском действии.
Не так просто, как вы думаете, не слишком сложно, просто нигде не задокументировано!
Взаимодействие Wix с условиями, свойствами и пользовательскими действиями
Выполнение принудительной переустановки, когда установка не позволяет удалить или переустановить и не выполняет откат.
Скрипт VBscript, используемый для переопределения установки, которая по какой-либо причине не удаляется.
Dim objShell
set objShell = wscript.createObject("wscript.shell")
iReturn = objShell.Run("CMD /K MsiExec.exe /I ""C:\Users\TheUser\Documents\Visual Studio 2010\Projects\InstallationTarget\HelloInstaller\bin\Debug\HelloInstaller.msi"" REINSTALLMODE=vomus REINSTALL=ALL",,True)