Create workbook with multiple sheets from Queries - excel

I am using ColdFusion 2016 and I've discovered that the server that I'm using only has CF9, I'm pretty new to this. I've been working on updating existing code to fit what the users now want. So far I've managed, but this is beyond me. I've got a website that generates an excel workbook with one sheet. It uses HTML and a query to create it. Starts with the query name in A1 the report date in A3, the table headers in A5:H5 and then the data in A6:H53 (The exact length could vary, but always from column A - H). I'll post what is being used to create the workbook. What i want to do is use 3 more queries to add 3 more sheets to the workbook. I've tried adding a function that I found here and that didn't do any good. I tried modifying the existing code some to try and use all 4 queries, no joy.
Any help would be appreciated. Let me know if I need to add more detail.
Here's the code:(I added comments for what I added trying to get this to work)
<cfsilent>
<!--- *******************************************************************
Filename: execSummary_Excel.cfm, v1.0 03/07/2012
Created By: Original Writer
Description: Excel report export for Executive Summary Report.
Change History:
Date........Name...........Description of Change........................
08/01/2012 Original Writer Added committed column.
02/28/2013 Original Writer Added stateGM and GM.
*************************************************************************--->
<cfinvoke component="financial.financial" method="getExecSummary" returnvariable="qExecSummary">
<cfinvokeargument name="level" value="#URL.level#" />
<cfinvokeargument name="stateGM" value="#URL.stateGM#" />
<cfinvokeargument name="GM" value="#URL.GM#" />
<cfinvokeargument name="engVP" value="#URL.engVP#" />
<cfinvokeargument name="engDir" value="#URL.engDir#" />
</cfinvoke>
<!---Added this to test if I can get more than one sheet to the Workbook--->
<cfinvoke component="financial.financial" method="getExecSummary_OLD" returnvariable="qExecSummary_OLD">
<cfinvokeargument name="level" value="#URL.level#" />
<cfinvokeargument name="stateGM" value="#URL.stateGM#" />
<cfinvokeargument name="GM" value="#URL.GM#" />
<cfinvokeargument name="engVP" value="#URL.engVP#" />
<cfinvokeargument name="engDir" value="#URL.engDir#" />
</cfinvoke>
<!--- Get Report Date since qExecSummary is more complex than the other report queries --->
<cfquery name="qRpt_Date" datasource="#application.dsn#">
SELECT DISTINCT rpt_date
FROM fin_data
</cfquery>
<cfsetting requesttimeout="5000" />
</cfsilent>
<!---Added this as a function that should allow me to use multiple queries and create a workbook with more than one sheet--->
<cfscript>
//Create new workbook with one sheet
//by default that sheet is the active sheet
Workbook = SpreadsheetNew("ExecSummary");
//Add Data to the sheet
format1.bold="true";
formatNum.dataformat="0.00%";
Spreadsheetformatcell(Workbook,format1,1,1);
SpreadSheetSetCellValue(Workbook,"Executive Summary Report",1,1);
Spreadsheetformatcell(Workbook,format1,3,1);
SpreadSheetSetCellValue(Workbook,"#dateFormat(now(),'mm/dd/yyyy')#",3,1);
SpreadSheetSetCellValue(Workbook,"Data Date",5,1);
SpreadSheetSetCellValue(Workbook,"Level",5,2);
SpreadSheetSetCellValue(Workbook,"Name",5,3);
SpreadSheetSetCellValue(Workbook,"Description",5,4);
SpreadSheetSetCellValue(Workbook,"Budget",5,5);
SpreadSheetSetCellValue(Workbook,"Commited",5,6);
SpreadSheetSetCellValue(Workbook,"Spent YTD",5,7);
SpreadSheetSetCellValue(Workbook,"% Spent",5,8);
arr="Here";
writedump(arr);
//if (qExecSummary.recordCount) {
// rowNum = 6;
// arr="Here";
// writedump(rowNum);
//alert(qExecSummary.recordCount);
//for(dataRow in qExecSummary){
//SpreadSheetSetCellValue(Workbook,dateFormat(qRpt_Date.rpt_Date, 'mm/dd/yyyy'),rowNum,1);
//SpreadSheetSetCellValue(Workbook,dataRow.responsible,rowNum,2);
//SpreadSheetSetCellValue(Workbook,dataRow.name,rowNum,3);
//SpreadSheetSetCellValue(Workbook,dataRow.Description,rowNum,4);
//SpreadSheetSetCellValue(Workbook,dataRow.bud_sum,rowNum,5);
//SpreadSheetSetCellValue(Workbook,dataRow.committed,rowNum,6);
//SpreadSheetSetCellValue(Workbook,dataRow.Spent_YTD,rowNum,7);
/*if (qExecSummary.bud_sum NEQ 0){
Spreadsheetformatcell(Workbook,formatNum,rowNum,8);
//percentSpent="#(qExecSummary.Spent_YTD/qExecSummary.bud_sum)*100#";
SpreadSheetSetCellValue(Workbook,(dataRow.Spent_YTD/dataRow.bud_sum)*100,rowNum,8);
}
else {*/
//SpreadSheetSetCellValue(Workbook,0,rowNum,8);
//}
//rowNum++;
//}
//End of WriteOutput
//} else {
// SpreadSheetAddRows(Workbook,"No results for your criteria.");
//}
</cfscript>
<!---
<cffunction name="QueriesToXLS" access="public">
<cfargument name="queryArr" required="true">
<cfargument name="sheetNameArr" required="false">
<cfset tempPath="C:\Temp\ExecutiveSummary" & ".xls">
<cfset counter= 1>
<cfloop array="#ARGUMENTS.queryArr#" index="i" >
<cfset sheetName="Sheet#counter#">
<cfif isDefined("ARGUMENTS.sheetNameArr")>
<cfset sheetName=ARGUMENTS.sheetNameArr[counter]>
</cfif>
<cfspreadsheet action="update" filename="#tempPath#" query="i" sheetName="#sheetName#"/>
<cfset counter += 1>
</cfloop>
<cfreturn SpreadsheetRead(tempPath)>
</cffunction>
<cfset xlsData = QueriesToXLS([qExecSummary,qExecSummary],["ExecutiveSummary","ExecutiveSummaryOLD"])>
--->
<cfheader name="Content-Disposition" value='attachment; filename="execSummaryNew.xls"'>
<!---cfcontent type="application/msexcel" variable="#SpreadsheetReadBinary(xlsData)#" reset="true"--->
<cfcontent type="application/vnd.ms-excel"> <!---This is where the application type is being set to Excel--->
<!---html xmlns:x="urn:schemas-microsoft-com:office:excel">
<head>
</head>
<h2>Executive Summary Report</h2>
<cfoutput>
<p>#dateFormat(now(), 'mm/dd/yyyy')#</p>
</cfoutput>
<table id="tableOne" class="yui" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th>Data Date</th>
<th>Level</th>
<th>Name</th>
<th>Description</th>
<th>Budget</th>
<th>Committed</th>
<th>Spent YTD</th>
<th>% Spent</th>
</tr>
</thead>
<cfif qExecSummary.recordCount>
<tbody>
<cfoutput query="qExecSummary">
<tr>
<td>#dateFormat(qRpt_Date.rpt_Date, 'mm/dd/yyyy')#</td>
<td>#qExecSummary.responsible#</td>
<td>#qExecSummary.name#</td>
<td>#qExecSummary.Description#</td>
<td>#qExecSummary.bud_sum#</td>
<td>#qExecSummary.committed#</td>
<td>#qExecSummary.Spent_YTD#</td>
<td><cfif qExecSummary.bud_sum NEQ 0>
#numberFormat((qExecSummary.Spent_YTD/qExecSummary.bud_sum)*100,"9.9")#%
<cfelse>
0
</cfif>
</td>
</tr>
</cfoutput>
</tbody>
<cfelse>
<tr>
<td colspan="9">No results for your criteria.</td>
</tr>
</cfif>
</table>
</html--->
EDIT
I have tried following the answer provided on the question referenced in the duplicate. I am not able to get it to work with the query that I have. I have added the following code in place of the script that I had:
EDIT 2
Updated this script, still not working. errors on the writeOutput().I'm not sure how to implement using a query to create the data for the rows?
<cfscript>
//Create new workbook with one sheet
//by default that sheet is the active sheet
Workbook = SpreadsheetNew("ExecSummary");
//Add Data to the sheet
format1.bold="true";
formatNum.dataformat="0.00%";
Spreadsheetformatcell(Workbook,format1,1,1);
SpreadSheetSetCellValue(Workbook,"Executive Summary Report",1,1);
Spreadsheetformatcell(Workbook,format1,3,1);
SpreadSheetSetCellValue(Workbook,"#dateFormat(now(),'mm/dd/yyyy')#",3,1);
SpreadSheetSetCellValue(Workbook,"Data Date",5,1);
SpreadSheetSetCellValue(Workbook,"Level",5,2);
SpreadSheetSetCellValue(Workbook,"Name",5,3);
SpreadSheetSetCellValue(Workbook,"Description",5,4);
SpreadSheetSetCellValue(Workbook,"Budget",5,5);
SpreadSheetSetCellValue(Workbook,"Commited",5,6);
SpreadSheetSetCellValue(Workbook,"Spent YTD",5,7);
SpreadSheetSetCellValue(Workbook,"% Spent",5,8);
if (qExecSummary.recordCount) {
rowNum = 6;
//writeOutput(query="qExecSummary");
for(dataRow in qExecSummary){
SpreadSheetSetCellValue(Workbook,dateFormat(qRpt_Date.rpt_Date, 'mm/dd/yyyy'),rowNum,1);
SpreadSheetSetCellValue(Workbook,dataRow.responsible,rowNum,2);
SpreadSheetSetCellValue(Workbook,dataRow.name,rowNum,3);
SpreadSheetSetCellValue(Workbook,dataRow.Description,rowNum,4);
SpreadSheetSetCellValue(Workbook,dataRow.bud_sum,rowNum,5);
SpreadSheetSetCellValue(Workbook,dataRow.committed,rowNum,6);
SpreadSheetSetCellValue(Workbook,dataRow.Spent_YTD,rowNum,7);
if (qExecSummary.bud_sum NEQ 0){
Spreadsheetformatcell(Workbook,formatNum,rowNum,8);
percentSpent="#(qExecSummary.Spent_YTD/qExecSummary.bud_sum)*100#";
SpreadSheetSetCellValue(Workbook,(qExecSummary.Spent_YTD/qExecSummary.bud_sum)*100,rowNum,8);
}
else {
SpreadSheetSetCellValue(Workbook,0,rowNum,8);
}
rowNum++;
}
//End of WriteOutput
} else {
SpreadSheetAddRows(Workbook,"No results for your criteria.");
}
</cfscript>
However it is showing errors on the line with the Output. I don't know what to change it to. I've searched for what is usable in the cfscript tags and found this, but there's nothing there that looks like it would help?
Made update to the <cfscript> now I'm getting this error:
FINAL EDIT
Here's the working script:
<cfsilent>
<!--- *******************************************************************
Filename: execSummary_Excel.cfm, v1.0 03/07/2012
Created By: Original Writer
Description: Excel report export for Executive Summary Report.
Change History:
Date........Name...........Description of Change........................
08/01/2012 Original Writer Added committed column.
02/28/2013 Original Writer Added stateGM and GM.
*************************************************************************--->
<cfinvoke component="financial.financial" method="getExecSummary" returnvariable="qExecSummary">
<cfinvokeargument name="level" value="#URL.level#" />
<cfinvokeargument name="stateGM" value="#URL.stateGM#" />
<cfinvokeargument name="GM" value="#URL.GM#" />
<cfinvokeargument name="engVP" value="#URL.engVP#" />
<cfinvokeargument name="engDir" value="#URL.engDir#" />
</cfinvoke>
<!---Added this to test if I can get more than one sheet to the Workbook--->
<cfinvoke component="financial.financial" method="getExecSummary331" returnvariable="qExecSummary331">
<cfinvokeargument name="level" value="#URL.level#" />
<cfinvokeargument name="stateGM" value="#URL.stateGM#" />
<cfinvokeargument name="GM" value="#URL.GM#" />
<cfinvokeargument name="engVP" value="#URL.engVP#" />
<cfinvokeargument name="engDir" value="#URL.engDir#" />
</cfinvoke>
<!--- Get Report Date since qExecSummary is more complex than the other report queries --->
<cfquery name="qRpt_Date" datasource="#application.dsn#">
SELECT DISTINCT rpt_date
FROM fin_data
</cfquery>
<cfsetting requesttimeout="5000" />
</cfsilent>
<!---Added this as a function that should allow me to use multiple queries and create a workbook with more than one sheet--->
<cfscript>
//Create new workbook with one sheet
//by default that sheet is the active sheet
Workbook = SpreadsheetNew("ExecSummary");
//Add Data to the sheet
//Formatting
format1.bold="true";
format1.fontsize=12;
format1.font="Calibri";
format2.bold="true";
format2.fontsize=18;
format2.font="Calibri";
formatNum.dataformat="0.0%";
//adding the Headers
Spreadsheetformatcell(Workbook,format2,1,1);
Spreadsheetformatcell(Workbook,format1,3,1);
Spreadsheetformatcell(Workbook,format1,5,1);
Spreadsheetformatcell(Workbook,format1,5,2);
Spreadsheetformatcell(Workbook,format1,5,3);
Spreadsheetformatcell(Workbook,format1,5,4);
Spreadsheetformatcell(Workbook,format1,5,5);
Spreadsheetformatcell(Workbook,format1,5,6);
Spreadsheetformatcell(Workbook,format1,5,7);
Spreadsheetformatcell(Workbook,format1,5,8);
SpreadSheetSetCellValue(Workbook,"Executive Summary Report",1,1);
SpreadSheetSetCellValue(Workbook,"#dateFormat(now(),'mm/dd/yyyy')#",3,1);
SpreadSheetSetCellValue(Workbook,"Data Date",5,1);
SpreadSheetSetCellValue(Workbook,"Level",5,2);
SpreadSheetSetCellValue(Workbook,"Name",5,3);
SpreadSheetSetCellValue(Workbook,"Description",5,4);
SpreadSheetSetCellValue(Workbook,"Budget",5,5);
SpreadSheetSetCellValue(Workbook,"Commited",5,6);
SpreadSheetSetCellValue(Workbook,"Spent YTD",5,7);
SpreadSheetSetCellValue(Workbook,"% Spent",5,8);
arr=server.ColdFusion.ProductVersion;
if (qExecSummary.recordCount) {
rowNum = 6;
do {
SpreadSheetSetCellValue(Workbook,dateFormat(qRpt_Date.rpt_date,'mm/dd/yyy'),rowNum,1);
SpreadSheetSetCellValue(Workbook,qExecSummary.responsible[rowNum-5],rowNum,2);
SpreadSheetSetCellValue(Workbook,qExecSummary.name[rowNum-5],rowNum,3);
SpreadSheetSetCellValue(Workbook,qExecSummary.Description[rowNum-5],rowNum,4);
SpreadSheetSetCellValue(Workbook,qExecSummary.bud_sum[rowNum-5],rowNum,5);
SpreadSheetSetCellValue(Workbook,qExecSummary.committed[rowNum-5],rowNum,6);
SpreadSheetSetCellValue(Workbook,qExecSummary.Spent_YTD[rowNum-5],rowNum,7);
if (qExecSummary.bud_sum[rowNum-5] NEQ 0){
Spreadsheetformatcell(Workbook,formatNum,rowNum,8);
SpreadSheetSetCellValue(Workbook,(qExecSummary.Spent_YTD[rowNum-5]/qExecSummary.bud_sum[rowNum-5]),rowNum,8);
}
else {
SpreadSheetSetCellValue(Workbook,0,rowNum,8);
}
rowNum++;
} while (rowNum - 6 LT qExecSummary.recordCount);
} else {
SpreadSheetAddRows(Workbook,"No results for your criteria.");
}
SpreadsheetCreateSheet(Workbook,"ExecSummaryTest");
SpreadsheetSetActiveSheet(Workbook,"ExecSummaryTest");
//Formatting
format1.bold="true";
format1.fontsize=12;
format1.font="Calibri";
format2.bold="true";
format2.fontsize=18;
format2.font="Calibri";
formatNum.dataformat="0.0%";
Spreadsheetformatcell(Workbook,format2,1,1);
Spreadsheetformatcell(Workbook,format1,3,1);
Spreadsheetformatcell(Workbook,format1,5,1);
Spreadsheetformatcell(Workbook,format1,5,2);
Spreadsheetformatcell(Workbook,format1,5,3);
Spreadsheetformatcell(Workbook,format1,5,4);
Spreadsheetformatcell(Workbook,format1,5,5);
Spreadsheetformatcell(Workbook,format1,5,6);
Spreadsheetformatcell(Workbook,format1,5,7);
Spreadsheetformatcell(Workbook,format1,5,8);
SpreadSheetSetCellValue(Workbook,"Executive Summary Report",1,1);
SpreadSheetSetCellValue(Workbook,"#dateFormat(now(),'mm/dd/yyyy')#",3,1);
SpreadSheetSetCellValue(Workbook,"Data Date",5,1);
SpreadSheetSetCellValue(Workbook,"Level",5,2);
SpreadSheetSetCellValue(Workbook,"Name",5,3);
SpreadSheetSetCellValue(Workbook,"Description",5,4);
SpreadSheetSetCellValue(Workbook,"Budget",5,5);
SpreadSheetSetCellValue(Workbook,"Commited",5,6);
SpreadSheetSetCellValue(Workbook,"Spent YTD",5,7);
SpreadSheetSetCellValue(Workbook,"% Spent",5,8);
arr=server.ColdFusion.ProductVersion;
if (qExecSummary331.recordCount) {
rowNum = 6;
do {
SpreadSheetSetCellValue(Workbook,dateFormat(qRpt_Date.rpt_date,'mm/dd/yyy'),rowNum,1);
SpreadSheetSetCellValue(Workbook,qExecSummary331.responsible[rowNum-5],rowNum,2);
SpreadSheetSetCellValue(Workbook,qExecSummary331.name[rowNum-5],rowNum,3);
SpreadSheetSetCellValue(Workbook,qExecSummary331.Description[rowNum-5],rowNum,4);
SpreadSheetSetCellValue(Workbook,qExecSummary331.bud_sum[rowNum-5],rowNum,5);
SpreadSheetSetCellValue(Workbook,qExecSummary331.committed[rowNum-5],rowNum,6);
SpreadSheetSetCellValue(Workbook,qExecSummary331.Spent_YTD[rowNum-5],rowNum,7);
if (qExecSummary331.bud_sum[rowNum-5] NEQ 0){
Spreadsheetformatcell(Workbook,formatNum,rowNum,8);
SpreadSheetSetCellValue(Workbook,(qExecSummary331.Spent_YTD[rowNum-5]/qExecSummary331.bud_sum[rowNum-5]),rowNum,8);
}
else {
SpreadSheetSetCellValue(Workbook,0,rowNum,8);
}
rowNum++;
} while (rowNum - 6 LT qExecSummary331.recordCount);
} else {
SpreadSheetAddRows(Workbook,"No results for your criteria.");
}
SpreadsheetSetActiveSheet(Workbook,"ExecSummary");
</cfscript>
<cfheader name="Content-Disposition" value='attachment; filename="execSummaryNew.xls"'>
<cfcontent type="application/msexcel" variable="#SpreadsheetReadBinary(Workbook)#" reset="true">

