Monday, October 30, 2006

Rant: The IDS is Dead, So Lets Throw the Baby Out with the Bathwater!!

Taosecurity is fighting the good fight. I am just shaking my head since I have long given up on the security community as a whole due to short sightedness and egotism. My perceived problem with the security community is that they want some magic “Silver Bullet” that will cure all of their woes so they can kick their feet up on the desk and not have to do any real work. So if one tool doesn’t work, they just want to chuck it to the curb with their old servers.

Taosecurity hits at the nerve of the issue in pointing out the difference between “product” and “system”. The “IDS” is typically seen as things like Snort, which is a product by Rich definition. I prefer the term tool myself, since it gets at the point I always try to make, whether its security, programming, or making a peanut butter and jelly sandwich. You need the “Right tool for the job”. In addressing the problem of security, the IDS is just another tool, and you need a whole bunch of tools to solve a problem of that scope. And just like any other tool, you can improve and modify it, like say combining the firewall and IDS to try to create an IPS, but it is not going to fully replace the two separate tools any more than the Nail Gun has replaced the good ‘ole hammer in the field of carpentry. (Which draws an interesting parallel, why don’t we see carpenters making such bold claims like “The Hammer is dead” while championing the Nail Gun? It’s probably because the hammer is still the appropriate tool for the job in a given set of circumstances.) Every few months someone’s got to go around with claims like “The IDS is DEAD”, or “The Firewall won’t protect you forever”, and silly one size fits all solutions like “Smart Firewalls”, “IPS”, and whatnot and what have you come around. I’ll believe the IDS is dead when claims like C/C++/Java/Whatever dies as well.

That whole “Right Tool for the Job” concept is a hard one for security theorist to grasp, and provides a decent lead in to a rant I’ve wanted to get off my chest. To demonstrate this, I recently had a discussion with someone in the security community the other day about a series of reports I am building in BIRT. The security guy basically tried to make an argument that the same thing could be done in PERL since it is already on the system, and reduce the need to install any further software. His real hidden agenda was to take an undeserved dig at Java as a language, and Java tools. Having developed reports in PERL before, I can easily say yes, it is possible to build these reports in PERL, but the guy is off his rocker if he thinks I am actually going to do so based on his opinion any more than I would be if I had entertained the notion of trying to convince him of my point of view.

From my perspective, it simply is not the appropriate tool for the job for a number of reasons. First, BIRT is a platform dedicated to reporting, PERL is a general purpose scripting language. Second, it is much easier to maintain a reporting system in BIRT than it would be in PERL due to the reduced amount of coding. And Third, it leverages the concept of re-use, so I can apply the combined knowledge of developers who have come before me and built a system of reporting elements in an easy, flexible, and rapid development method without having to rediscover the pitfalls that they discovered. After all, a whole community building a dedicated tool would know a little more than little ‘ole me trying to develop something from the ground up, hence BIRT reason for existence and following by a dedicated developer community. So on the merits of scope, maintenance, re-use, and development time, BIRT was determined to be the appropriate tool for the job.

This is an attitude I encounter all too often. While some would consider it cockiness or outright rudeness, I actually believe it’s more of a reflection of fear. To use an analogy, they would prefer to lock the house in a vault rather than risk it being broken into, regardless of the fact that the airlessness of the vault will kill everyone inside.I could go much deeper, but that touches on another one of my beliefs of the failing of the security community, their lack of perspective in the concept of utility. While I agree that security should not be sacrificed in terms of security, I do not hold the seemingly overwhelming belief in over-securing an environment to the extreme of limiting the utility of a system to the point where it is totally unusable. I have seen this trend prevail all too often, and does not address the real issues of security, or lack there of, in an environment. All it does is limit users while giving security teams a sense of accomplishment, meanwhile the real threats are holding the keys to the kingdom.

Point is, there are proper tools for certain jobs, and tossing out Snort because someone claims IDS is dead is doing to leave you as limited as tossing out the Philips head screwdriver when some nut claims that Torx is the future.

Thursday, October 26, 2006

BIRT: Scripted Data Source to Call a Web Service

One of the things that makes BIRT a powerful platform is its how extendable it is due to the ability to invoke Java classes. In the case of the following example, I can report off of the previously developed Web Service using Apache Axis (or more specifically, the WSDL2JAVA utility), a Java class, and a scripted data source.

The first thing I need to do is create the proxy classes using Apache Axis. I used Axis 1.4 for this example since 1.2 seemed to create classes that I couldn’t develop against using Java 1.5. The web service resides at http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx, and since it is a .Net web service, I will need to pull the WSDL by calling http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx?WSDL. In order to generate the proxy classes using Axis, I had to call WSDL through Java to generate the WSDL file. Although not necessary, I included all of the JAR files under the <Axis Folder>/lib. So to create my classes, I run the following command:

java -classpath "C:\apache_axis\axis-1_4\lib\axis-ant.jar;C:\apache_axis\axis-1_4\lib\axis.jar;C:\apache_axis\axis-1_4\lib\commons-discovery-0.2.jar;C:\apache_axis\axis-1_4\lib\commons-logging-1.0.4.jar;C:\apache_axis\axis-1_4\lib\jaxrpc.jar;C:\apache_axis\axis-1_4\lib\log4j-1.2.8.jar;C:\apache_axis\axis-1_4\lib\saaj.jar;C:\apache_axis\axis-1_4\lib\wsdl4j-1.5.1.jar" org.apache.axis.wsdl.WSDL2Java http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx?WSDL

A pretty long command, a batch file or something will help in future uses. I have seen wrappers for this before, but apparently Apache did away with including one in the distribution in a previous version. Anyway, I get a set of classes creating in the namespace structure of my Web Service.

Now that I have these, I need to create a small class to handle my calls. The reason I do this is for logical reasons. Techincally, I could just import these proxy classes and invoke them directly from BIRT, however, I don’t feel that they logically make sense. In my mind, I have a process like so invisioned:

