在WiX中,如何按名称选择IIS网站?

时间:2009-08-28 13:02:31

标签: iis iis-7 iis-6 wix wix3

我想要做的是向安装程序用户显示他们服务器上的网站列表,并允许他们选择一个(使用此处描述的方法:http://www.cmcrossroads.com/content/view/13160/120/,现在看来已经破了{{3}对于核心代码)。然后,安装程序将在所选网站中创建一个虚拟目录。

但是,我的搜索似乎表明,在WiX中指定网站的唯一方法是通过IP,端口和标头。要求这些不是非常用户友好,所以我留下了编写第二个自定义操作的想法,以从网站名称获取这些详细信息。

有更好的方法吗?

BTW如果影响答案,这需要在IIS6和IIS7中都有效。

5 个答案:

答案 0 :(得分:10)

确定可以(在IIS6或IIS7中具有Metabase兼容性),感谢this发送到邮件列表,解释了iis:Website元素工作的稍微奇怪的方式。有用的部分是:

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元素,但这是项目编译所必需的。

答案 1 :(得分:3)

在我的安装程序中,我不想创建一个网站。我想允许用户选择现有的网站。我在Javascript中使用自定义操作和一个自定义UI面板执行此操作。


自定义操作代码:

//
// 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's 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's 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>

这是UI面板的.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>

UI面板显示一个列表框,该列表框会自动填充ListBox表中带有WEBSITE第一个字段的元素。该表在运行时由Javascript中的自定义操作填充。

要在合适的时间调用自定义操作,您需要在.wxs主文件中使用以下内容:

<InstallUISequence>
  <Custom Action="EnumerateWebSites" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>

答案 2 :(得分:0)

虽然这个问题和答案仍然有效,但我认为值得问问自己是否真的想要使用网站名称。我想存储它以便在卸载期间使用,然后保存站点ID可能是一个更好的主意。在这种情况下,网站元素变为:

<iis:WebSite Id="IisWebsite" Description="Dummy" SiteId="[IIS_WEBSITEID]">
  <iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>

答案 3 :(得分:0)

回复IisEnumSites:Exception: 0x80004005 : Modify, Mode, Record

我有类似的经验,到目前为止我发现的是从parseInt中提取的网站ID:

record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6));  // WebSiteNo

我的网站名称为W3SVC/1528550093,我怀疑1528550093AvailableWebSites表来说太大了。

一旦我有if语句来过滤掉这些大数字,脚本就可以了。

希望对其他人有所帮助。

答案 4 :(得分:0)

基于Cheeso的回答和更新的自定义操作,以将C#与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's 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's 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's 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="&amp;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="&amp;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="&amp;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事件。这将运行自定义操作,以使用所选网站更新属性。

然后在应用更改时遵循Dan关于使用SiteId =“ *'的回答。