В WiX как я могу выбрать веб-сайт IIS по имени?
Что я хотел бы сделать, это показать пользователю установщика список сайтов на своем сервере и позволить им выбрать один (используя описанный здесь метод: http://www.cmcrossroads.com/content/view/13160/120/, который теперь выглядит сломанным, см. здесь для основного кода). Затем установщик создаст виртуальный каталог на выбранном веб-сайте.
Однако мой поиск, похоже, показал, что единственный способ указать сайт в WiX - IP, Port и Header. Просить об этом не очень удобно, поэтому мне осталась идея написать второе пользовательское действие, чтобы получить эти данные от имени веб-сайта.
Есть ли лучший способ?
BTW Это должно работать как в IIS6, так и в IIS7, если это влияет на ответ.
Ответы
Ответ 1
ОК, возможно (в IIS6 или IIS7 с совместимостью с Metabase), благодаря этой почте в список рассылки, объясняющий слегка причудливый способ iis: элемент веб-сайта работает. Полезная часть:
Using a fragment like this and test with v3.0.5120.0:*
<iis:WebSite Id="WebSite" Description="Default Web Site" SiteId="*">
<iis:WebAddress Id="TestWebSite" Port="1" />
</iis:WebSite>
The following work:
1. If WebSite/@SiteId="*" then a case sensitive match on WebSite/@Description happens.
2. If WebSite/@SiteId matches the site id than WebSite/@Description is ignored and a match on site id happens.
3. If WebSite/@SiteId has any value WebAddress/@Port is ignored (although the syntax requires it and it can't be 0).
4. If WebSite/@SiteId is missing WebAddress/@Port is used and WebSite/@Description is ignored.
5. Once a website is created and gets site id, you can rename it (therefore its site id is not the hash of its name), the WebSite/@SiteId="*" syntax will match on the WebSite/@Description.
Итак, мой код WiX выглядит следующим образом:
<DirectoryRef Id="TARGETDIR">
<Component Id="IisSetup" Guid="YOUR-GUID-HERE">
<iis:WebVirtualDir Id="IisVirtualDir" Alias="[IIS_VIRTUALDIRNAME]" Directory="INSTALLLOCATION" WebSite="IisWebsite">
<iis:WebApplication Id="IisWebApplication" Name="[IIS_VIRTUALDIRNAME]" WebAppPool="IisAppPool" Isolation="high"/>
</iis:WebVirtualDir>
<iis:WebAppPool Id="IisAppPool" Name="[IIS_APPPOOLNAME]" Identity="networkService"/>
</Component>
</DirectoryRef>
<!-- Note that this entry should not be put under a component. If it is WiX
will update the website on install and remove it on uninstall -->
<iis:WebSite Id="IisWebsite" Description="[IIS_WEBSITENAME]" SiteId="*">
<iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>
Элемент iis: WebAddress никогда не должен использоваться, но необходим для компиляции проекта.
Ответ 2
В моем установщике я не хотел создавать веб-сайт. Я хотел разрешить пользователю выбирать существующий веб-сайт. Я сделал это с помощью специального действия в Javascript и одной пользовательской панели пользовательского интерфейса.
Пользовательский код действия:
//
// CustomActions.js
//
// Custom Actions usable within WIX For IIS installations.
//
// EnumerateWebSites_CA():
// Adds new UI to the MSI at runtime to allow the user to select a
// website, to which an ISAPI filter will be added.
//
// UpdatePropsWithSelectedWebSite_CA():
// fills session with properties for the selected website.
//
// SetAuthProps_CA():
// sets properties for the needed user and group that needs authorization to the created dir.
//
//
// original idea from:
// http://blog.torresdal.net/2008/10/24/WiXAndDTFUsingACustomActionToListAvailableWebSitesOnIIS.aspx
//
// Mon, 23 Nov 2009 10:54
//
//
// ===================================================================
// 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.
};
//*****************************************************************************
// Purpose: Custom action that enumerates the local websites, and stores their
// properties in the ListBox and AvailableWebSites tables.
// Effects: Fills the ListBox table and creates and fills the AvailableWebSites
// tables.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
function EnumerateWebSites_CA()
{
try
{
LogMessage("function EnumerateWebSites_CA() ENTER");
var c = 1;
var serverBindings, aBindings;
var listboxesView = Session.Database.OpenView("SELECT * FROM ListBox");
listboxesView.Execute();
var record = Session.Installer.CreateRecord(4);
record.StringData(1) = "WEBSITE"; // Property
record.IntegerData(2) = c++; // display order
record.StringData(3) = "Server"; // returned bby the selection
record.StringData(4) = "Server-wide"; // displayed in the UI
listboxesView.Modify(MsiViewModify.InsertTemporary, record);
// Create this table dynamically. We could also create this
// custom table in the WiX .wxs file , but that not necessary.
// old quote: ``````
// my quote: '''''
// var createCmd = Session.Database.OpenView("CREATE TABLE 'AvailableWebSites' ('WebSiteNo' INT NOT NULL, 'WebSiteDescription' CHAR(50), 'WebSitePort' CHAR(50) NOT NULL, 'WebSiteIP' CHAR(50), 'WebSiteHeader' CHAR(50) PRIMARY KEY 'WebSiteNo')")
var createCmd = Session.Database.OpenView("CREATE TABLE AvailableWebSites (Num INT NOT NULL, Name CHAR(64), Desc CHAR(64), Port CHAR(16) NOT NULL, IP CHAR(32), Hostname CHAR(80) PRIMARY KEY Num)")
createCmd.Execute();
createCmd.Close();
LogMessage("Table 'AvailableWebSites' has been created");
var websitesView = Session.Database.OpenView("SELECT * FROM AvailableWebSites");
websitesView.Execute();
LogMessage("Query from Table 'AvailableWebSites' has returned");
var iis = GetObject("winmgmts://localhost/root/MicrosoftIISv2");
// See the metabase hierarchy diagram here:
// http://msdn.microsoft.com/en-us/library/ms524661.aspx
// http://msdn.microsoft.com/en-us/library/ms525545.aspx
// list "virtual servers", which is the same as websites.
var query = "SELECT * FROM IIsWebServerSetting"
// get the list of virtual servers
var results = iis.ExecQuery(query);
LogMessage("WMI Query completed.");
LogMessage("WMI Query results : " + typeof results);
for(var e = new Enumerator(results); !e.atEnd(); e.moveNext())
{
var site = e.item();
// site.Name // W3SVC/1, W3SVC/12378398, etc
// site.Name.substr(6) // 1, 12378398, etc
// site.ServerComment) // "Default Web Site", "Site2", etc
// site.ServerBindings(0).Port // 80, 8080, etc
LogMessage("Web site " + site.Name);
LogMessage("listbox record");
record = Session.Installer.CreateRecord(4);
record.StringData(1) = "WEBSITE";
record.IntegerData(2) = c++;
record.StringData(3) = site.Name.substr(6); // site.Name;
record.StringData(4) = site.ServerComment + " (" + site.Name + ")";
listboxesView.Modify(MsiViewModify.InsertTemporary, record);
LogMessage("websites record");
LogMessage("website(" + site.Name + ") name(" + site.ServerComment + ") port(" + site.ServerBindings(0).Port + ")");
record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6)); // WebSiteNo
record.StringData(2) = site.Name; // name, like W3SVC/1
record.StringData(3) = site.ServerComment; // WebSiteDescription
record.StringData(4) = site.ServerBindings(0).Port; // WebSitePort
record.StringData(5) = site.ServerBindings(0).Ip; // WebSiteIP; maybe empty
record.StringData(6) = site.ServerBindings(0).Hostname; // WebSiteHeader; maybe empty
websitesView.Modify(MsiViewModify.InsertTemporary, record);
}
listboxesView.Close();
websitesView.Close();
LogMessage("function EnumerateWebSites_CA() EXIT");
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException(exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
//*****************************************************************************
// Purpose: Custom action that copies the selected website properties from the
// AvailableWebSites table to properties.
// Effects: Fills the WEBSITE_DESCRIPTION, WEBSITE_PORT, WEBSITE_IP, WEBSITE_HEADER
// properties.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
function UpdatePropsWithSelectedWebSite_CA()
{
try
{
LogMessage("function UpdatePropsWithSelectedWebSite_CA() ENTER");
var selectedWebSiteId = Session.Property("WEBSITE");
LogMessage("selectedWebSiteId(" + selectedWebSiteId + ") type(" + typeof selectedWebSiteId + ")");
// check if the user selected anything
if (selectedWebSiteId == "")
{
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (None)");
return MsiActionStatus.None;
}
if (selectedWebSiteId.toUpperCase() == "SERVER")
{
Session.Property("WEBSITE_NAME") = "W3SVC";
Session.Property("WEBSITE_DESCRIPTION") = "Server";
Session.Property("WEBSITE_PORT") = "";
Session.Property("WEBSITE_IP") = "";
Session.Property("WEBSITE_HEADER") = "";
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (Ok)");
return MsiActionStatus.Ok;
}
var websitesView = Session.Database.OpenView("SELECT * FROM `AvailableWebSites` WHERE `Num`=" + selectedWebSiteId);
websitesView.Execute();
var record = websitesView.Fetch();
LogMessage("website Fetch() complete");
if (record.IntegerData(1) == parseInt(selectedWebSiteId))
{
Session.Property("WEBSITE_NAME") = record.StringData(2);
Session.Property("WEBSITE_DESCRIPTION") = record.StringData(3);
Session.Property("WEBSITE_PORT") = record.StringData(4);
Session.Property("WEBSITE_IP") = record.StringData(5);
Session.Property("WEBSITE_HOSTNAME") = record.StringData(6);
}
websitesView.Close();
LogMessage("function UpdatePropsWithSelectedWebSite_CA() EXIT (Ok)");
}
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) = "IisEnumSites: 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) = "IisEnumSites: " + msg;
Session.Message(MsgKind.Log, record);
}
function decimalToHexString(number)
{
if (number < 0)
{
number = 0xFFFFFFFF + number + 1;
}
return number.toString(16).toUpperCase();
}
// Testing only
function Test1_CA()
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "Hello, this is an error message";
Session.Message(msgKindUser + iconInformation + btnOk, record);
return MsiActionStatus.Ok;
}
Зарегистрируйте пользовательские действия следующим образом:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Binary Id="IisScript_CA" SourceFile="CustomActions.js" />
<CustomAction Id="EnumerateWebSites"
BinaryKey="IisScript_CA"
JScriptCall="EnumerateWebSites_CA"
Execute="immediate"
Return="check" />
<CustomAction Id="UpdatePropsWithSelectedWebSite"
BinaryKey="IisScript_CA"
JScriptCall="UpdatePropsWithSelectedWebSite_CA"
Execute="immediate"
Return="check" />
</Fragment>
</Wix>
Это .wxs для панели пользовательского интерфейса:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI>
<Dialog Id="SelectWebSiteDlg" Width="370" Height="270" Title="Select a Web Site">
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="Next" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="Back" />
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Please select which web site you want to install to." />
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="Select a Web Site" />
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="SelectWebSiteLabel" Type="Text" X="20" Y="60" Width="290" Height="14" NoPrefix="yes" Text="Select the web site for the filter:" />
<Control Id="SelectWebSiteCombo" Type="ListBox" X="20" Y="75" Width="200" Height="150" Property="WEBSITE" Sorted="yes" />
</Dialog>
</UI>
</Fragment>
</Wix>
Панель пользовательского интерфейса представляет список, который автоматически заполняется элементами из таблицы ListBox с первым полем WEBSITE. Эта таблица заполняется во время выполнения пользовательским действием в Javascript.
Чтобы вызвать пользовательское действие в нужное время, вам нужно что-то подобное в главном файле .wxs:
<InstallUISequence>
<Custom Action="EnumerateWebSites" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>
Ответ 3
Пока этот вопрос и ответ все еще действительны, я думаю, стоит подумать, действительно ли вы хотите использовать имя веб-сайта. Я хочу сохранить его для использования во время удаления, а затем сохранить идентификатор сайта, вероятно, лучше. В этом случае элемент веб-сайта становится:
<iis:WebSite Id="IisWebsite" Description="Dummy" SiteId="[IIS_WEBSITEID]">
<iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>
Ответ 4
Ответить на IisEnumSites:Exception: 0x80004005 : Modify, Mode, Record
У меня есть аналогичный опыт, и то, что я нашел до сих пор, является идентификатором сайта, который извлекает из parseInt
:
record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6)); // WebSiteNo
У меня есть сайт с именем типа W3SVC/1528550093
, и я подозреваю, что 1528550093
слишком большой для таблицы AvailableWebSites
.
Как только у меня есть оператор if, чтобы отфильтровать это большое число, а script работает нормально.
Надеюсь, что эта помощь для других.
Ответ 5
На основе ответа Cheeso и обновленного пользовательского действия для использования С# с Microsoft.Web.Administration, а не Javascript с WMI. Проверено на IIS 8.5.
CustomActions.cs:
public class IISCustomActions
{
//*****************************************************************************
// Purpose: Custom action that enumerates the local websites, and stores their
// properties in the ListBox and AvailableWebSites tables.
// Effects: Fills the ListBox table and sets WEBSITE.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
[CustomAction]
public static ActionResult GetWebsites(Session session)
{
ActionResult result = ActionResult.Success;
session.Log("Begin GetWebSites");
try
{
var c = 1;
var listboxesView = session.Database.OpenView("SELECT * FROM ListBox");
listboxesView.Execute();
var iisManager = new ServerManager();
SiteCollection sites = iisManager.Sites;
string firstWebSite = String.Empty;
foreach (Site site in sites)
{
session.Log("Web site " + site.Name);
string itemKey = site.Name;
// Set the default selection if one isn't passed in from the command line
if (("Default Web Site" == itemKey) && String.IsNullOrEmpty(session["WEBSITE"]))
{
session["WEBSITE"] = itemKey;
}
// If this is the first item, store it name so we can select it as the default selection
// if Default Web Site doesn't exist
if (1 == c)
{
firstWebSite = itemKey;
}
Record listboxItem = new Record(4);
listboxItem.SetString(1, "WEBSITE"); // Property to update
listboxItem.SetInteger(2, c++); // Display order
listboxItem.SetString(3, itemKey); // Key returned by the selection
listboxItem.SetString(4, site.Name); // Displayed in the UI
listboxesView.Modify(ViewModifyMode.InsertTemporary, listboxItem);
session.Log("website record (" + site.Name + ") id(" + site.Id + ")");
}
// They musn't have Default Web Site in their list of sites
if (String.IsNullOrEmpty(session["WEBSITE"]))
{
session["WEBSITE"] = firstWebSite;
}
listboxesView.Close();
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
//*****************************************************************************
// Purpose: Custom action that copies the selected website properties from the
// AvailableWebSites table to properties.
// Effects: Fills the IISROOT and WEBSITE_PORT
// properties.
// Returns: MsiActionStatus.Ok if the custom action executes without error.
// MsiActionStatus.Abort if error.
//*****************************************************************************
[CustomAction]
public static ActionResult UpdatePropsWithSelectedWebSite(Session session)
{
session.Log("Begin UpdatePropsWithSelectedWebSite");
ActionResult result = ActionResult.Success;
try
{
var selectedWebSiteId = session["WEBSITE"];
session.Log("selectedWebSiteId(" + selectedWebSiteId + ")");
var iisManager = new ServerManager();
Site site = iisManager.Sites[selectedWebSiteId];
session["WEBSITE_PORT"] = site.Bindings[0].EndPoint.Port.ToString();
session["IISROOT"] = site.Applications["/"].VirtualDirectories["/"].PhysicalPath;
session.Log("End UpdatePropsWithSelectedWebSite EXIT (Ok)");
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
}
Зарегистрируйте пользовательское действие, как это:
<Binary Id='WiXCustomActionsDLL' SourceFile='CustomActions.CA.dll' />
<CustomAction Id="GetWebsitesAction"
Return="check"
Execute="immediate"
BinaryKey="WiXCustomActionsDLL"
DllEntry="GetWebsites" />
<InstallUISequence>
<Custom Action='GetWebsitesAction' Before='AppSearch' />
</InstallUISequence>
<!-- Updating IISROOT in the UI does not update the value of it sub-directory INSTALLLOCATION.
So we have this to force the update of INSTALLLOCATION with a custom action. -->
<CustomAction Id="ChangeDir" Directory="INSTALLLOCATION" Value="[IISROOT]ProjectWebSite" />
<InstallExecuteSequence>
<Custom Action='ChangeDir' After='CostFinalize'></Custom>
</InstallExecuteSequence>
<!-- This populates properties for IISROOT and WEBSITE_PORT after this before files are installed -->
<CustomAction Id="UpdatePropsWithSelectedWebSiteAction"
Return="check"
Execute="immediate"
BinaryKey="WiXCustomActionsDLL"
DllEntry="UpdatePropsWithSelectedWebSite" />
Диалог wxs выглядит так:
<UI>
<Dialog Id="IISDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
<Control Id="SelectWebSiteLabel" Type="Text" X="20" Y="73" Width="100" Height="15" NoPrefix="yes" Text="Select web site:" />
<Control Id="SelectWebSiteCombo" Type="ListBox" X="20" Y="89" Width="200" Height="150" Property="WEBSITE" Sorted="yes" />
<Control Id="VirtualHostLabel" Type="Text" X="235" Y="73" Width="100" Height="15" TabSkip="no" Text="&Application Path Alias:" />
<Control Id="VirtualHostTextbox" Type="Edit" X="235" Y="89" Height="17" Width="120" Property="IIS_VIRTUAL_DIR" Indirect="no" />
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
<Publish Event="NewDialog" Value="MaintenanceTypeDlg" Order="1">Installed</Publish>
<Publish Event="NewDialog" Value="LicenseAgreementDlg" Order="2">NOT Installed</Publish>
</Control>
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
<Publish Event="NewDialog" Value="CMParametersDlg">1</Publish>
<Publish Event="DoAction" Value="UpdatePropsWithSelectedWebSiteAction">1</Publish>
</Control>
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
</Control>
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
<Text>Configure settings for your Web Server</Text>
</Control>
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
<Text>{\WixUI_Font_Title}Settings</Text>
</Control>
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
</Dialog>
</UI>
Обратите внимание на событие DoAction в элементе управления Next. Запускается настраиваемое действие для обновления свойств с использованием выбранного веб-сайта.
А затем следуйте ответу Дэна относительно использования SiteId = "* 'при применении изменений.
Ответ 6
@Dinesh Pannu: Можете ли вы также опубликовать свои сообщения в CustomAction.cs? Я не знаю, откуда все эти классы.
Thn