I'm not the best a Linq and I have written the below query and it just seems my where clause is redundant and there has to be a better way?
Given the following XML structure:
<Views>
<Fulfillment>
<SecurityRoleName>ABCD</SecurityRoleName>
<SecurityRoleViews>
<RoleView name="A" />
<RoleView name="B" />
<RoleView name="C" />
<RoleView name="D" />
<RoleView name="E" />
<RoleView name="F" />
</SecurityRoleViews>
<PublicRoleViews>
<RoleView name="Z" />
<RoleView name="Y" />
<RoleView name="X" />
<RoleView name="W" />
<RoleView name="V" />
<RoleView name="U" />
</PublicRoleViews>
</Fulfillment>
</Views>
I wrote the following to get a single value (FulfillmentRoleName) and two List (SecuredViews, PublicViews) objects.
FulfillmentRoleName = configParms.Descendants("Fulfillment")
.Where(node => (string)node.Element("SecurityRoleName") == "SecurityRoleName")
.Select(node => node.Value.ToString())
.First();
SecuredViews = configParms.Descendants("SecurityRoleViews")
.Where(node => (string)node.Element("RoleView") == "RoleView")
.Select(node => node.Attribute("name").Value.ToString())
.ToList();
PublicViews = configParms.Descendants("PublicRoleViews")
.Where(node => (string)node.Element("RoleView") == "RoleView")
.Select(node => node.Attribute("name").Value.ToString())
.ToList();
I would want the following values:
FulfillmentRoleName = ABCD
SecuredViews = List of names A,B,C...
PublicViews = List of names Z,Y,X....
It is the where clause that I am unsure of:
.Where(node => (string)node.Element("RoleView") == "RoleView")
Seems there has to be a more elegant way to locate that node?
Thank you for taking the time to help
As per Ahmad Mageed recommendation I am using the more elegant Element("") approach. However I am getting an instantiation error...as if the element collection is not built yet??
However if I use configParms.Root.Value I get my SecurityRoleName value???
I would have thought that views would be the root....or is it the first node that has a value???
You can use the Elements method and provide the name to match. This would allow you to replace the where query with Elements("RoleView").
Some other observations:
You can simplify the first query by grabbing the "Fulfillment" element directly by using the Element method, instead of Descendants.
The Value property returns a string. The ToString() calls are redundant.
Here's an updated version of your queries:
// if configParms is an XDocument use configParms.Root
var securityRoleName = configParms.Element("Fulfillment")
.Element("SecurityRoleName").Value;
var securedViews = configParms.Descendants("SecurityRoleViews")
.Elements("RoleView")
.Select(node => node.Attribute("name").Value)
.ToList();
var publicViews = configParms.Descendants("PublicRoleViews")
.Elements("RoleView")
.Select(node => node.Attribute("name").Value)
.ToList();
Related
i have a code
<int-jpa:updating-outbound-gateway
request-channel="nativeQlChannel" auto-startup="true"
native-query="update Transactions t set t.transaction_Status = :transactionStatus where t.bank_Reference_Number = :bankReferenceNumber "
entity-manager="entityManager" persist-mode="PERSIST" reply-channel="nativeQlChannelOne"
use-payload-as-parameter-source="false">
after this execution it calls
<int-jpa:outbound-channel-adapter
channel="nativeQlChannel" entity-class="org.ncb.quickpay.grs.persistence.entities.PartnerResponseDetails"
persist-mode="PERSIST" entity-manager="entityManager" >
<int-jpa:transactional/>
</int-jpa:outbound-channel-adapter>
<int-jpa:outbound-channel-adapter
channel="nativeQlChannelOne"
native-query="update Transactions t set t.transaction_Status = :transactionStatus where t.bank_Reference_Number = :bankReferenceNumber "
entity-manager="entityManager">
<int-jpa:transactional />
<int-jpa:parameter name="transactionStatus"
expression="payload['transactionStatus']" />
<int-jpa:parameter name="bankReferenceNumber"
expression="payload['bankReferenceNumber']" />
</int-jpa:outbound-channel-adapter>
but i am not able to get any parameter here , as it has 1 as value in payload which is the status of the previous query execution , how can i share the payload so that i can get all parameter which are available to previous query.
A common pattern is to store a reference to the payload in a header; then restore it...
<int:header-enricher ...> <!-- before the first gateway -->
<int:header name="savePayload" expression="payload">
</int:header-enricher>
<int:transformer ... expression="headers.savePayload"> <!-- after the first gateway -->
Hi I am trying to fetch the accounts from CRM 2011. I am fetching the data in the EntityCollection . But when I am trying to read or access data from entityCollection it displayed first record but throwing an error after that record. Kindly have a look to below code and suggest me.
string fetch2 = #"
<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='account'>
<attribute name='name' />
<attribute name='address1_city' />
<attribute name='primarycontactid' />
<attribute name='telephone1' />
<attribute name='accountid' />
<order attribute='name' descending='false' />
<filter type='and'>
<condition attribute='accounttype' operator='eq' value='01' />
</filter>
</entity>
</fetch>";
try
{
EntityCollection fxResult = _service.RetrieveMultiple(new FetchExpression(fetch2));
foreach (var e in fxResult.Entities)
{
Console.WriteLine("Id:{0},Name:{1},City:{2}", e.Attributes ["accountid"].ToString(), e.Attributes["name"].ToString(), e.Attributes["address1_city"].ToString());
// Console.WriteLine("Id:{0},Name:{1},City:{2}", e.ToEntity["accountid"]);
}
}
catch (Exception e)
{
Console.WriteLine("Error:==" + e.Message);
}
Before access an attribute you need to ask if it is in the context:
e.Attributes.Contains("address1_city")
If the collection contains the attribute, then you can access it safe.
string accountid = (string)e.Attributes["address1_city"]
The reason the attribute doesn't come in the collection it's because it is null or you are not retrieving it. In this case maybe, one of your attributes is null. Maybe address1_city.
When retrieving attribute values of late-bound Entity objects, the recommended approach is to use method getAttributeValue<T>. When the attribute is not present in the entity's attribute collection, it returns default(T).
The primary key ('id') of the record is always present when it is returned by the OrganizationService.
So your code should look like this:
EntityCollection fxResult = _service.RetrieveMultiple(new FetchExpression(fetch2));
foreach (var e in fxResult.Entities)
{
Console.WriteLine(
"Id:{0},Name:{1},City:{2}",
e.Id,
e.GetAttributeValue<string>("name"),
e.GetAttributeValue<string>("address1_city"));
}
You can safely use the item selector when you need to assign a value to an attribute, regardless if it is already present or not.
E.g. the following code line is valid:
e["name"] = "Demo Accountname";
In IBM WebSphere Commerce How the facade can be called from the command Instead of calling it from jsp. we call it from jsp like
<wcf:getData type="com.ibm.commerce.catalog.facade.datatypes.CatalogNavigationViewType" var="catalogNavigationView"
expressionBuilder="${navigationView}" varShowVerb="showCatalogNavigationView"
maxItems="${pageSize}" recordSetStartNumber="${WCParam.beginIndex}">
<wcf:param name="searchProfile" value="${searchProfile}" />
<wcf:param name="searchTerm" value="${newSearchTerm}" />
<wcf:param name="intentSearchTerm" value="${intentSearchTerm}" />
<wcf:param name="searchType" value="${searchType}" />
<wcf:param name="searchSource" value="${WCParam.searchSource}" />
<wcf:param name="metaData" value="${WCParam.metaData}" />
<wcf:param name="orderBy" value="${WCParam.orderBy}" />
<c:forEach var="facetValue" items="${param.facet}">
<c:if test="${fn:contains(facetValue , '|')}">
<c:set var="facetValue" value="${fn:replace(facetValue,'|',',')}"/>
</c:if>
<wcf:param name="facet" value="${facetValue}" />
</c:forEach>
<wcf:param name="advancedFacetList" value="${newAdvancedFacetList}"/>
<wcf:param name="categoryId" value="${currentCategoryId}" />
<wcf:param name="filterTerm" value="${newFilterTerm}" />
<wcf:param name="filterType" value="${WCParam.filterType}" />
<wcf:param name="filterFacet" value="${removeFacet}" />
<wcf:param name="manufacturer" value="${newManufacturer}" />
<wcf:param name="minPrice" value="${WCParam.minPrice}" />
<wcf:param name="maxPrice" value="${WCParam.maxPrice}" />
<wcf:contextData name="storeId" data="${WCParam.storeId}" />
<wcf:contextData name="catalogId" data="${WCParam.catalogId}" />
</wcf:getData>
This is how we can call it from jsp but I want to call this facade from the command (from the java code)
Please give me any suggestion
Thanks
Ankit
You can do it like this :
public someMethod(){
....
CatalogFacadeClient catalogFacadeClient = new CatalogFacadeClient(getBusinessContextType(), null);
GetType getVerb = CatalogFacadeClient.createGetVerb("_wcf:XPath", getXPathExpressionString());
ShowCatalogNavigationViewDataAreaType showDataArea = catalogFacadeClient.getCatalogNavigationView(getVerb);
response = showDataArea.getCatalogNavigationView();
....
}
public String getXPathExpressionString() {
StringBuffer expression = new StringBuffer();
expression.append("{_wcf.ap='IBM_Store_CatalogEntrySearch';");
expression.append("_wcf.search.term='" + toto+ "';");
expression.append("_wcf.search.spellcheck='false';");
expression.append("_wcf.search.type='1000';");
expression.append("_wcf.search.source='Q'}");
expression.append("/CatalogNavigationView");
return expression.toString();
}
protected BusinessContextType getBusinessContextType() {
BusinessContextType businessContext = CommerceFoundationFactory.eINSTANCE.createBusinessContextType();
ContextDataType storeId = CommerceFoundationFactory.eINSTANCE.createContextDataType();
storeId.setName("storeId");
storeId.setValue(currentStoreId);
ContextDataType catalogId = CommerceFoundationFactory.eINSTANCE.createContextDataType();
catalogId.setName("catalogId");
catalogId.setValue(currentCatalogId);
ContextDataType langIdContext = CommerceFoundationFactory.eINSTANCE.createContextDataType();
langIdContext.setName("langId");
langIdContext.setValue(langId.toString());
businessContext.getContextData().add(storeId);
businessContext.getContextData().add(catalogId);
businessContext.getContextData().add(langIdContext);
return businessContext;
}
Your code works!! Thank you so much!
Some Remarks about getXPathExpressionString() method:
1.- In order to prepare the expression builder correctly please check get-data-config.xml and get the expression builder by name. Name is the expressionBuilder param in wcf:getData
expressionBuilder="${navigationView}"
<wcf:getData type="com.ibm.commerce.catalog.facade.datatypes.CatalogNavigationViewType" var="catalogNavigationView"
expressionBuilder="${navigationView}" varShowVerb="showCatalogNavigationView"
maxItems="${pageSize}" recordSetStartNumber="${WCParam.beginIndex}">
Value of ${navigationView} is = "getCatalogNavigationView"
Search this value in get-data-config.xml file and look like this:
<expression-builder>
<name>getCatalogNavigationView</name>
<data-type-name>CatalogNavigationView</data-type-name>
<expression-template>{_wcf.ap='$accessProfile$';_wcf.search.profile='$searchProfile$';_wcf.search.facet.field.limit='$facetLimit$';_wcf.search.term='$searchTerm$';_wcf.search.intent.term='$intentSearchTerm$';_wcf.search.originalterm='$originalSearchTerm$';_wcf.search.category='$categoryId$';_wcf.search.type='$searchType$';_wcf.search.exclude.term='$filterTerm$';_wcf.search.exclude.type='$filterType$';_wcf.search.manufacturer='$manufacturer$';_wcf.search.price.minimum='$minPrice$';_wcf.search.price.maximum='$maxPrice$';_wcf.search.facet='$facet$';_wcf.search.advanced.facet='$advancedFacetList$';_wcf.search.exclude.facet='$filterFacet$';_wcf.search.sort='$orderBy$';_wcf.search.meta='$metaData$';_wcf.search.source='$searchSource$';_wcf.search.store='$physicalStoreIds$'}/CatalogNavigationView</expression-template>
<param>
<name>accessProfile</name>
<value>IBM_Store_CatalogEntrySearch</value>
</param>
<param>
<name>searchType</name>
<value>0</value>
</param>
<param>
<name>searchSource</name>
<value>O</value>
</param>
<param>
<name>searchProfile</name>
<value>IBM_findCatalogEntryByNameAndShortDescription</value>
</param>
</expression-builder>
source: http://158.85.49.234/WEB-INF/config/com.ibm.commerce.catalog-fep/get-data-config.xml
So this tag : <expression-template> contains the treasure :), i.e the solr query expression.
2.- With this expression-template we could can create a correct expression:
public String getXPathExpressionString() throws JspException, AbstractBusinessObjectDocumentException {
String expressionTemplate = "{_wcf.ap='$accessProfile$';_wcf.search.profile='$searchProfile$';_wcf.search.category='$categoryId$';_wcf.search.type='$searchType$';_wcf.search.sort='$orderBy$';_wcf.search.source='$searchSource$';_wcf.search.facet='$facet$';_wcf.search.exclude.facet='$filterFacet$';_wcf.search.meta='$metaData$';_wcf.search.price.minimum='$minPrice$';_wcf.search.price.maximum='$maxPrice$'}/CatalogNavigationView";
ExpressionBuilderConfig expressionBuilderConfig = new ExpressionBuilderConfig();
expressionBuilderConfig.setName("getCatalogNavigationView");
expressionBuilderConfig.setDataTypeName("CatalogNavigationView");
expressionBuilderConfig.setExpressionLanguage("_wcf:XPath");
expressionBuilderConfig.setExpressionTemplate(expressionTemplate);
HashMap<String,String[]> parameters = new HashMap<String, String[]>();
parameters.put("accessProfile", new String[]{"IBM_Store_CatalogEntrySearch"});
parameters.put("searchProfile", new String[]{"IBM_findCatalogEntryByNameAndShortDescription"});
parameters.put("searchType", new String[]{"xyz"});
parameters.put("categoryId",new String[]{ "123"});
parameters.put("minPrice",new String[]{"0"});
ExpressionType expressionType = expressionBuilderConfig.buildExpression(parameters);
return expressionType.getValue();
}
-------------------------------*-------------------------------------
This can be used to convert websphere ecommerce in a back-end or service provider. With that we can stop using jsp, scriplets, dojo 1.5 and use whatever in front-end layer like current js frameworks.
Here's my appender configuration from my app.config. This just prints out the literal string instead of translating it to the date (i.e., it literally prints "[START: %date{MM/dd/yy HH:mm} ]").
<appender name="RollingLogFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="C:\somelog" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="-yyyy-MM-dd'.txt'" />
<layout type="log4net.Layout.PatternLayout">
<header value="[START: %date{MM/dd/yy HH:mm} ]
" />
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} - %message" />
<footer value="[END]
" />
</layout>
</appender>
How can I get this to print the date/time in the header?
Answer from here.
<layout type="log4net.Layout.DynamicPatternLayout">
...
<header value="[BEGIN LOGGING AT %date]%newline"/>
<footer value="[END LOGGING AT %date]%newline"/>
...
</layout>
Works for me like a charm. No need to write a piece of code.
Also in projects we usually use:
<header type="log4net.Util.PatternString" value="Our Application Name version %property{Assembly.Version}, .NET version %property{Runtime.Version}, %date ***%newline"/>
Take a look at PatternString doc also.
Also I've noticed that log file won't appear until you write first log statement.
An easy way to do this is to subclass log4net.Layout.PatternLayout and override Header and Footer. Then you can add whatever you want to your Header: date stamp, machine name, user name, assembly version, or whatever your heart desires:
using System;
using log4net.Layout;
namespace MyAssembly
{
class MyPatternLayout : PatternLayout
{
public override string Header
{
get
{
var dateString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
return string.Format("[START: {0} ]\r\n", dateString);
}
}
}
}
Include this new class in your assembly, and use the new type in your file, like this:
<layout type="MyAssembly.MyPatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} - %message" />
</layout>
Since you overrode Header and Footer, you don't even need to add it here. The Header will be added every time your application starts, and every time the file rolls over.
Building on #Wizou's comment. See the DynamicPatternLayout Class documentation.
I'm actually using this difference in behaviour to have a start and end time
One important difference between PatternLayout and DynamicPatternLayout is the treatment of the Header and Footer parameters in the configuration. The Header and Footer parameters for DynamicPatternLayout must be syntactically in the form of a PatternString, but should not be marked as type log4net.Util.PatternString. Doing so causes the pattern to be statically converted at configuration time and causes DynamicPatternLayout to perform the same as PatternLayout.
Setting the type on Header to PatternString but leaving Footer as dynamic
<layout type="log4net.Layout.DynamicPatternLayout">
<param name="Header" value="%newline**** Trace Opened Local: %date{yyyy-MM-dd HH:mm:ss.fff} UTC: %utcdate{yyyy-MM-dd HH:mm:ss.fff} ****%newline" type="log4net.Util.PatternString" />
<param name="Footer" value="**** Trace Closed %date{yyyy-MM-dd HH:mm:ss.fff} ****%newline" />
</layout>
I encountered this problem and a quick workaround I used is to just use a fixed header and footer. Then do this as soon as you have the ILog object:
log.Info(string.Format("[START: {0} ]\r\n", dateString))
So just under the header you will get the information you desire.
E.g
===Start===
[START: 2012-02-23 12:12:12 ]
logs...
What I would like to do is show the installer user a list of the websites on their server and allow them to choose one (using the method described here: http://www.cmcrossroads.com/content/view/13160/120/, which now seems broken see here for the core code). The installer would then create a virtual directory in the chosen website.
However, my searching seems to have revealed that the only way to specify a website in WiX is by IP, Port, and Header. Asking for these is not very user friendly, so I'm left with the idea of writing a second custom action to get those details from a website name.
Is there a better way?
BTW This needs to work in both IIS6 and IIS7 in case that affects the answer.
OK it is possible (in IIS6 or IIS7 with Metabase compatibility), thanks to this post to the mailing list explaining the slightly bizarre way the iis:Website element works. The useful part is:
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.
So my WiX code ends up looking like:
<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>
The iis:WebAddress element should never be used but is necessary for the project to compile.
In my installer I didnt want to create a Web site. I wanted to allow the user to select an existing website. I did this with a custom action in Javascript, and one custom UI panel.
Custom Action code:
//
// 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;
}
Register the Custom Actions like this:
<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>
This is the .wxs for the UI Panel:
<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>
The UI panel presents a listbox, which is automatically populated with elements from the ListBox table with the first field of WEBSITE. This table is populated at runtime by the custom action in Javascript.
To invoke the custom action at the right time, you need something like this in the main .wxs file:
<InstallUISequence>
<Custom Action="EnumerateWebSites" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>
Whilst this question and answer are still valid, I think it's worth asking yourself whether you really want to use the website name. I you want to store it for use during uninstallation then saving the site ID is probably a better idea. In which case the website element becomes:
<iis:WebSite Id="IisWebsite" Description="Dummy" SiteId="[IIS_WEBSITEID]">
<iis:WebAddress Id="IisWebAddress" Port="80" />
</iis:WebSite>
Reply on For IisEnumSites:Exception: 0x80004005 : Modify, Mode, Record
I have similar experience and what I found so far is the site Id that extract from parseInt:
record = Session.Installer.CreateRecord(6);
record.IntegerData(1) = parseInt(site.Name.substr(6)); // WebSiteNo
I have a website with a name like W3SVC/1528550093 and I suspect 1528550093 is too big for the AvailableWebSites table.
Once I have the if statement to filter out these big number, and the script work fine.
Hope this help for other.
Based on Cheeso's answer and updated custom action to use C# with Microsoft.Web.Administration rather than Javascript with WMI. Tested against 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;
}
}
Register custom action like this:
<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" />
The dialog wxs looks like:
<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>
Note the DoAction event in the Next button control. This runs the custom action to update properties using the selected website.
And then follow Dan's answer regarding use of SiteId="*' when applying changes.