WriteOutput() is for displaying data on screen. To add data to a spreadsheet, you need to use spreadsheet functions. In your scenario, you can use a for/in loop to iterate through the query rows. Then use SpreadSheetSetCellValue(sheet, value, row, col) to populate the individual cells.
Update 1:
If you run the code below (from "Edit 2"), in a brand new .cfm script, you will see it works perfectly. So any errors must be coming from a different part of the code. If you are having trouble tracking down errors, I would recommend starting over with a clean slate. Create a brand new script with very simple code: just the minimum necessary to generate the spreadsheet. Omit anything extra like functions, downloads, etcetera and get the basic code working first.
<cfscript>
// FOR DEMO ONLY: Create manual queries
qRpt_Date = queryNew("");
queryAddColumn(qRpt_Date, "Rpt_Date", [now()]);
qExecSummary = queryNew("");
queryAddColumn(qExecSummary, "responsible", [1,12,13]);
queryAddColumn(qExecSummary, "bud_sum", [0,50,100]);
queryAddColumn(qExecSummary, "Spent_YTD", [0,50,100]);
queryAddColumn(qExecSummary, "Name", ["Name A","Name B","Name C"]);
queryAddColumn(qExecSummary, "Description", ["Description A","DescriptionB","Description C"]);
queryAddColumn(qExecSummary, "Committed", [0,50,100]);
//Create new workbook with one sheet
//by default that sheet is the active sheet
Workbook = SpreadsheetNew("ExecSummary");
//Add Headers
headerTextFormat.bold=true;
Spreadsheetformatcell(Workbook,headerTextFormat,1,1);
SpreadSheetSetCellValue(Workbook,"Executive Summary Report",1,1);
Spreadsheetformatcell(Workbook,headerTextFormat,3,1);
SpreadSheetSetCellValue(Workbook, dateFormat(now(),'mm/dd/yyyy'),3,1);
SpreadSheetSetCellValue(Workbook,"Data Date",5,1);
SpreadSheetSetCellValue(Workbook,"Level",5,2);
SpreadSheetSetCellValue(Workbook,"Name",5,3);
SpreadSheetSetCellValue(Workbook,"Description",5,4);
SpreadSheetSetCellValue(Workbook,"Budget",5,5);
SpreadSheetSetCellValue(Workbook,"Commited",5,6);
SpreadSheetSetCellValue(Workbook,"Spent YTD",5,7);
SpreadSheetSetCellValue(Workbook,"% Spent",5,8);
//Add data detail
if (qExecSummary.recordCount) {
// Start populating the spreadsheet on this row number
// Change as needed.
rowNum = 6;
// Loop through query rows
for(dataRow in qExecSummary) {
// add
SpreadSheetSetCellValue(Workbook,dateFormat(qRpt_Date.rpt_Date, 'mm/dd/yyyy'),rowNum,1);
SpreadSheetSetCellValue(Workbook,dataRow.responsible,rowNum,2);
SpreadSheetSetCellValue(Workbook,dataRow.name,rowNum,3);
SpreadSheetSetCellValue(Workbook,dataRow.Description,rowNum,4);
SpreadSheetSetCellValue(Workbook,dataRow.bud_sum,rowNum,5);
SpreadSheetSetCellValue(Workbook,dataRow.committed,rowNum,6);
SpreadSheetSetCellValue(Workbook,dataRow.Spent_YTD,rowNum,7);
if (qExecSummary.bud_sum != 0){
Spreadsheetformatcell(Workbook,formatNum,rowNum,8);
SpreadSheetSetCellValue(Workbook,(qExecSummary.Spent_YTD/qExecSummary.bud_sum)*100,rowNum,8);
}
else {
SpreadSheetSetCellValue(Workbook,0,rowNum,8);
}
rowNum++;
}
//End of WriteOutput
} else {
SpreadSheetAddRows(Workbook,"No results for your criteria.");
}
//For test purposes, save the results to a file
SpreadSheetWrite(Workbook, "c:/path/to/yourFile.xls", true);
WriteOutput("Done!");
</cfscript>
Update 2:
As noted in the comments, for..in query loops are not supported in CF9. If you are using an older version of CF, then you will need to use a from/to loop instead.
// Loop through query rows
for(x = 1; x lte yourQueryName.recordCount; x++) {
SpreadSheetSetCellValue(Workbook, yourQueryName.YourColumnName[x], theSheetRowNum, 1);
// ...
theSheetRowNum++;
}
Side note, personally I prefer cfscript. However, if you feel more comfortable with CFML you can always rewrite the example. Just get rid of the cfscript tags and replace:
<cfscript>
SomeFunctionName(....);
</cfscript>
with:
<cfset SomeFunctionName(....)>

