2013-06-21 12:05:11 UTC
Замечания
Начиная с Windows 7 и Windows Server 2008 R2, содержащих Windows Installer 5.0, появилась замечательная возможность сделать один установочный пакет, корректно устанавливающий приложения как для текущего пользователя, так и для всех пользователей, учитывая все нюансы UAC. Однако, это делается не так очевидно, как кажется на первый взгляд. Вот этим и хочется поделиться.
Начнем пожалуй с пруфлинка на MSDN. Там говорится что, для того, чтобы сделать такой пакет, вам нужно установить начальные значения свойств ALLUSERS и MSIINSTALLPERUSER в 2 (два) и 1 (один) соответственно. После того, как это будет сделано, настройкой по умолчанию будет установка только для одного (текущего) пользователя, но с возможностью изменить это (т.е. установить для всех) в процессе установки.
Это была теория, а теперь перейдем к практике с использованием WiX toolset. В WiX UI расширении есть возможность выбрать последовательность WixUI_Advanced в которой можно выбрать как вы будете ставить приложение — только для текущего пользователя или для всех. Замечательная вещь, но, как это часто бывает, без обработки напильником это не работает как ожидается. Корень проблемы лежит в том, что WixUI_Advanced ничего не знает про свойство MSIINSTALLPERUSER и соответственно не делает того, что надо делать для корректной работы такого пакета установки. Для того, чтобы это исправить, вам понадобятся исходники WiX, Взять можно с CodePlex.
Действия
Первое, что надо сделать, это в вашем главном файле wxs (Обычно Product.wxs), установить вышеупомянутые свойства в нужные значения:
<Property Id="ALLUSERS" Secure="yes" Value="2" /> <Property Id="MSIINSTALLPERUSER" Secure="yes" Value="1" />
Далее, в каталоге src\ext\UIExtension\wixlib найти файл WixUI_Advanced.wxs, скопировать его к себе и включить в свой проект. Файл можно переименовывать, а можно и оставить как есть, не критично. Однако, чтобы не было пересечения с библиотечной последовательностью, необходимо переименовать id для модифицированной UI, т.е. надо найти в файле строчку:
<UI Id="WixUI_Advanced">
И переименовать значение Id в например WixUI_MySetup, т.е. теперь это будет выглядеть так:
<UI Id="WixUI_MySetup">
И ссылаться теперь нужно будет на этот Id т.е вместо:
<UIRef Id="WixUI_Advanced" />
надо:
<UIRef Id="WixUI_MySetup" />
Обычно это делается в Product.wxs.
Затем, найти определения custom actions WixSetDefaultPerUserFolder, WixSetDefaultPerMachineFolder, WixSetPerUserFolder, WixSetPerMachineFolder, по умолчанию они выглядят так:
<CustomAction Id="WixSetDefaultPerUserFolder" Property="WixPerUserFolder" Value="[LocalAppDataFolder]Apps\[ApplicationFolderName]" Execute="immediate" /> <CustomAction Id="WixSetDefaultPerMachineFolder" Property="WixPerMachineFolder" Value="[ProgramFilesFolder][ApplicationFolderName]" Execute="immediate" /> <CustomAction Id="WixSetPerUserFolder" Property="APPLICATIONFOLDER" Value="[WixPerUserFolder]" Execute="immediate" /> <CustomAction Id="WixSetPerMachineFolder" Property="APPLICATIONFOLDER" Value="[WixPerMachineFolder]" Execute="immediate" />
и переименовать их Id, чтобы не было пересечения с библиотечными, например так:
<CustomAction Id="MyWixSetDefaultPerUserFolder" Property="WixPerUserFolder" Value="[LocalAppDataFolder]Apps\[ApplicationFolderName]" Execute="immediate" /> <CustomAction Id="MyWixSetDefaultPerMachineFolder" Property="WixPerMachineFolder" Value="[ProgramFilesFolder][ApplicationFolderName]" Execute="immediate" /> <CustomAction Id="MyWixSetPerUserFolder" Property="APPLICATIONFOLDER" Value="[WixPerUserFolder]" Execute="immediate" /> <CustomAction Id="MyWixSetPerMachineFolder" Property="APPLICATIONFOLDER" Value="[WixPerMachineFolder]" Execute="immediate" />
Соответственным образом нужно модифицировать InstallExecuteSequence и InstallUISequence, для того чтобы работали ваши, а не библиотечные custom actions. Исправленный вариант:
<InstallExecuteSequence>
<Custom Action="MyWixSetDefaultPerUserFolder" Before="CostFinalize" />
<Custom Action="MyWixSetDefaultPerMachineFolder" After="MyWixSetDefaultPerUserFolder" />
<Custom Action="MyWixSetPerUserFolder" After="MyWixSetDefaultPerMachineFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS="" OR (ALLUSERS=2 AND (NOT Privileged)))
</Custom>
<Custom Action="MyWixSetPerMachineFolder" After="MyWixSetPerUserFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS=1 OR (ALLUSERS=2 AND Privileged))
</Custom>
</InstallExecuteSequence>
<InstallUISequence>
<Custom Action="MyWixSetDefaultPerUserFolder" Before="CostFinalize" />
<Custom Action="MyWixSetDefaultPerMachineFolder" After="MyWixSetDefaultPerUserFolder" />
<Custom Action="MyWixSetPerUserFolder" After="MyWixSetDefaultPerMachineFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS="" OR (ALLUSERS=2 AND (NOT Privileged)))
</Custom>
<Custom Action="MyWixSetPerMachineFolder" After="MyWixSetPerUserFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS=1 OR (ALLUSERS=2 AND Privileged))
</Custom>
</InstallUISequence>
После этого, в последовательности диалогов, нужно найти следующие строчки:
<Publish Dialog="InstallScopeDlg" Control="Next" Property="ALLUSERS" Value="{}" Order="2">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="ALLUSERS" Value="1" Order="3">
WixAppFolder = "WixPerMachineFolder"
</Publish>
И заменить их на нужные нам:
<Publish Dialog="InstallScopeDlg" Control="Next" Property="MSIINSTALLPERUSER" Value="1" Order="3">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="MSIINSTALLPERUSER" Value="{}" Order="2">
WixAppFolder = "WixPerMachineFolder"
</Publish>
Т.е. нам не нужно менять свойство ALLUSERS, т.к. это все испортит, а нужно менять свойство MSIINSTALLPERUSER, как написано в требованиях к такого типа инсталляторам.
После этих двух строчек, надо добавить ещё две:
<Publish
Dialog="InstallScopeDlg"
Control="Next"
Event="DoAction"
Value="MyWixSetDefaultPerMachineFolder"
Order="3">WixAppFolder = "WixPerMachineFolder"</Publish>
<Publish
Dialog="InstallScopeDlg"
Control="Next"
Event="DoAction"
Value="MyWixSetDefaultPerUserFolder"
Order="3">WixAppFolder = "WixPerUserFolder"</Publish>
Это запуск custom actions, устанавливающих целевые папки по умолчанию в нужное нам значение. Без этого, при варианте установки для всех пользователей, целевая папка будет установлена неверно, т.к. custom actions в InstallUISequence и InstallExecuteSequence отрабатывают ещё при свойстве MSIINSTALLPERUSER = 1, и соответственно [ProgramFilesFolder] будет указывать в локальную для пользователя папку, вместо C:\Program Files
В итоге, у нас должно получиться примерно следующее (новый файл WixUI_Advanced.wxs):
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<WixVariable Id="WixUISupportPerUser" Value="1" Overridable="yes" />
<WixVariable Id="WixUISupportPerMachine" Value="1" Overridable="yes" />
<PropertyRef Id="ApplicationFolderName" />
<CustomAction
Id="MyWixSetDefaultPerUserFolder"
Property="WixPerUserFolder"
Value="[LocalAppDataFolder]Apps\[ApplicationFolderName]"
Execute="immediate" />
<CustomAction
Id="MyWixSetDefaultPerMachineFolder"
Property="WixPerMachineFolder"
Value="[ProgramFilesFolder][ApplicationFolderName]"
Execute="immediate" />
<CustomAction
Id="MyWixSetPerUserFolder"
Property="APPLICATIONFOLDER"
Value="[WixPerUserFolder]"
Execute="immediate" />
<CustomAction
Id="MyWixSetPerMachineFolder"
Property="APPLICATIONFOLDER"
Value="[WixPerMachineFolder]"
Execute="immediate" />
<InstallExecuteSequence>
<Custom Action="MyWixSetDefaultPerUserFolder" Before="CostFinalize" />
<Custom Action="MyWixSetDefaultPerMachineFolder" After="MyWixSetDefaultPerUserFolder" />
<Custom Action="MyWixSetPerUserFolder" After="MyWixSetDefaultPerMachineFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS="" OR (ALLUSERS=2 AND (NOT Privileged)))
</Custom>
<Custom Action="MyWixSetPerMachineFolder" After="MyWixSetPerUserFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS=1 OR (ALLUSERS=2 AND Privileged))
</Custom>
</InstallExecuteSequence>
<InstallUISequence>
<Custom Action="MyWixSetDefaultPerUserFolder" Before="CostFinalize" />
<Custom Action="MyWixSetDefaultPerMachineFolder" After="MyWixSetDefaultPerUserFolder" />
<Custom Action="MyWixSetPerUserFolder" After="MyWixSetDefaultPerMachineFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS="" OR (ALLUSERS=2 AND (NOT Privileged)))
</Custom>
<Custom Action="MyWixSetPerMachineFolder" After="MyWixSetPerUserFolder">
ACTION="INSTALL" AND APPLICATIONFOLDER="" AND (ALLUSERS=1 OR (ALLUSERS=2 AND Privileged))
</Custom>
</InstallUISequence>
<UI Id="WixUI_MySetup">
<TextStyle
Id="WixUI_Font_Normal"
FaceName="!(loc.Advanced_Font_FaceName)"
Size="!(loc.Advanced_Font_Normal_Size)" />
<TextStyle
Id="WixUI_Font_Bigger"
FaceName="!(loc.Advanced_Font_FaceName)"
Size="!(loc.Advanced_Font_Bigger_Size)" />
<TextStyle
Id="WixUI_Font_Title"
FaceName="!(loc.Advanced_Font_FaceName)"
Size="!(loc.Advanced_Font_Title_Size)" Bold="yes" />
<TextStyle
Id="WixUI_Font_Emphasized"
FaceName="!(loc.Advanced_Font_FaceName)"
Size="!(loc.Advanced_Font_Emphasized_Size)"
Bold="yes" />
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
<Property Id="WixUI_Mode" Value="Advanced" />
<DialogRef Id="BrowseDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
<DialogRef Id="ProgressDlg" />
<DialogRef Id="ResumeDlg" />
<DialogRef Id="UserExit" />
<DialogRef Id="WelcomeDlg"/>
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="1">1</Publish>
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="2">
WIXUI_INSTALLDIR_VALID<>"1"
</Publish>
<Publish Dialog="AdvancedWelcomeEulaDlg" Control="Advanced" Event="NewDialog" Value="InstallScopeDlg" Order="1">
!(wix.WixUISupportPerMachine) AND !(wix.WixUISupportPerUser)
</Publish>
<Publish Dialog="AdvancedWelcomeEulaDlg" Control="Advanced" Event="NewDialog" Value="FeaturesDlg" Order="2">
NOT !(wix.WixUISupportPerMachine)
</Publish>
<Publish Dialog="AdvancedWelcomeEulaDlg" Control="Advanced" Event="NewDialog" Value="InstallDirDlg" Order="3">
!(wix.WixUISupportPerMachine) AND NOT !(wix.WixUISupportPerUser)
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Back" Event="NewDialog" Value="AdvancedWelcomeEulaDlg">1</Publish>
<!-- override default WixAppFolder of WixPerMachineFolder as standard user won't be shown the radio group to set WixAppFolder -->
<Publish Dialog="InstallScopeDlg" Control="Next" Property="WixAppFolder" Value="WixPerUserFolder" Order="1">
!(wix.WixUISupportPerUser) AND NOT Privileged
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="MSIINSTALLPERUSER" Value="1" Order="3">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="MSIINSTALLPERUSER" Value="{}" Order="2">
WixAppFolder = "WixPerMachineFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Event="DoAction" Value="MyWixSetDefaultPerMachineFolder" Order="3">
WixAppFolder = "WixPerMachineFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Event="DoAction" Value="MyWixSetDefaultPerUserFolder" Order="3">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="APPLICATIONFOLDER" Value="[WixPerUserFolder]" Order="4">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Property="APPLICATIONFOLDER" Value="[WixPerMachineFolder]" Order="5">
WixAppFolder = "WixPerMachineFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Event="NewDialog" Value="FeaturesDlg" Order="6">
WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="InstallScopeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="7">
WixAppFolder = "WixPerMachineFolder"
</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="InstallScopeDlg">
!(wix.WixUISupportPerUser)
</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="AdvancedWelcomeEulaDlg">
NOT !(wix.WixUISupportPerUser)
</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">
NOT WIXUI_DONTVALIDATEPATH
</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3">
NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"
</Publish>
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="FeaturesDlg" Order="4">
WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"
</Publish>
<Publish
Dialog="InstallDirDlg"
Control="ChangeFolder"
Property="_BrowseProperty"
Value="[WIXUI_INSTALLDIR]"
Order="1">1</Publish>
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
<Publish Dialog="FeaturesDlg" Control="Back" Event="NewDialog" Value="InstallScopeDlg">
NOT Installed AND WixAppFolder = "WixPerUserFolder"
</Publish>
<Publish Dialog="FeaturesDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg">
NOT Installed AND WixAppFolder = "WixPerMachineFolder"
</Publish>
<Publish
Dialog="FeaturesDlg"
Control="Back"
Event="NewDialog" Value="MaintenanceTypeDlg">Installed</Publish>
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="ChangeButton" Event="NewDialog" Value="FeaturesDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">
Installed AND NOT PATCH
</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="3">
Installed AND PATCH
</Publish>
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">
Installed AND PATCH
</Publish>
</UI>
<InstallUISequence>
<Show Dialog="WelcomeDlg" Before="AdvancedWelcomeEulaDlg" >Installed AND PATCH</Show>
</InstallUISequence>
<Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER" />
<UIRef Id="WixUI_Common" />
</Fragment>
</Wix>
Вот и все.