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>
Вот и все.