Related

trying to wrap a structure with another set of structure

I am trying to wrap a structure with another structure, but definitely i am missing something
i want to show it like this
account: {
"name": "Example Account",
"details": "https://www.example.com"
}
i am trying this function, but i am missing something, probably i should use structmap, not sure
<cffunction name="SO">
<cfargument name="name" required="true" type="struct">
<cfargument name="data" required="true" type="struct">
<cfloop collection="#arguments.Data#" index="i">
<cfset arguments.Name['#lcase(i)#'] = arguments.Data[i]>
</cfloop>
<cfreturn arguments.Name>
</cffunction>
When I first looked at this, I thought you essentially wanted to have a new struct with a "name" (containing a string) and a "data" containing a struct.
Your initial setup is essentially
<cfset name = "Test" >
<cfset data = { details: "detail1" , details2: "detail2" } >
When dealing with most data structures in CF, I usually find it much easier and cleaner to work with CFSCRIPT instead of tags. So I came up with
<cfscript>
function SO ( required String name, required Struct data ) {
var retval = { name:"",data:{} } ;
retval.name = arguments.name ;
arguments.data.map( function(key, val) {
retval.data[key.lcase()] = val ;
} ) ;
return retval;
}
writedump( SO(name,data) ) ;
</cfscript>
Trying to unwind this, it appears that all you're trying to do is add a "name" value to your "data" struct. This can easily be done with
<cffunction name="SO_tags">
<cfargument name="name" required="true" type="string">
<cfargument name="data" required="true" type="struct">
<cfset retval2 = arguments.data >
<cfset retval2.name = arguments.name>
<cfreturn retval2>
</cffunction>
<cfset structure = {"account":SO_tags(name,data)}>
<cfdump var = #structure#>
Or using script, it can come down to just a single line.
<cfscript>
function SO3 ( required String name, required Struct data ) {
return arguments.data.append( {"name":arguments.name} ) ;
}
writedump( {"account":SO3(name,data)} ) ;
</cfscript>
Depending on how this fits into the rest of your code, you may not even need a function.
https://cffiddle.org/app/file?filepath=b6e92a83-ebc9-40eb-a712-f402d7f9ed85/f4fb9f8e-3b60-4a64-8c02-2cd106736df0/4e00abae-b949-4934-86e8-870745259127.cfm
EDIT: Changed output to nest it within an "account" structure.