Invoke class with Employee ID ( Assign Data Set values the property values of my Class.

That simple, I don’t want to deal with the issues of calling the Locator class generated from Axis inside of BIRT. So I will create a very simple Java class that will call the web service in its constructor method, and have get methods to return the employees name, ID, and transcript information.

The class I create was done in Eclipse, including my generated Axis classes in the buildpath. It is as follows:

//I have no idea how the java.* and javax.* classes got imported??
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import my.webservice.namespace.*;

public class pojoTest {
//Local variables to store the returned information
private String name;
private String geid;
private TranscriptEntry[] trans;

//The constructor, which will invoke the web service on invokation
public pojoTest(String id)
{
//Get the locator class for my web service
GetEmployeeTranscriptLocator getEmp = new GetEmployeeTranscriptLocator();

try {
//Create the employee object, and assign the variables the results
Employee emp = getEmp.getGetEmployeeTranscriptSoap().getEmployeeAndTranscript(id);
geid = emp.getID();
name = emp.getName();
trans = emp.getTranscript();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServiceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

//My get methods for my properties
public String getName()
{
return name;
}

public String getID()
{
return geid;
}

public TranscriptEntry[] getTranscript()
{
return trans;
}

//Main will test the class from the command line
public static void main(String[] args) {
// TODO Auto-generated method stub
pojoTest p = new pojoTest("0005003335");

System.out.println(p.getID() + " - " + p.getName());
for (int x = 0; x &lt; p.getTranscript().length; x++)
System.out.print(p.getTranscript()[x].getCourseCode() + " - " + p.getTranscript()[x].getCourseName() + '\n');
p = null;

}

}


I do a test run and check my console output to verify that the web service is being called correctly. Once I am satisfied, I gather up the Axis JAR files, and my .CLASS file for my created class. Now, I read this article on BIRTWorld which seemed to indicate that I could just keep the class file in my project directory and have this work. I will be perfectly honest, I was not able to get that to work. I will have to ask Scott next time I talk to him how he got that to work. What I ended up having to do is follow the steps from here. (Sorry, apparently Actuate doesn’t believe in Anchor tags, so just look for the question “Q: How do I get data from a POJO (Plain Old Java Object)? “). I copied my compiled .CLASS file, my Apache Axis JAR files, and the proxy classes (keeping the same namespace) into a folder under the following path:

C:\Eclipse\eclipse\plugins\org.eclipse.birt.report.viewer_<version>\birt\WEB-INF\classes

Now, since this is all set up for previewing, I need to create a report that consumes this as a scripted data source. In the BIRT Perspective, I create a new report project, called pojoTest. To create the scripted data source, first, go to the Data Sources slot in either the Data Explorer or in the Outline, right mouse click, and choose New Data Source. In the Wizard, choose “Scripted Data Source” and give it a name.

Now, as an aside, I am not sure that the Scripted Data Source is really “doing” anything behind the scenes, I am visualizing that it is just a placeholder, and empty object that fufills the requirement of a dataset that it must have a data source. I could be wrong about that, however I haven’t seen anything that would lead me to believe otherwise, but once I accepted this, I was able to wrap my head around the process much easier.

With the data source created, I go to the Data Sets slot, right mouse click on it, and create a new Data Set. Most of the information is already filled in, I just change the name of the data set to something more meaningful. It is already set to the scripted data source. The columns I created for my data set are as follows:

ID
Name
CourseCode
CourseName

With the data set selected, change to the Script tab in the Report Designer, and select the Open method for the Data Set. To get the scripted data set to call the object, I used the following code in the Open Method:

obj = Packages.pojoTest("0005003335");
transcript = obj.getTranscript();
cnt = 0;
max_cnt = transcript.length;

Here, the class I created is being called via the Packages.pojoTest call. I then pull the transcript into a separate object. The cnt variable is being used in the fetch method as a placeholder to determine which record we are currently on, and the max_cnt will be used to loop through each transcript entry.

Now, the Fetch method itself, where our data set returns a row, will keep being called until it returns false. So, what I do is keep incrementing cnt, set the values of my row, and return true. If cnt > max_cnt, then return false. The Fetch method is below:

if (cnt < max_cnt)
{
row["ID"] = obj.getID();
row["Name"] = obj.getName();
row["CourseCode"] = transcript[cnt].getCourseCode();
row["CourseName"] = transcript[cnt].getCourseName();
cnt++;

return true;
}
else
return false;

Now I can use the data set like a regular query data set. I create a table element in my report, and run the report. It works like butter.

Wednesday, October 25, 2006

VMWare: Server Consolidation Using VMWare

I always go on about the virtues of VMWare. While I think it’s cool as hell that I can run different OS on a single computer, it finally dawned on me that I can use it for one of its intended purposes, server consolidation.

I had a network layout like so where I had several older, smaller PC’s serving a single purpose scattered throughout my network. This can get to be a headache to manage, not to mention expensive on the old electricity bill. Fortune has smiled on me, however, since I happened to have acquired a beefier Dell Poweredge server some time ago. While it is antiquated by today’s standards, it will work nicely in consolidating all these little floating PC’s I had doing menial tasks such as SSH serving and Squid proxying.

Using the free VMWare Server release, consolidating couldn’t be any easier. Simply set up a VM for whatever machine you choose, and either image that VM from the existing machine, or setup a fresh instance and migrate data/processes over. In my case, I chose the latter due to some goofiness with the existing setups and some broken package upgrades (not too bad considering some of these machines have been running processes for several years, and have gone through a few major distro upgrades).

Once setup, the VM instance can be set to startup when the host machine starts up by setting the startup option under the VM settings, Option Tab, and Startup/Shutdown option. That’s it.

Well, that’s not exactly it; I did run into 1 serious problem. Apparently the VMWare Registration Service kept failing to start. When I checked the Event Log, I would see the following error:

The VMware Registration Service service depends on the VMware Authorization Service service which failed to start because of the following error:
After starting, the service hung in a start-pending state.

What the hell is this? After some searching, and not finding any real applicable suggestions, I did manage to come across a decent solution on the VMWare forums (sorry, I don’t have the exact post). Basically, if you set the VMWare Authorization Service and the VMWare Registration Service to manual startup, you can set a scheduled task like below to run on System Startup. This corrected the issue for me. The actual Schedule Task Run command is as follows:

cmd /k "ping -n 61 localhost & net.exe start "vmware registration service""

Now, I have the Virtual Machine running under the 1 server, avoiding the need to have several machines scattered around my domicile. It definitely cuts down on the amount of heat generated, however the server itself is pretty loud. But, I can’t complain, maintenance is much easier now, and if you’re in the same boat, a decent server can be had on E-Bay for under a grand.

Thursday, October 19, 2006

General: Finally Found Code Formatters for Blogger

I finally found a decent formatter for code. As you might have noticed, the code on my site has been hit or miss, due to the irregularities of Blogger for Word, and Blogger in general.

Microsoft Language Specific Formatter

General Formatter

Visual Basic: How to Use ADO to Query Against a Text File

As I continue to migrate scripts over to other people for my inevitable departure from my current job, I am moving over old Visual Basic programs to VBScript for easier maintenance. One of the scripts that I am converting is a small one that gets a list of employees, and replaces their Employee ID’s with their SSN. Since, legally, we cannot store SSN information in our database, we only have a small window to grab the list of SSN’s from an encrypted file, decrypt it, and do the swaps, delete the feed, and send off the encrypted results to our vendor.

The script I have here demonstrates how to do the swap. It will query the parent database to get the list of employees, and then query a Text File using ADO. To speed things up, it only runs the query against the SSN file 1 time, and then pulls finds the results using the RecordSet.Find method. As I explaining yesterday, this is faster than having to do a repetitive query, and in this case, the performance gain is warranted. The only thing I kick myself on is I should have created a function for formatting those darn date variables.

There are a few caveats in querying a text file. First, you need the proper Connection string. I tried a few different things to get this to work right. The ODBC way kept giving me errors, so I abandoned it. I ended up with the following Connection String:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\TEMP\;Extended Properties="text;HDR=NO;FMT=Delimited"

First, the Data Source points to the directory where the files reside, in my case TEMP. In the extended properties, I am specifying that there is no header row, and the format is delimited. The key to get this to work correctly and recognize fields is in a file that must reside in the same folder as the text files called schema.ini. My ssn.txt file looks like so:

GEID;SSN;FirstName;MiddleName;LastName

As you can see, I have semi-colon delimited fields. In order for this to work, my Schema.ini file looks like so:

[SSN_GEID.txt]
Format = Delimited(;)
Col1=GEID Text
Col2=SSN Text
Col3=FNAME Text
Col4=MNAME Text
Col5=LNAME Text

That’s it. I can now query against this text file from within my script just like it was a database. Below is the final script:


Const GETSSN = "SELECT * From ssn.txt"

Const GETEMPS = "Query to get List of employees"

'Variable for output file, usinga temp variab:wle instead of a constant since we need to use todays
'date in the name
Dim OUTPUTFILE_LOCATION
Dim objFSO
Dim objStream

'The ADO objects for retrieving the data we need for employees
Dim adEmpsConnection
set adEmpsConnection = createobject("ADODB.Connection")
Dim adEmpsCommand
set adEmpsCommand = createobject("ADODB.Command")
Dim adEmpsRS

'ADO for getting SSN
Dim adSSNConnection
set adSSNConnection = createobject("ADODB.Connection")
Dim adSSNCommand
set adSSNCommand = createobject("ADODB.Command")
Dim adSSNRS

'X is a temporary counter, plus a few variables to store the numeric day and month
Dim x
dim numericMonth
dim numericDay

'set up the Employees connection
adEmpsConnection.ConnectionString = "Provider=MSDAORA.1;Password=password;User ID=user;Data Source=tserve;Persist Security Info=True"
adEmpsConnection.CursorLocation = 3
adEmpsConnection.Open

'set up the employees command
adEmpsCommand.CommandType = 1
adEmpsCommand.CommandText = GETEMPS
adEmpsCommand.ActiveConnection = adEmpsConnection

'set up the SSN connection
adSSNConnection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\TEMP\;Extended Properties=""text;HDR=NO;FMT=Delimited"""
adSSNConnection.CursorLocation = 3
adSSNConnection.Open

'set up the SSN command for a prepared query
adSSNCommand.CommandType = 1
adSSNCommand.CommandText = GETSSN
adSSNCommand.ActiveConnection = adSSNConnection

'Get the 2 digit month and 2 digit day to use in our archive filename. VBScript does not include a function for this
if (len(CStr(Month(Now))) < 2) then
numericMonth = "0" & CStr(month(now))
else
numericMonth = CStr(month(now))
end if
'Get the 2 digit day. VBScript does not include a function for this
if (len(CStr(day(Now))) < 2) then
numericDay = "0" & CStr(day(now))
else
numericday = CStr(day(now))
end if
'set file name, append todays date in yyyy-mm-dd format so it will display in Alpha order correctly
OUTPUTFILE_LOCATION = "C:\temp\feed-" &amp;amp;amp; year(now) & "-" &amp;amp;amp; numericMonth & "-" &amp;amp;amp; numericDay & ".txt"

'open file for output
'Open OUTPUTFILE_LOCATION For Output As #1
Set objFSO = createobject("scripting.filesystemobject")
Set objStream = objFSO.CreateTextFile(OUTPUTFILE_LOCATION, True)

'get the employees who have completed the course and the SSN’s
Set adEmpsRS = adEmpsCommand.Execute
set adSSNRS = adSSNCommand.Execute

'Go through the resulting recordset for each employee
'On Error Resume Next
Do While (adEmpsRS.EOF <> True)
'get the employees SSN number using the Already existing recordset and just search to find the result rather than requerying.
adSSNRS.MoveFirst
adSSNRS.Find "GEID = '" & adEmpsRS("no_emp") & "'"
'if no SSN is found, do not insert into file
If adSSNRS.EOF = False Then
'Print using Pipe delimited format, using mm-dd-yyyy for year format. First get the date format from our data record, then print
'out using the objStream.Writeline object

'Get the 2 digit month and 2 digit day to use in our archive filename. VBScript does not include a function for this
if (len(CStr(Month(adEmpsRS("dt_ch_compdt")))) < 2) then
numericMonth = "0" & CStr(month(adEmpsRS("dt_ch_compdt")))
else
numericMonth = CStr(month(adEmpsRS("dt_ch_compdt")))
end if

'Get the 2 digit day. VBScript does not include a function for this
if (len(CStr(day(adEmpsRS("dt_ch_compdt")))) < 2) then
numericDay = "0" & CStr(day(adEmpsRS("dt_ch_compdt")))
else
numericday = CStr(day(adEmpsRS("dt_ch_compdt")))
end if

objStream.Writeline adEmpsRS("nm_emp_last") & "" & adEmpsRS("nm_emp_first") & "" & adSSNRS("ssn") & "" & numericMonth & "-" &amp; numericDay & "-" & year(adEmpsRS("dt_ch_compdt"))
End If

'Go to next record and do any pending events
adEmpsRS.MoveNext
Loop

'close and free memory and close file
adEmpsConnection.Close
adSSNConnection.Close

Wednesday, October 18, 2006

Visual Basic: How to Sleep or Pause a Visual Basic Program Execution for Time Period

Another one of those quick tips that I always forget. In order to use a Sleep function in Visual Basic, include the following library declaration in any module or form:

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Lets say you need to pause a programs execution by 5 seconds. You would call the function like so:

Sleep(5000)

VBScript: Populating an Excel File from a Database Query using VBScript

While I typically do not recommend using a VBScript to populate an Excel file, I had needed to do so for a script I am passing off responsibility for. This scripts intended purpose is to pull a list of employees who have completed a course and fill out a particular template Excel file we use for mass loading entries into a database. This demonstrates how to instantiate the Excel Application, open a spreadsheet, select a worksheet, and populate from a Visual Basic script. It also demonstrates for to format a date field to the YYYYMMDD format since VBScript lacks the Format for FormatDate functions. It also makes use of External UDL files for the data connection.

'The recordset objects for Our Learning Management System
Dim sumtotal_con
Dim sumtotal_com
Dim sumtotal_rs
Set sumtotal_con = CreateObject("ADODB.Connection")
Set sumtotal_com = CreateObject("ADODB.Command")

Dim ts_con
Dim ts_com
Dim ts_rs
Set ts_con = CreateObject("ADODB.Connection")
Set ts_com = CreateObject("ADODB.Command")

'Create the objects for Excel
Dim e
Dim wb
Dim sheet
Set e = CreateObject("EXCEL.APPLICATION")
e.Workbooks.Open ("C:\auto_rpt\etc\TranscriptTemplate.xls")
Set wb = e.ActiveWorkbook
Set sheet = wb.Sheets("TRANSCRIPTS")

'generic counter and string to format month to 2 digit number
Dim x
dim numericMonth
dim numericDay

WScript.Echo "Run date: " & Now
WScript.Echo "Opening connection to Access Database"
'open the SumTotal Database
sumtotal_con.ConnectionString = "File Name=C:\auto_rpt\etc\SumTotal.udl;"
sumtotal_con.CursorLocation = 3
sumtotal_con.Open

sumtotal_com.ActiveConnection = sumtotal_con
sumtotal_com.CommandType = 1
sumtotal_com.CommandText = "SELECT GEID, AttemptEndCompleteDate, cOMPLETIONsTATUS, pass, testscore, attemptendcompletedate FROM SOMEARBITRARYDATABASETABLE where SOME = Conditions and ARE = MET"

Set sumtotal_rs = sumtotal_com.Execute
WScript.Echo "Number of records read from database: " & sumtotal_rs.RecordCount

'Open target database to insure values do not already exist
ts_con.ConnectionString = "File Name=C:\auto_rpt\etc\tserve.udl;"
ts_con.CursorLocation = 3
ts_con.Open

ts.ActiveConnection = ts_con
ts_com.CommandType = 1
ts_com.CommandText = "SELECT 'X' from TARGET_TABLE where cd_crs = 'CE' and geid = ?"
ts_com.Parameters.Append ts_com.CreateParameter("EmpID", adVarChar, adParamInput, 50)

x = 2
sumtotal_rs.MoveFirst
While (sumtotal_rs.EOF = False)
ts_com("EmpID") = sumtotal_rs("GEID")
set ts_rs = ts_com.execute

if (ts_rs.RecordCount > 0) then
‘These are arbitrary fields in the spreadsheet. For this demo, you don’t really need
‘to know what they stand for, just that some fields are populating hard coded ‘values, and some are values in the returned query
sheet.Cells(x, 1).Value = "NATL"
sheet.Cells(x, 2).Value = sumtotal_rs("GEID")
sheet.Cells(x, 6).Value = "CE"
sheet.Cells(x, 8).Value = "CESLScript"
sheet.Cells(x, 9).Value = sumtotal_rs("AttemptEndCompleteDate")
sheet.Cells(x, 10).Value = "F = Finished"
sheet.Cells(x, 17).Value = sumtotal_rs("testscore")
end if

x = x + 1
sumtotal_rs.MoveNext
Wend

'Get the 2 digit month. VBScript does not include a function for this
if (len(CStr(Month(Now))) < 2) then
numericMonth = "0" & CStr(month(now))
else
numericMonth = CStr(month(now))
end if

'Get the 2 digit day. VBScript does not include a function for this
if (len(CStr(day(Now))) < 2) then
numericDay = "0" & CStr(day(now))
else
numericday = CStr(day(now))
end if

Set sheet = Nothing
wb.SaveAs "C:\auto_rpt\ce\Transcript-" & year(now) & numericMonth & numericDay & ".xls"
wb.Close
Set wb = Nothing

e.Quit
Set e = Nothing

ts_con.close
sumtotal_con.Close

Set sumtotal_rs = Nothing
Set sumtotal_com = Nothing
Set sumtotal_con = Nothing

Set ts_rs = Nothing
Set ts_com = Nothing
Set ts_con = Nothing

There are a couple of things to note in the above. The above query to check for the existence of a record before putting it into the template is not the most efficient. The better way to go is to pull a recordset of all employees with my course, and compare against the recordset in local memory. This will alleviate the overhead over querying the database for each record, which the above code does not do. More design is required for that, and for the purposes of this script, this overhead is acceptable. Remember; always develop with a goal in mind and within the confines of your project. If performance were not an acceptable tradeoff for development time, then I would have gone that route. The second assumption is that we do not have write access to the target database, making the mass feed file necessary.

Sunday, October 15, 2006

Java: The Google Web Toolkit

I was recently turned on to the Google Web Toolkit. I find the concept of being able to build web applications in a decent development environment with pre-built UI widgets and an actual debugger to be very enticing. What has surprised me is that tutorials on getting started are few and far between. In this article, I will outline how I installed the Google Web Toolkit under Ubuntu, how to create a new Eclipse Project, how to import, and show a basic UI program I write and compiled using the Google Web Toolkit.


First, download the Google Web Toolkit from their website. For me I downloaded the GWT for Linux, version 1.1. Installation is simple, just extract the archive.


Now that the archive is extracted (for me, in ~/gwt-linux-1.1.10), I can create my project and application skeleton. There are a few scripts that are provided with GWT for creating various things, such as projects, applications, Junit testing files, but I am only concerned with the project creator and the application creator. First thing I want to do is create my Project. I do so by changing into my GWT folder, and running the projectCreator script. I am going to create a project called InterfaceTest for Eclipse. So to create my project I run the following command:


./projectCreator -eclipse InterfaceTest -out InterfaceTest


What this does is it creates an Eclipse project called InterfaceTest in the output folder InterfaceTest. Next, I need to generate my application skeleton. I do so by running the following command:


./applicationCreator -eclipse InterfaceTest -out InterfaceTest com.digiassn.blogspot.client.InterfaceTest


It is very important to keep the project names consistent when using the Application Creator and Project Creator, otherwise Eclipse will not compile the project correctly.


Now that I have my project skeleton, I can import the project into Eclipse. From Eclipse, go to the File menu, select Import, under the General item, choose Existing Projects into Workspace, and navigate to the folder where the newly created project is stored. Once done, the new InterfaceTest project is in my workspace.


Now I want to go into my HTML file that was created using the Application Creator, and add a DIV tag to use as an “anchor” to find and insert a rollover text item. Using the Package Explorer, I navigate to the InterfaceTest/src/com.digiassn.blogspot/public/InterfaceTest.html file, open it up in an editor (make sure it is in the editor, otherwise it will try to open in a browser by default), and add a DIV tag anywhere in the BODY section. I do not change any of the HTML elements that were added by default, I only add the folowing tag:



Now, I open the InterfaceTest/src/com.digiassn.blogspot.client/InterfaceTest.java file for editing. The following code uses the import all the necessary UI libraries, and create a page that will add a rollover text item to the DIV tag, set up a response to a rollover event that will change the text and to the current date and add a table on Mouse Over, and change it to something else when the mouse is moved away. It will also create a menu with 1 drilldown item, and add a single item that will have an action that creates an alert message.


package com.digiassn.blogspot.client;


import com.google.gwt.core.client.EntryPoint;

import com.google.gwt.user.client.ui.MouseListener;

import com.google.gwt.user.client.ui.Label;

import com.google.gwt.user.client.ui.RootPanel;

import com.google.gwt.user.client.ui.Widget;

import com.google.gwt.user.client.ui.MenuBar;

import com.google.gwt.user.client.ui.MenuItem;

import com.google.gwt.user.client.Command;

import com.google.gwt.user.client.Window;

import com.google.gwt.user.client.ui.Grid;

import java.util.Date;


public class InterfaceTest implements EntryPoint {


public void onModuleLoad() {

final Date d = new Date();

final Label roller = new Label("Default value");

final Grid g = new Grid(1,1);

final MenuBar m = new MenuBar();

final MenuBar m2 = new MenuBar();

final Command c = new Command() {

public void execute() {

Window.alert("Hello");

}

};

final MenuItem mi = new MenuItem("Test", c);

//Create a menu with 1 drilldown and 1 item that will make a popup

m2.addItem(mi);

m.addItem("Top", m2);

RootPanel.get().add(m);

//Setup the table to add when we do a rollover

g.setBorderWidth(2);

g.setText(0, 0, "This is a test");

//Add the text with the rollover to the DIV tag

RootPanel.get().add(roller);

//Add the mouse listener events.

roller.addMouseListener(

new MouseListener() {

public void onMouseEnter(Widget sender) {

//Set the text to the date from the date object

roller.setText(d.toString());

//Add the table to the DIV tag

RootPanel.get().add(g);

}


//Change the text whent he mouse leaves, and remove the

//table

public void onMouseLeave(Widget sender) {

roller.setText("Leaving element...");

RootPanel.get().remove(g);

}


// Do nothing

public void onMouseDown(Widget sender, int x, int y) {}


// Do nothing

public void onMouseMove(Widget sender, int x, int y) {}


// Do nothing

public void onMouseUp(Widget sender, int x, int y) {}

}

);

}

}


The onMouseUp, onMouseMove, and onMouseDown methods are blank, but they must be declared in order for the MouseListener event to work. I could have created the MouseListener outside of the add method, but I mixed up the declarations of that and the Menu to demonstrate the different methods that can used.


Once this program has been entered, I can run it from within Eclipse by going to the Project Explorer, right mouse clicking on the InterfaceTest project, and choosing Run As. Under the Java Application section, I choose the InterfaceTest configuration, and run the program. This will launch a mini-browser with the newly created application. Once I am through debugging (I will ignore that my menu overlaps my rollover when I drill down), I can compile my final program. I open up a terminal again, go to my Eclipse Workspace, and run the InterfaceTest-compile script. This will create a ./www/com.digiassn.blogspot.InterfaceTest directory, with my HTML files. All I need to do is copy these to a web server and run.


Not much to it. I did notice that the compiler was able to use the java.util.Date object without any fuss. I tried to use the com.google.gwt.emul.java.util package, but did not have any luck. Since the java.util packages seemed to work, I wonder what other internal packages can be used. Obviously the UI libraries won't work. I look forward to trying some other projects in the GWT.

Saturday, October 14, 2006

Linux: Allowing Root Login Under Ubuntu

In a previous article, I demonstrated how you could get root access under Ubuntu. I had a user comment that they would also like to be able to log in as root, not just escalate privileges after login. This really goes against the Ubuntu security paradigm, but is actually a very easy thing to do.


In Ubuntu, the mechanism that prevents the user from logging in as root is the login manager. By default, Ubuntu uses GDM by default, so the instructions will be different under Xubuntu and Kubuntu, or if you have changed your Display Manager/Login Manager. To configure GDM to allow root login, the /etc/gdm/gdm.conf-custom file needs to be modified. The reason you modify this file instead of the /etc/gdm/gdm.conf file is the the latter can be changed during an update, and in later versions of GDM, the gdm.conf-custom file has been provided for user overrides of settings. Any changes in the /etc/gdm/gdm.conf-custom file will take precedence. Under the [security] section, add the following entry:


AllowRoot=true


Thats all there is to it. This will only allow root logins from the local terminal. If you are using a remote X session, you will still not be able to login as root unless you override the AllowRemoteRoot setting. Fortunately, depending on your point of view, Linux does not use anything to neuter the root account.

Thursday, October 12, 2006

Linux: Installing Fritz 9 on Ubuntu using Wine

One of the things that really annoys me is that the recent kernel with Ubuntu doesn't have the kernel headers in the package tree yet. What that means for me is that I have to reboot to an earlier version of the kernel in order to use VMWare. Since, at the current moment, the only thing I am doing with my Windows setup under VMWare is playing Fritz 9 and analyzing games, I wanted a better way to do it than having to boot into VMWare each time. Fortunatly, Wine has made tremendous strides since the last time I had worked with it. I used this article as a baseline for my installation instructions, and it is where I borrowed the run script from.


First thing I did was start up Synaptic and installed the following two packages:


Wine

LibWine


Once installed, I opened up a terminal window and typed in “wine” at the command prompt. Of course, the first time I run this, I need to wait while the configuration is created. I also decide I want to reconfigure some default options, just in case some libraries need to be included, so after Wine completed, I type in “winecfg” at the command prompt to bring up the Wine Configuration Utility.


I set the following options for Fritz:

Windows Version: Windows 98

I go to Drivers, and hit AutoDetect for devices


That is it for my configuration. Now, I mount my CD-Rom and run the setup program with Wine.


mount /media/cdrom

cd /media/cdrom

wine Setup.exe


From here, the Setup Wizard starts successfully. I go through the standard Fritz 9 Installation using the following options:

Directory: C:\Fritz9\

Documents and databases: C:\Fritz9\Documents

(Note: In both cases, I had to manually navigate to My Computer\C: in order to set these folders.)

I took out the Midi Files, Speech Files, 3D, Spoken Notation, and Speech Engine.


After the installation hung, I followed the instructions on the above linked article and copied the STDOLE* files from my existing Windows 98 installation over to my fake Windows System folder. In addition to the STDOLE files, I also put any other libraries that were listed as “exported” under the WineDBG output when the installation crashed, so the following libraries were copied from my Windows\System folder to the System folder under my Fake Windows:

STDOLE*

RPC*

OLE*

KERNEL32*


I re-ran the installation program for Wine, and put in the following library overrides:

ole32 (native, builtin)

oleaut32 (native, builtin)

oledlg (native, builtin)

rpcrt4 (native, builtin)


With these updates, I went back to the /media/cdrom/Setup folder and re-ran the setup program:

wine setup.exe


That appeared to solve all of my installation woes. The Direct X setup didn't quite go as well as I was expecting, but that is OK. I just ignored the failed installation.


So I do my first run like so:

cd /home/digiassn/.wine/drive_c/Fritz9

wine ChessProgram9.exe


The problem that I run into is that the fonts are screwed up... so I try to getting rid of my library overrides to see if that fixes the problem. That did not fix my problem, so the next thing I tried was to remove the \fonts files, which fixed my problems. When I actually got Fritz to run, it couldn't find my CDROM drive. I noticed when i was doing the registering that the first drive I was prompted for when looking for the CDROM was not the correct drive assigned from the auto-assignment. So I restarted the configuration tool, and removed all drives except the CDROM and the C: drive, and set the CDROM drive type to CDROM. I reran the ChessProgram9 executable. I got as far as getting Fritz started, however the program hung when I went to any menus. So I got back to the drawing board and re-override all my libraries as illustrated above since I removed the overrides trying to correct the font issue. That also did not fix my issue. I could play chess just fine, but each time I tried to open a menu, it caused the program to lock. I looked at the diagnostic messages in the terminal when running, and noticed that there were a lot of errors “toolbar” and “dbghelp”, so I looked to see if there was a way to override these libraries as well.


I didn't quite get this up and running 100 percent, but I did manage to get it somewhat usable. With keyboard shortcuts and all the menu icons enabled, I was able to enter my games and do what I needed to do. This isn't ideal, but it will do for now. I have found some issues where a patch and recompile will fix the problem, but at this point in time, I will live with the issue until I have more time.


Update: I installed the Wine packages for Ubuntu from the Wine HQ site, which work much better than the default Ubuntu Wine packages. I still had the issue with the toolbar, however rather than hanging now, the program just crashes immediatly. So I tried installing Fritz 8 using the above instructions, and sure enough, Fritz 8 works without any problems. I will have to live with the features that Fritz 8 offers, but since I just catalog and analyze my games, I don't believe I will miss much.

Coldfusion: Consuming Web Services with Coldfusion

Since I had developed such a pretty Web Service in Web Designer 2005 Express previously, I wanted to see the different methods to consume this service in various languages. I already demonstrated how to do so in ASP .Net, so now I have decided to go a little off the beaten path and do so in Coldfusion MX. Fortunately, doing so in Coldfusion MX using Dreamweaver as the development environment is incredibly easy.



In Dreamweaver 8, there is a really cool Web Service Component tool under the Application/Component tab. If you click on the “+” after selecting “Web Service” from the drop down, you will get a little wizard asking for the URL to retrieve the WSDL for the web service. That’s it; the Proxy generator will take care of the rest.



To consume the service, you can drag over the various methods into your Coldfusion page, or you can manually put in the Coldfusion tags to do so. In my case, I want to use the “getEmployeeAndTranscript” method in my web service, and return an Employee object. I need to pass in an ID as well, so I would use the CFINVOKE take like so:



<cfinvoke webservice="http://training.citibankus.citigroup.net/test/ws/getEmployeeTranscript/Service1.asmx?WSDL"

method="getEmployeeAndTranscript"

returnvariable="aEmployee">

     <cfinvokeargument name="id" value="0005003335"/>

</cfinvoke>



This will return my Employee object with the Transcript brought back as an array. Since the transcript is an array, I can loop through the results by retrieving the array length. Below is the code I used to return my results:



<cfoutput>

     #aEmployee.name#<br />

     #aEmployee.ID#<br />

     Total Courses: #ArrayLen(aEmployee.transcript.transcriptEntry)#<br />

     <table border="1">

       <tr>

          <td> </td>

          <td><strong>Course Code</strong></td>

          <td><strong>Course Name</strong></td>

          <td><strong>Completion Date</strong></td>

       </tr>

       

       <cfloop index="i" from="1" to="#ArrayLen(aEmployee.transcript.transcriptEntry)#">

          <tr>

          <td>#i#</td>

          <td>#aEmployee.transcript.transcriptEntry[i].CourseCode#</td>

          <td>#aEmployee.transcript.transcriptEntry[i].CourseName#</td>

          <td>#DateFormat(aEmployee.transcript.transcriptEntry[i].completionDate, "dd-mmm-yyyy")#</td>

          </tr>

     </cfloop>

     

     </table>          

</cfoutput>



The first thing I am doing in this snippet is returning the Employee information. Then I use the ArrayLen function to output the number of transcript entries. I then put a table in place and put up the header for the columns. I then use a CFLOOP tag to go through each element in the array. Coldfusion did something really strange with the Array itself. It created a Object called ArrayOfTranscript, with a property called transcriptEntry which contained the actual array. I suppose this is more inline with the original Web Service relationship I created. If I didn’t have the web service viewer that Dreamweaver provides, I would not have been able to determine this structure.

Wednesday, October 11, 2006

Sigh: No Really Cool Article

I am a little disapointed that the management staff over the project I mentioned previously has declined having an article written about it due to the fact that it is a "limited public release". Kind of a bummer, I personally think its the coolest thing since sliced bread.

Friday, October 06, 2006

ASP.Net: A More Complex Web Service to Retrieve Employee and Transcript Information

One of the current ideas I am throwing around to people who will listen at the office is the idea of implementing commonly used report items as web services rather than as database queries. So I set about building a few web services to retrieve Employee and Transcript information.

Basically, I want two functions, one to retrieve information on an Employee with Transcript information, and one to retrieve a list of transcript information only. I played around with several methods of doing this in .Net, since transcript information is typically a list of classes that have been completed. The problem with passing this information through SOAP is that the classes need to be serializable, and by default Collections and Array are not. I tried creating a basic Dataset and populating the information manually, but this turned out to be a little cumbersome to decipher on the client end, and I felt this was not going to work with my next task, which is to build reports off of these web services. If I get the OK from the original author of these connectors, I will write more about them later.

In this project, I have a few identifiable classes. Of course, there are Employees, which will consist of a ID, a Name, and a Transcript. The Transcript is a list of classes that have been completed. Transcripts are built up by TranscriptEntries (which I should have called classes to be consistent). TranscriptEntries contain information such as Course Name, Course Code, and Completion Date.

The tricky part is the Transcript. Since it needs to be a collection of some sort, I had to inherit from the CollectionBase class. The reason I consider this strange is that if I implement a collection, I get all sorts of errors about the lack of an ISerializable interface, yet if I inherit from a CollectionBase, all I need to do is expose a default method (which in my case was the Index function), and an Add function. That’s it. The rest takes care of itself when I publish this as a web service. The following project is developed in Visual Web Developer Express 2005.

So below are the 3 data structure classes that were defined in the project, in order or TranscriptEntry, Transcript, and Employee.

File: transcript.vb
Imports Microsoft.VisualBasic

Public Class Transcript
    Inherits CollectionBase

    Public Sub Add(ByVal value As TranscriptEntry)
        Me.InnerList.Add(value)
    End Sub

    Default Public ReadOnly Property Item(ByVal Index As Integer) As TranscriptEntry
        Get
            If InnerList.Count() = 0 Then
                Exit Property
            End If
            'maintain compatibility (Underlying ArrayList 0 based)
            Item = InnerList.Item(Index)
        End Get
    End Property

End Class

Public Class TranscriptEntry
    Dim ccourseCode As String
    Dim ccourseName As String
    Dim cdateComplete As Date

    Property CourseCode() As String
        Get
            Return ccourseCode
        End Get
        Set(ByVal value As String)
            ccourseCode = value
        End Set
    End Property

    Property CourseName() As String
        Get
            Return ccourseName
        End Get
        Set(ByVal value As String)
            ccourseName = value
        End Set
    End Property

    Property CompletionDate() As Date
        Get
            Return cdateComplete
        End Get
        Set(ByVal value As Date)
            cdateComplete = value
        End Set
    End Property
End Class

File: Employee.VB
Imports Microsoft.VisualBasic

Public Class Employee
    Private cvalid As String
    Private cvalname As String
    Private ctrnTranscript As New Transcript

    Property ID() As String
        Get
            Return cvalid
        End Get
        Set(ByVal value As String)
            cvalid = value
        End Set
    End Property
    Property Name() As String
        Get
            Return cvalname
        End Get
        Set(ByVal value As String)
            cvalname = value
        End Set
    End Property
    Property Transcript() As Transcript
        Get
            Return ctrnTranscript
        End Get
        Set(ByVal value As Transcript)
            ctrnTranscript = value
        End Set
    End Property
End Class

Very basic. Now, I create a new WebService called GetEmployeeTranscript with two methods, getEmployeeAndTranscript, and one called getTranscript. Both take the Employee ID as a parameter, and return the appropriate classes. Below is the code for this service:

Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols

<WebService(Namespace:="http://localhost")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class GetEmployeeTranscript
    Inherits System.Web.Services.WebService

    <WebMethod()> _
    Public Function getEmployeeAndTranscript(ByVal id As String) As Employee
        Dim e As New Employee
        Dim con As New System.Data.OleDb.OleDbConnection
        Dim com As New System.Data.OleDb.OleDbCommand
        Dim rs As System.Data.OleDb.OleDbDataReader
        Dim p As New System.Data.OleDb.OleDbParameter

        Dim t As TranscriptEntry

        con.ConnectionString = "Provider=OraOLEDB.Oracle.1;Password=password;Persist Security Info=True;User ID=user;Data Source=TSERVE"
        con.Open()

        com.CommandType = Data.CommandType.Text
        com.Connection = con
        com.CommandText = "select no_emp, nm_emp_last || ', ' || nm_emp_first name from employees where no_emp = ?"

        p.ParameterName = "GEID"
        p.Value = id
        com.Parameters.Add(p)

        Try
            rs = com.ExecuteReader

            While (rs.Read)
                e.ID = rs("no_emp")
                e.Name = rs("name")
            End While

            rs.Close()
            rs = Nothing
        Catch ex As Exception
            'insert some exception handler here
        End Try

        com.CommandText = "select course_histories.cd_crs, nm_crs, course_histories.dt_ch_compdt, course_histories.cd_ch_status from course_histories, courses where course_histories.cd_crs = courses.cd_crs and no_emp = ?"

        Try
            rs = com.ExecuteReader

            While (rs.Read)
                t = New TranscriptEntry

                Try
                    t.CourseCode = rs("cd_crs")
                Catch ex11 As Exception
                    t.CourseCode = ""
                End Try
                Try
                    t.CourseName = rs("nm_crs")
                Catch ex As Exception
                    t.CourseName = ""
                End Try
                Try
                    t.CompletionDate = CDate(rs("dt_ch_compdt"))
                Catch ex1 As Exception
                    t.CompletionDate = CDate("01-JAN-1900")
                End Try

                e.Transcript.Add(t)
                t = Nothing
            End While

            rs.Close()
        Catch ex As Exception
            'Insert some exception handler here
        End Try

        p = Nothing
        com.Parameters.Clear()
        con.Close()
        com = Nothing
        con = Nothing
        rs = Nothing

        Return e
        e = Nothing
    End Function

    <WebMethod()> _
    Public Function getTranscript(ByVal id As String) As Transcript
        Dim con As New System.Data.OleDb.OleDbConnection
        Dim com As New System.Data.OleDb.OleDbCommand
        Dim rs As System.Data.OleDb.OleDbDataReader
        Dim p As New System.Data.OleDb.OleDbParameter

        Dim transcript As New Transcript
        Dim t As TranscriptEntry

        con.ConnectionString = "Provider=OraOLEDB.Oracle.1;Password=password;Persist Security Info=True;User ID=user;Data Source=TSERVE"
        con.Open()

        com.CommandType = Data.CommandType.Text
        com.Connection = con
        com.CommandText = "select course_histories.cd_crs, nm_crs, course_histories.dt_ch_compdt, course_histories.cd_ch_status from course_histories, courses where course_histories.cd_crs = courses.cd_crs and no_emp = ?"

        p.ParameterName = "GEID"
        p.Value = id
        com.Parameters.Add(p)

        Try
            rs = com.ExecuteReader

            While (rs.Read)
                t = New TranscriptEntry

                Try
                    t.CourseCode = rs("cd_crs")
                Catch ex11 As Exception
                    t.CourseCode = ""
                End Try
                Try
                    t.CourseName = rs("nm_crs")
                Catch ex As Exception
                    t.CourseName = ""
                End Try
                Try
                    t.CompletionDate = CDate(rs("dt_ch_compdt"))
                Catch ex1 As Exception
                    t.CompletionDate = CDate("01-JAN-1900")
                End Try

                transcript.Add(t)
                t = Nothing
            End While

            rs.Close()
        Catch ex As Exception
            'Insert some exception handler here
        End Try

        p = Nothing
        com.Parameters.Clear()
        con.Close()
        com = Nothing
        con = Nothing
        rs = Nothing

        Return transcript
        transcript = Nothing
    End Function
End Class

One thing to note, I had a hell of a time with the Oracle driver when I published this. I kept getting errors like “The 'OraOLEDB.Oracle.1' provider is not registered on the local machine” and driver not found. Of course, I knew better since I had the Oracle 9I client installed. Turns out it is a permission issue on the Oracle folder. I just set the permission to full control on Everyone and let it trickle down to the child folders. This is not adviseable on a production machine, however. I am only doing this since it is a development machine.

Once published, I go to the publishing location http://localhost/GetEmployeeTranscript/Service.asmx to get an idea if the publish was successful. One of the nice features about .Net is you can preview your web services before you consume them. One little tip, if you need the WSDL version of the description, you can append ?WSDL to the end of the asmx url. So for me I would use http://localhost/GetEmployeeTranscript/Service.asmx?WSDL.

Now, I need to build a client. I start a new project (it really doesn’t matter what language it is in, but I keep in Web Developer .Net since I do not want to change into another IDE). I add a Web Reference to the above URLs, and import my web reference into the project. The client itself is simply 3 text boxes, one to get an Employee ID, and two to display the output. I also add a listbox to display the transcript of the Employee. I add the code to retrieve the results to a Command button. Below is the code for the client itself:

<%@ Page Language="VB" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim temp_id As String
        Dim tempEmployee As localhost.Employee
        Dim transcriptService As New localhost.GetEmployeeTranscript
        dim transcriptEntryTemp as localhost.TranscriptEntry  
                        
        temp_id = txtGEID.Text
        tempEmployee = transcriptService.getEmployeeTranscript(temp_id)
        
        txtName.Text = tempEmployee.Name
        txtGEID2.Text = tempEmployee.ID
          
                
        for each transcriptEntrytemp in tempEmployee.Transcript
            listbox1.Items.Add(transcriptEntrytemp.CourseCode & ": " & transcriptEntrytemp.CourseName & " - " & transcriptEntrytemp.CompletionDate)
        next
        
        transcriptService = nothing
        tempEmployee = nothing      
    End Sub


</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="Label1" runat="server" Text="Enter a GEID"></asp:Label>
        <asp:TextBox ID="txtGEID" runat="server"></asp:TextBox>
        <br />
        <asp:Button ID="cmdGetTranscript" runat="server" OnClick="Button1_Click" Text="Button" /><br />
        <br />
        <asp:Label ID="Label2" runat="server" Text="Results"></asp:Label>
        <br />
        <asp:TextBox ID="txtName" runat="server"></asp:TextBox>
        <br />
        <asp:TextBox ID="txtGEID2" runat="server"></asp:TextBox><br />
        <asp:ListBox ID="ListBox1" runat="server" Height="265px" Width="692px"></asp:ListBox>
         
    </div>
    </form>
</body>
</html>

Nothing to it really. Now that it is published, I need to find out how to build reports on this service.  I do have a ODA component for Actuate that will allow this, however I am awaiting permission from the author to write about it and showcase it. Otherwise, I can always do it the manual way and write either an ASPX or Coldfusion page to do so.