simplesearch modx with date dropdown integration

Iam new to modx(revolution version 2.5.7) and simple search(simplesearch-1.9.2-pl)
I need to add date dropdown (need to fetch results with matching date which is a template variable as type date ) with simplesearch extra in modx plugin. I have attached screenshot of my searchpage for reference. Please help me to solve this.
https://forums.modx.com/thread/95128/advsearch-to-show-search-value-based-on-dropdown-box.
After many painful debugging , got my code working.
[b]My code[/b] ,
[[!AdvSearchForm? &tpl=`AdvanceSearchForm_tpl`]]
</h1>
<h2>Results</h2>
<p>[[!AdvSearch? &parents=`12`&queryHook=`FilterCalenderSnippet` ]]
[b]form tpl (AdvanceSearchForm_tpl) :--[/b]
[code]<form id="[[+advsearch.asId]]_advsea-form" class="advsea-form" action="[[~[[+advsearch.landing]]]]" method="[[+advsearch.method]]">
<fieldset>
<input type="hidden" name="id" value="[[+advsearch.landing]]" />
<input type="hidden" name="asId" value="[[+advsearch.asId]]" />
[[+advsearch.helpLink]]<input type="text" id="[[+advsearch.asId]]_advsea-search" name="[[+advsearch.searchIndex]]" value="[[+advsearch.searchValue]]" />
[[$SeminarCalendarDateChunk]]// give the dropdown of dates,you can put your form elements
[[+advsearch.liveSearch:isnot=`1`:then=`<input type="submit" id="[[+advsearch.asId]]_advsea-submit" name="sub" value="[[%advsearch.search? &namespace=`advsearch` &topic=`default`]]" />`:else`=``]]
</fieldset>
</form>
[[+advsearch.resultsWindow]]
[b]Query Hook snippet(FilterCalenderSnippet)[/b]
[ul]
[li]My Date tv is EventDateTv[/li]
[/ul]
[code]
<?php
$andConditions = array();
// here i need to show events between one given input month. so I did some php to fetch first and last days of given month
if (!empty($_REQUEST['calendar_date'])) {
$dateToTest = $_REQUEST['calendar_date'];// my form element name is calendar_date
$lastday = date('Y-m-t',strtotime($dateToTest));
$andConditions['tv.EventDateTv:>='] = $dateToTest;
$andConditions['tv.EventDateTv:<='] = $lastday ;
}
if (!empty($andConditions)) {
$qhDeclaration = array(
'qhVersion' => '1.3',
'andConditions' => $andConditions
);
$hook->setQueryHook($qhDeclaration);
}
return true;
[/code]`enter code here`

Lotus Notes and C#: NotesRichTextItem how to recreate content or loop through elements in respective order

I am currently exporting a lotus notes database using the C# Interop Domino assembly from NuGet,
I haven't found a way to identify objects or elements in a NotesRichTextItem in the order they were entered, for example, maybe I enter a paragraph first, then a table , then an attachment.
Is there a way to loop through the elements in their respect order ?
I have found a way to find elements with FindFirstElement, but you have to pass what element type you are looking for, this is very difficult as extracting all elements without order would make the content to lose its context.
thanks
There is a way to analyze Notes document's RichText items using DXL - a special XML format for Notes. Use DxlExporter to export a Notes document to DXL format. You can "walk" then through XML and get the content of RichText item with elements in right order.
For this RichText item e.g.
you'd get this DXL
<item name='Body'>
<richtext>
<pardef id='1'/>
<par def='1'>aaaaaaa</par>
<table widthtype='fixedleft' refwidth='1.0667in'>
<tablecolumn width='0.6729in'/>
<tablecolumn width='0.3938in'/>
<tablerow>
<tablecell>
<pardef id='3' keepwithnext='true' keeptogether='true'/>
<par def='3'>111</par></tablecell>
<tablecell>
<pardef id='4' keepwithnext='true' keeptogether='true'/>
<par def='4'>222</par></tablecell>
</tablerow>
<tablerow>
<tablecell><par def='3'>333</par></tablecell>
<tablecell><par def='4'>444</par></tablecell>
</tablerow>
</table>
<pardef id='5' leftmargin='1.2500in' list='bullet'/>
<par def='5'>xxx</par>
<par def='5'>yyy</par>
<par def='5'>zzz</par>
<pardef id='6' leftmargin='1in'/>
<par def='6'>
<attachmentref name='icon16.gif' displayname='icon16.gif'>
<picture height='34px' width='61px'>
<notesbitmap>lQAmAAAAAAAAAAAAA...</notesbitmap>
<caption>icon16.gif</caption>
</picture>
</attachmentref>
</par>
</richtext>
</item>
Here is a Java agent which exports selected documents to a file.
import lotus.domino.*;
public class JavaAgent extends AgentBase {
#Override
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
DocumentCollection dc = agentContext.getUnprocessedDocuments();
String filename = "c:/temp/exportDocs.dxl";
Stream stream = session.createStream();
if (stream.open(filename)) {
stream.truncate();
DxlExporter exporter = session.createDxlExporter();
exporter.setRichTextOption(0);
exporter.setMIMEOption(0);
stream.writeText(exporter.exportDxl(dc));
} else {
System.out.println("Cannot open " + filename);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Unfortunately the API gives you no way to do this:
Navigation is within elements of the same type. You can find or get
the first element of a type, the next element of a type, and the nth
element of a type. You cannot find or get an element regardless of
type.
http://publib.boulder.ibm.com/infocenter/domhelp/v8r0/topic/com.ibm.designer.domino.main.doc/H_NOTESRICHTEXTNAVIGATOR_CLASS.html
UPDATE: I forgot to mention that you may want to check out a third-party tool from Genii Software called MidasLSX that possibly could help you. http://www.geniisoft.com/showcase.nsf/MidasLSX

Displaying an Image/Icon inside an EXT.NET GridPanel Cell depinding on value

Greeting,
I need to display an image/icon AND a value in an EXT.NET cell (of a gridpanel). The value comes from the datatable. The value can either be a string ‘good’ or ‘bad’ and resides in the column ‘Status’.
For example: good accept.png or bad cancel.png.
Layout:
<ext:GridPanel ID="grid" runat="server">
<Store>
<ext:Store ID="Store1" runat="server">
<Reader>
<ext:ArrayReader>
<Fields>
<ext:RecordField Name="status" Mapping="Status" />
</Fields>
</ext:ArrayReader>
</Reader>
</ext:Store>
</Store>
<ColumnModel ID="ColumnModel1" runat="server">
<Columns>
<ext:Column DataIndex="status" Header="Status" Width="160">
</ext:Column>
</Columns>
</ColumnModel>
</ext:GridPanel>
Now I have seen some exaples but I can’t seem to get the picture, I think it has something to do with this:
<script type="text/javascript">
function imgRenderer(value, meta, record, rowIndex, colIndex, store) {
if(data == ‘good’)
{
return "<img src='accept.png'/>"
}
else (data == "bad")
{
return "<img src='cancel.png'/>"
}
}
</script>
More info:
http://miamicoder.com/2009/displaying-an-image-inside-an-ext-js-gridpanel-cell-part-2/
http://techmix.net/blog/2010/11/25/add-button-to-extjs-gridpanel-cell-using-renderer/
I forgot to return the value.
<ext:Column ColumnID="columnStatus" DataIndex="omschrijving" Header="Status" Width="150">
<Renderer Handler="return imgRenderer(value);" />
</ext:Column>
You have options, although I think there is just one minor syntax error that is causing the problem.
Option 1:
In your existing code, you should change data to value.
Example
// existing
if(data == ‘good’)
// revised
if(value == ‘good’)
Option 2:
Rename your images to same value as the value, although this would still require using the value attribute instead of data. Rename accept.png to good.png, and same renaming with the "bad" image. With this change you shouldn't require the if|else statement.
Example
// existing
if(data == ‘good’)
{
return "<img src='accept.png'/>"
}
else (data == "bad")
{
return "<img src='cancel.png'/>"
}
// revised
return '<img src="' + value + '.png"/>';
Hope this helps.

In WiX how can I select an IIS website by name?

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.

Resources